← Back to Blog
·8 min read

Regex Flags Are More Powerful Than You Think — And More Dangerous

Most developers know i means case-insensitive and g means global. That surface knowledge hides a dangerous API quirk in the g flag, Unicode subtleties in the u flag, and real behavioral changes in mthat catch developers off guard. Here is every flag, what it does, and where it hurts you.

The i flag: case-insensitive (straightforward, one gotcha)

/hello/i.test("HELLO")  // true
/hello/i.test("Hello")  // true

// Gotcha: without 'u', case folding only covers ASCII.
// Turkish dotless 'ı' does not case-fold to 'I' without locale consideration.
// For Unicode-correct case-insensitive matching, combine i + u:
/ı/iu  // handles non-ASCII case folding more correctly

The g flag: global — and stateful. This one bites people.

The g flag makes exec() and test() stateful. The regex object stores its last match position in lastIndex. Each call to exec() or test() advances lastIndex. This is the source of some of the most baffling bugs in JavaScript:

const re = /hello/g;
re.test("hello world");  // true  — lastIndex is now 5
re.test("hello world");  // false — starts at position 5, no match
re.test("hello world");  // true  — lastIndex reset to 0 after failure

// Classic bug in a conditional:
const re = /d+/g;
if (re.test(input)) {
  // re.lastIndex is now non-zero
  const match = re.exec(input);  // starts mid-string, misses early matches!
}

// Safe pattern: use re.exec() in a loop, or use str.match() / str.matchAll():
for (const match of input.matchAll(/d+/g)) {
  console.log(match[0]);  // no lastIndex statefulness issue
}

The gflag's stateful behavior is a JavaScript design decision from the Netscape era that the language cannot remove without breaking the web. It is widely considered a design mistake. Prefer str.matchAll() (ES2020) over manual re.exec()loops to avoid it entirely.

The m flag: multiline — redefines ^ and $

const text = "line one
line two
line three";

// Without m: ^ matches start of string, $ matches end of string
/^line/g.test(text)   // true — matches "line one" at start of string
/two$/g.test(text)    // false — "two" is not at end of string

// With m: ^ matches start of each line, $ matches end of each line
/^line/gm             // matches "line one", "line two", "line three"
/two$/gm              // true — "two" is at end of its line

This distinction matters for parsing multi-line text. Without m, ^ and $ are string boundaries. With m, they are line boundaries. If you are parsing log files or structured text and your anchors behave unexpectedly, m is usually the missing flag.

The s flag: dotAll — dot matches newlines

// Without s: . does not match 

/a.b/.test("a
b")   // false

// With s (ES2018+): . matches any character including 

/a.b/s.test("a
b")  // true

// Before the s flag existed, the workaround was [\s\S]:
/a[\s\S]b/.test("a
b")  // true — matches "any character including newlines"

The s flag (dotAll) was added in ES2018. Before it existed, the idiomatic JavaScript solution was the [\s\S] hack. If you see that pattern in legacy code, . with the s flag is the modern equivalent.

The u flag: Unicode mode — you probably need this

The u flag enables full Unicode mode. Without it, JavaScript regex treats strings as UTF-16 code units, not Unicode code points. This breaks for characters outside the Basic Multilingual Plane (emoji, some Asian scripts):

// Without u: emoji is two UTF-16 units, . matches only one
/^.$/.test("😀")    // false — emoji is 2 code units, . matches 1

// With u: emoji is one Unicode code point
/^.$/u.test("😀")   // true

// Unicode property escapes (u flag required):
/p{Emoji}/u.test("😀")     // true
/p{Script=Latin}/u.test("a") // true
/p{Uppercase_Letter}/u.test("A") // true

If your application handles user input in any language beyond ASCII, the u flag is not optional — it is necessary for correct behavior. The v flag (ES2024) is the successor to u with additional Unicode set operations; prefer it in new code if your environment supports it.

The d flag: indices — match position tracking

The d flag (ES2022) adds a indices property to the match result, containing the start and end positions of each capture group:

const match = "2026-05-17".match(/(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/d);
console.log(match.indices);
// [[0, 10], [0, 4], [5, 7], [8, 10]]
// Overall match:   chars 0-10
// year group:      chars 0-4
// month group:     chars 5-7
// day group:       chars 8-10

// Useful for: syntax highlighting, editor extensions, source maps

Published May 31, 2026 · By the utili.dev Team