RegExp.prototype.lastIndex

Holds the index where the next match attempt starts for a global or sticky regex.

Signature
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

Try it yourself
Loading editor…
Press Run to execute the code.

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.

Example · javascript
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.

Example · javascript
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

  • Be the first to comment on this lesson.