Text and data

JavaScript global regex: why repeated tests change

A JavaScript RegExp with g or y keeps a lastIndex cursor. Reusing it with test or exec can start the next search after the previous match, so repeated tests of the same string can differ. This state belongs to the RegExp object, not to changing input text.

A regular expression finds matches and capture positions within sample text.
A match position and the cursor for the next search are different pieces of state.

Run the same test three times

This small example uses one persistent RegExp instance and the same input each time. The first match advances lastIndex to 1. The second search begins there and fails because no further a exists; that failure resets the cursor. The third test starts at zero again.

Run this snippet in a JavaScript console to reproduce object reuse. Regex Tester is useful for exploring patterns and captures, but an isolated tester run does not reproduce every lifecycle choice in your application.

const re = /a/g;
console.log(re.test("a"), re.lastIndex); // true 1
console.log(re.test("a"), re.lastIndex); // false 0
console.log(re.test("a"), re.lastIndex); // true 1

Choose a pattern lifetime that fits the operation

For a yes/no whole-string validation, a non-global expression is usually simpler. Anchors define the extent of the match, while the absence of g avoids the persistent cursor. Anchors by themselves do not neutralize the global flag.

For iterating through matches, state is intentional. A loop using exec needs the global flag and must handle zero-length matches safely, or use a suitable matchAll workflow. Sticky y differs from g because it requires a match exactly at lastIndex rather than searching ahead.

Resetting lastIndex can make an existing shared expression behave predictably for one operation, but shared mutable state is easy to misuse across helper calls. A fresh RegExp per independent task is often easier to reason about.

Operation choices
TaskExampleState consideration
Simple validation/^[A-Z]+$/.test(value)No global cursor
Independent global scannew RegExp(pattern, "g")Fresh cursor per scan
Reuse an instance intentionallyre.lastIndex = 0 before useCaller owns the reset
Sticky token scanning/token/yMust match exactly at the cursor

Add a regression case with repeated calls

When a validation helper fails intermittently, call it twice with the same known-good input and once with invalid input. Then test two distinct strings in succession. If the result changes with call order, inspect captured or cached RegExp objects.

Separate this problem from catastrophic backtracking: a stateful regex can be fast but surprising, while a fresh regex can still be expensive on adversarial input. Review both correctness and execution limits when accepting user-defined patterns.

const isUppercase = value => /^[A-Z]+$/.test(value);
console.assert(isUppercase("ABC"));
console.assert(isUppercase("ABC"));
console.assert(!isUppercase("AbC"));

References

Use the linked specifications and product documentation to check behavior in your own environment.

Continue troubleshooting

Related tools

Frequently asked questions

Does test always start at zero?

No. Global and sticky expressions use their current lastIndex value.

Do anchors fix global regex state?

No. Anchors constrain matching positions but do not remove the persistent cursor.

Does a fresh regex prevent slow matching?

No. Pattern complexity and input still determine the work performed by the engine.

Need help with a tool or found a bug?

Report an issue directly to engineering. No accounts required — all client data remains private on your device.

Report an Issue →