Your Questions About Regex Flags Answered: g, i, m, s, u, and y

Every few weeks I see the same question in developer forums: "Why is my regex only matching the first result?" or "What does the s flag actually do?" Regex flags are one of those things that feel mysterious until someone explains them plainly. So let me just answer the questions directly — no fluff, just the flag, a before-and-after, and what it means for your code.

The g Flag — Global Matching

Q: Why does my regex only find the first match even though there are three in the string?

Because without g, the regex engine stops after the first match. That's the default behavior — find one, done.

const str = "cat bat sat";

str.match(/at/);    // ["at"] — stops at "cat"
str.match(/at/g);   // ["at", "at", "at"] — finds all three

Q: I'm using exec() in a loop and it's running forever. Why?

Classic footgun. When you use exec() with the g flag, the regex object remembers its position via lastIndex. If you're creating a new regex literal inside the loop, lastIndex resets every iteration and you get an infinite loop. Move the regex outside the loop.

const re = /\d+/g;
const str = "room 101, floor 3, seat 42";
let match;
while ((match = re.exec(str)) !== null) {
  console.log(match[0]); // "101", "3", "42"
}

Q: Does g affect test()?

Yes, and this surprises people. test() with g advances lastIndex on each call. So calling /\d/g.test("a1b2") twice on the same regex object gives true then false. If you just need a yes/no answer, skip the g flag entirely when using test().


The i Flag — Case-Insensitive

Q: Do I need to write [aA] or [a-zA-Z] to match both cases?

Not if you add i. The flag tells the engine to treat uppercase and lowercase letters as equivalent throughout the entire pattern.

"Hello World".match(/hello/);    // null
"Hello World".match(/hello/i);   // ["Hello"]

Q: Does case-insensitivity work with Unicode characters, like accented letters?

Partially, and this is where it gets interesting. The i flag alone handles basic Unicode case folding, but for full Unicode case matching — especially for less common scripts — you want to pair it with the u flag. More on that below.

/ß/i.test("SS");    // false without u
/ß/iu.test("SS");   // true — ß folds to SS in Unicode

The m Flag — Multiline

Q: I'm trying to match text at the start of each line but ^ only matches the very beginning of the whole string. What's wrong?

That's exactly what ^ does without m — it anchors to the start of the entire input. With m, the anchors ^ and $ change meaning: they now match at the start and end of each line, not the whole string.

const log = `ERROR: disk full
INFO: retrying
ERROR: timeout`;

log.match(/^ERROR.*/g);
// Without m: null (^ only matches start of whole string)

log.match(/^ERROR.*/gm);
// With m: ["ERROR: disk full", "ERROR: timeout"]

Q: Does m make . match newlines?

No. That's a very common mix-up. The m flag only changes what ^ and $ mean. Making . match newlines is the job of the s flag — read on.


The s Flag — Dotall (Single-line mode)

Q: My pattern /.+/ isn't matching across multiple lines. I thought . matches everything?

The dot matches almost everything — except a newline character (\n), by default. The s flag (sometimes called "dotall" mode) removes that exception.

const html = `
hello world
`; html.match(/
.+<\/div>/); // null — . doesn't cross the \n html.match(/
.+<\/div>/s); // ["
\n hello world\n
"]

Q: Is s supported everywhere?

It's been part of JavaScript since ES2018, so it works in all modern browsers and Node.js 10+. If you're writing regex for an older environment or a server-side language like PHP or Python, the flag exists there too (Python calls it re.DOTALL), but the letter might differ. In JavaScript /s is the one you want.

Q: Before s existed, how did people match newlines with a dot?

The old workaround was [\s\S] — a character class that matches whitespace OR non-whitespace, which together cover everything including newlines. You'll still see this pattern in older codebases.

html.match(/
[\s\S]+<\/div>/); // Works, but verbose

The u Flag — Unicode Mode

Q: I'm matching emoji and my regex is acting strange — splitting them in half. What's going on?

JavaScript strings are internally UTF-16, and some characters (emoji, many Chinese characters, various symbols) are encoded as two 16-bit units called surrogate pairs. Without u, the regex engine sees these as two separate characters and can match half of a character — producing garbage results or wrong counts.

"😀".length;         // 2 — JS counts UTF-16 code units

/^.$/.test("😀");    // false — engine sees 2 chars
/^.$/u.test("😀");   // true — engine sees 1 Unicode code point

Q: My regex throws a syntax error with some escape sequences. Adding u fixes it — why?

Unicode mode is stricter about invalid escapes. Without u, JavaScript silently ignores escape sequences it doesn't recognize (like \p or \a outside a recognized context). With u, they throw a syntax error, which is actually helpful — it means you catch typos and broken patterns at parse time rather than getting silent wrong answers.

Q: What's \p{} and do I need u for it?

Yes, absolutely. \p{} is a Unicode property escape — a way to match characters by category rather than spelling them all out. Want to match any letter from any script? \p{L}. Any digit? \p{N}. Any emoji? \p{Emoji}. These only work with the u flag.

/\p{L}+/u.test("café");      // true — matches letters incl. é
/\p{Emoji}/u.test("Hello 🎉"); // true

The y Flag — Sticky

Q: What does "sticky" actually mean? How is it different from g?

Both g and y use lastIndex to track position. The difference is what happens when the match isn't found right at lastIndex. With g, the engine keeps scanning forward until it finds a match. With y, the match must start exactly at lastIndex — if it doesn't, the match fails immediately.

const str = "aaa bbb aaa";
const re_g = /aaa/g;
const re_y = /aaa/y;

re_g.exec(str);  // ["aaa"] at index 0 — ok
re_g.exec(str);  // ["aaa"] at index 8 — engine skipped the space+bbb

re_y.exec(str);  // ["aaa"] at index 0 — ok
re_y.exec(str);  // null — lastIndex is now 3, next char is " ", not "a"

Q: When would I actually use y?

The sticky flag shines when you're building a tokenizer or parser — situations where you're consuming a string left to right and every token must start exactly where the last one ended. There's no room for skipping unknown characters; anything unexpected should be an error. Parsers for DSLs, template engines, and configuration formats often use y exactly because of this strict positional guarantee.

function tokenize(src) {
  const token = /\d+|\w+|\s+|./y;
  const tokens = [];
  while (token.lastIndex < src.length) {
    const m = token.exec(src);
    if (!m) throw new Error(`Unexpected char at ${token.lastIndex}`);
    tokens.push(m[0].trim());
  }
  return tokens.filter(Boolean);
}

Q: Can I use y and g together?

Yes, and in JavaScript it's valid. In practice it's rare — y is usually enough on its own for sequential tokenizing. But combining them doesn't produce an error; y just takes precedence for the "must match at lastIndex" behavior while g keeps the global semantics.


Quick Reference Card

Flag Name What it changes
g Global Find all matches, not just the first
i Case-insensitive Treat A and a as the same
m Multiline ^ and $ match per-line, not per-string
s Dotall Make . match newlines too
u Unicode Full Unicode code point handling, enables \p{}
y Sticky Match must start exactly at lastIndex

Flags combine freely — /pattern/gimu is perfectly valid. Just be deliberate about which ones you actually need, because g on a test check or missing u on emoji matching are the kind of bugs that hide for weeks.