
Regex greedy vs lazy matching: kenapa pattern Anda menangkap terlalu banyak
Dipublikasikan 24 Jul 2026
Anda menulis <b>.*</b> untuk mengambil isi tag bold, mengujinya pada <b>Hello</b>, dan hasilnya sempurna. Lalu Anda jalankan pada HTML asli yang punya dua tag bold di baris yang sama — <b>Hello</b> and <b>World</b> — dan alih-alih mendapat dua match, Anda malah mendapat satu match raksasa yang membentang di kedua tag. Ini bukan bug pada regex engine Anda; ini adalah perilaku default dari *, +, dan {n,m}, dan solusinya cukup satu karakter begitu Anda paham apa yang sebenarnya terjadi.
Kenapa .* menangkap lebih dari yang diharapkan
Secara default, quantifier dalam regex bersifat greedy — .* bukan berarti “cocokkan beberapa karakter,” melainkan “cocokkan sebanyak mungkin karakter, lalu baru mundur jika sisa pattern benar-benar mengharuskannya.”
Mari telusuri <b>.*</b> terhadap <b>Hello</b> and <b>World</b> langkah demi langkah:
<b>cocok dengan<b>pertama..*mulai dengan melahap seluruh sisa string — semuanya sampai akhir, termasuk<b>World</b>yang kedua.- Engine kemudian perlu menemukan
</b>untuk menyelesaikan match, jadi ia mulai mundur dari ujung.*satu karakter demi satu karakter. - Posisi pertama (dihitung mundur dari akhir) di mana
</b>cocok adalah</b>paling terakhir dalam string — bukan yang pertama yang “secara logis” seharusnya jadi titik berhenti.
Jadi hasil match-nya adalah <b>Hello</b> and <b>World</b> secara keseluruhan, karena greedy matching selalu mencoba string terpanjang terlebih dahulu dan hanya menyusutkannya sesedikit mungkin agar sisa pattern berhasil cocok.
Solusinya: buat quantifier menjadi lazy dengan ?
Menambahkan ? tepat setelah quantifier akan membalikkan sifatnya dari greedy menjadi lazy (juga disebut “non-greedy” atau “reluctant”): *?, +?, ??, {n,m}?.
Quantifier lazy melakukan kebalikannya: ia mulai dengan mencocokkan sesedikit mungkin karakter, lalu baru memperluas jika sisa pattern belum bisa cocok.
<b>.*?</b> terhadap string yang sama:
<b>cocok dengan<b>pertama..*?mulai dengan mencocokkan nol karakter.- Engine memeriksa: apakah
</b>cocok tepat di sini? Tidak (posisi masih di “Hello…”, belum sampai</b>) — jadi.*?diperluas satu karakter dan diperiksa lagi. - Ini berulang karakter demi karakter sampai
.*?sudah melahap tepatHello, dan pada titik itu</b>langsung cocok.
Hasilnya: <b>Hello</b> dan <b>World</b> kembali sebagai dua match terpisah, yang hampir selalu itulah yang sebenarnya Anda inginkan saat mem-parsing struktur mirip tag atau delimiter.
Kapan Anda justru butuh greedy (bukan sekadar “default yang salah”)
Greedy bukan kesalahan dalam bahasa regex — ia benar untuk kasus lain yang sama seringnya terjadi: menangkap batas terluar dari sesuatu, bukan unit terkecil di dalamnya. Jika Anda mengekstrak “semua yang ada di antara { pertama dan } terakhir” dari teks mirip JSON (misalnya untuk mengambil satu object utuh terlepas dari nesting-nya), greedy adalah pilihan yang tepat, sementara lazy justru akan berhenti di } bagian dalam pertama dan menghasilkan potongan yang terpotong dan tidak valid.
Aturan praktisnya: lazy untuk potongan kecil berulang yang dibatasi delimiter (tag, string berkutip, item list); greedy untuk “ambil seluruh rentang terluar.”
Referensi cepat
| Pattern | Perilaku | Gunakan saat |
|---|---|---|
.*, .+, {n,m} |
Greedy — cocok dengan yang terpanjang | Anda ingin rentang terluar, atau hanya ada satu match dalam string |
.*?, .+?, {n,m}? |
Lazy — cocok dengan yang terpendek | Anda punya beberapa potongan mirip yang dibatasi delimiter dan ingin masing-masing terpisah |
Kalau Anda tidak yakin perilaku mana yang sebenarnya sedang dilakukan sebuah pattern pada input asli Anda (bukan string uji), menjalankannya pada teks multi-match yang sesungguhnya di regex tester dengan highlight match secara langsung akan langsung memperlihatkan perbedaannya — Anda akan melihat satu blok highlight raksasa untuk greedy versus beberapa blok terpisah untuk lazy, yang biasanya lebih cepat dibanding menalar karakter demi karakter.