Regex flags explained: i, g, m, s, u, y and d
What each JavaScript regex flag changes, with live examples: case-insensitive i, global g (and the lastIndex trap), multiline m, dotAll s, unicode u, sticky y and indices d, plus how Python spells the same ideas.

· 4 min read
/^colou?r$/i
match
- color
- Colour
- COLOR
- colour
spare
- colr
- colours
- discolor
- kolor
A flag changes how the whole pattern behaves. In JavaScript, flags go after the closing slash: /cat/gi. In Python they're arguments, like re.IGNORECASE, or inline at the start of the pattern, like (?i).
There are seven in JavaScript. Two you'll use constantly, a few you'll need now and then, and one has a trap that has caught nearly everyone. Every example is live, so edit the patterns and watch the lines change.
01i: ignore case
Letters match in either case:
It saves writing [Cc][Oo][Ll]…, and it also applies inside classes: with i, [a-z] accepts capitals too.
Python: re.IGNORECASE, re.I, or (?i).
02g: global
Without g, a regex finds the first match and stops. With it, methods like match, matchAll and replace work on every match:
"a1b22c333".match(/\d+/); // ["1"], plus index and input
"a1b22c333".match(/\d+/g); // ["1", "22", "333"]
"a-b-c".replace(/-/, "+"); // "a+b-c"
"a-b-c".replace(/-/g, "+"); // "a+b+c"
Python has no g. Instead you pick the function: re.search finds one match, re.findall and re.finditer find all of them, and re.sub replaces all by default.
The lastIndex trap
A regex with g (or y) remembers where its last match ended, in lastIndex, and the next test() starts from there. Reuse one such regex on several strings and results start to alternate:
const hasDigit = /\d/g;
hasDigit.test("a1"); // true, lastIndex is now 2
hasDigit.test("b2"); // false: the search starts at index 2, past the "2"
hasDigit.test("b2"); // true again, lastIndex had reset to 0
The fix is simple: don't put g on a regex you only use with test(). You don't need it to check whether something matches.
03m: multiline
^ and $ match at the start and end of each line instead of the whole text. The post on anchors and multiline mode covers it in full.
"one\ntwo".match(/^\w+$/gm); // ["one", "two"]
Python: re.MULTILINE, re.M, or (?m).
04s: dotAll
Normally . matches any character except a line break. With s, it matches line breaks too:
/start.*end/.test("start\nend"); // false
/start.*end/s.test("start\nend"); // true
Before s existed, people wrote [\s\S] for "any character at all", and you'll still see it in older code.
Python: re.DOTALL, re.S, or (?s).
05u: unicode
u makes the pattern work in whole Unicode characters instead of UTF-16 pieces, and it turns on property escapes like \p{L} (any letter, in any script):
It also makes . match one emoji instead of half of one, and it makes the parser strict: an unnecessary escape like \- outside a class becomes a syntax error instead of being quietly ignored. JavaScript's newer v flag is a stricter superset of u with set operations inside classes.
Python's re is Unicode-aware by default but has no \p{…}. The third-party regex module adds it.
06y: sticky
With y, a match must start exactly at lastIndex, not anywhere after it. It's mostly for tokenizers that walk through a string piece by piece:
const word = /\w+/y;
word.lastIndex = 0;
word.exec("hi there"); // ["hi"], lastIndex is now 2
word.exec("hi there"); // null: index 2 is a space, and y won't skip ahead
07d: indices
d adds an indices array to each match with the start and end of the match and of every group:
const m = /(\d+)-(\d+)/d.exec("pages 12-30");
m.indices[0]; // [6, 11], the whole match
m.indices[1]; // [6, 8], the first group, "12"
Handy for highlighting matches in an editor. Python has this built in: m.span(1).
08Try one
Use the i flag to keep this one short:
Catch yes in any case, and nothing else.
Targets · 0/5 caught
- yestarget
- Yestarget
- YEStarget
- yEstarget
- yeStarget
Innocents · 0 hit
- yesterdayinnocent
- eyesinnocent
- noinnocent
- yeinnocent
- y e sinnocent
Type a regex — the board updates as you go.
09Checklist
iignores case.mmakes^and$per line.slets.match line breaks.gfinds every match, but don't use it withtest()on a reused regex.uhandles full Unicode and enables\p{L}.ymatches only atlastIndex.dadds match positions.- Python:
re.I,re.M,re.S, or inline(?i),(?m),(?s). Nog: usefindallorsub.
10Keep practising
- The guide's flags section has a quick reference, and unicode goes deeper on
u. - Line wrangling is a practice pack where the flags matter.
- Try today's hunt, or browse past boards in the archive.