RegExp.prototype.lastIndex
Holds the index where the next match attempt starts for a global or sticky regex.
regexp.lastIndex- Added in JavaScript ES3 (1999)
Return value
numberA non-negative integer: the position in the string at which the next exec() or test() begins. Initially 0.
Errors
TypeError on assignment when the property has been made non-writable, for example by Object.freeze() on the regex.
Changelog
- PHP ES5lastIndex became a non-configurable, non-enumerable but writable own data property of each RegExp instance; it is not inherited from the prototype.
- PHP ES2015With the u flag, automatic advancement moves by whole code points rather than UTF-16 code units.
lastIndex is the one piece of mutable state on a regex, and the source of more confusing bugs than any other part of the API. It is an own data property of each instance — writable, but not enumerable and not configurable.
When it is consulted
Only when the regex carries g or y. Under those flags exec() and test() start searching at lastIndex, move it to the end of the match on success, and reset it to 0 on failure. Without those flags the property is still there and still writable, but every search starts at 0 and the value is never updated.
Why shared regexes break
A module-level /g/ regex carries its position across calls and even across different input strings. Validate four identical strings and you get true, false, true, false. Fixes, in order of preference: drop the g flag if you only need a boolean; use String.prototype.matchAll(), which works on an internal clone; or set re.lastIndex = 0 before each use.
Writing to it deliberately is how sticky tokenizers move through their input.
Example
When to use it
- Rewind a global regex to the start of the string before reusing it
- Drive a sticky tokenizer by setting the offset before each exec()
- Resume scanning a long document from a saved position
Frequently asked questions
Why does my global regex give alternating true and false results?
Because test() left lastIndex past the previous match and the next call resumed from there. Remove the g flag if you only need a boolean, or set re.lastIndex = 0 before each call.
Do I need to reset lastIndex when using matchAll or replaceAll?
No. Both operate on an internal clone of the regex, so the original's lastIndex is neither read nor modified. exec(), test(), match() and replace() do interact with it.
Does lastIndex do anything without the g or y flag?
No. The property exists and is writable on every regex, but exec() and test() only consult and update it when global or sticky is set. Otherwise every search starts at index 0.
More examples
The shared global regex bug
lastIndex is not reset between different strings — only when a match fails. The last line shows the manual reset that rescues a regex you cannot change, but removing the g flag is the real fix.
const shared = /^\d+$/g;
console.log(["1", "2", "3", "4"].map(v => shared.test(v)));
// [ true, false, true, false ]
const stateless = /^\d+$/;
console.log(["1", "2", "3", "4"].map(v => stateless.test(v)));
// [ true, true, true, true ]
console.log(["1", "2"].map(v => { shared.lastIndex = 0; return shared.test(v); }));Ignored without g or y
The plain regex ignores the value entirely — it neither starts there nor updates it. The sticky regex requires the match to begin exactly at lastIndex, so offset 0 fails even though "b" is present one character later.
const plain = /b/;
plain.lastIndex = 10;
console.log(plain.exec("abc").index, plain.lastIndex); // 1 10
const sticky = /b/y;
sticky.lastIndex = 1;
console.log(sticky.exec("abc")[0], sticky.lastIndex); // "b" 2
sticky.lastIndex = 0;
console.log(sticky.exec("abc"), sticky.lastIndex); // null 0
Discussion