flashman
← All posts

Sticky regex y flag and lastIndex debugging

Debug a sticky JavaScript regex that never skips ahead by comparing the y flag with the g flag, then reading match indexes from a single matchAll pass.

2026-10-16 · 6 min read · Rahul Chitturi

  • regex
  • javascript
  • parsing

The JavaScript y flag is sticky. A sticky search may succeed only at lastIndex. It does not scan forward to the next place the pattern could match. The g flag does scan forward, and it also updates lastIndex after a hit.

On the text ababa, /a/g reports indexes 0, 2, and 4. /a/gy reports only index 0. After that hit, lastIndex is 1, the next character is b, and sticky matching stops. The other two a characters are still in the string. The flag refused to skip the b.

A failed sticky search resets lastIndex

If exec fails on a global or sticky expression, the spec sets lastIndex back to 0. A tokenizer that does not advance past a character it could not match will call exec again at the start and loop. The loop is the caller's retry, not the engine searching ahead.

Multiline ^ does not cancel that rule. On foo bar\nfoo baz, /^foo/gm matches at 0 and at 8. /^foo/gmy matches only at 0, because the second attempt starts at the index just after the first foo, which is a space, not at the next line. Sticky ^ can match at lastIndex. It cannot jump to a later line to find one.

  • Log lastIndex before and after each exec.
  • Treat a null result as a hard stop unless you move the index yourself.
  • Add y only when the next match must start at the current index.
  • Keep a copy of the pattern without y for the search that is allowed to skip.

Read one matchAll pass, not a carried index

String.prototype.matchAll throws unless the expression is global. A pattern with y and no g fails that check. A pattern with gy runs one sticky pass over the string and returns the indexes it could take without skipping.

Flashman's regex tester builds a new RegExp and calls matchAll once. It shows those indexes, up to 50 of them. It does not keep lastIndex between runs, and it will not reproduce a loop that resets the index and retries. Paste ababa with flags g, then with flags gy, and diff the index lists. Use the JSON formatter when the scanner's log is a list of offsets you need to compare with that output.

Try these tools