Strings and regular expressions: lengths, flags and the stateful g
Strings are immutable UTF-16 sequences, so length is not a character count. Regex flags, groups, greedy versus lazy, and the lastIndex trap.
After this lesson you can
- Explain why "length" can disagree with what a reader counts as characters
- Choose between replace, replaceAll, match and matchAll
- Use capture groups, named groups and lookbehind in a pattern
- Avoid the stateful test() trap and escape user input before building a RegExp
A string is an immutable sequence of UTF-16 code units. Every
method — toUpperCase, slice, replace, trim — returns a new
string. Assigning to s[0] never changes the string: in non-strict code
it is silently ignored, in strict code it throws a TypeError. A
reassigned variable gets a new string; the old one is untouched.
length counts code units, not characters
Characters outside the Basic Multilingual Plane, such as most emoji, take
two code units, so "👍".length is 2. Spreading a string or
using for...of iterates by code point instead. Accented letters can
also be written two ways: é as one code point, or e followed by a
combining accent. They look identical, but they are different strings
until you call normalize() on both.
export function run() { const composed = "caf\u00e9"; // é as one code point const decomposed = "cafe\u0301"; // e + combining acute accent const thumbs = "\u{1F44D}"; // a single emoji let s = "abc"; s[0] = "X"; // ignored: strings are immutable let strictWrite; try { (() => { "use strict"; s[0] = "X"; })(); } catch (e) { strictWrite = e.name; } return { composedLength: composed.length, decomposedLength: decomposed.length, sameString: composed === decomposed, sameAfterNormalize: composed.normalize() === decomposed.normalize(), emojiLength: thumbs.length, emojiCodePoints: [...thumbs].length, afterIndexAssign: s, strictWrite, replaceFirst: "a-b-c".replace("-", "+"), replaceEvery: "a-b-c".replaceAll("-", "+"), lastChar: "hello".at(-1), padded: "7".padStart(3, "0"), };}The everyday methods: slice(start, end) (negative indexes count from
the end), includes, startsWith, endsWith, padStart,
split/join, trim, at(-1). replace with a string pattern
replaces only the first match; replaceAll, or a regex with the g
flag, replaces every one.
Regular expressions
A regex literal is /pattern/flags; new RegExp(source, flags) builds
one from a string (where every backslash must itself be escaped). The
flags you will meet: g (all matches), i (ignore case), m (^ and
$ match at line breaks), s (. also matches newlines), u
(Unicode code points), (sticky: match only at ).
export function run() { const isCat = /cat/g; const sharedGlobal = [isCat.test("cat"), isCat.test("cat"), isCat.test("cat")]; const html = "<b>bold</b>"; const date = /(?<year>\d{4})-(?<month>\d{2})/.exec("due 2026-10"); const pairs = [..."a1 b22 c333".matchAll(/([a-z])(\d+)/g)].map( (m) => `${m[1]}:${m[2]}`, ); const userInput = "1+1"; const escaped = userInput.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return { sharedGlobal, greedy: html.match(/<.+>/)[0], lazy: html.match(/<.+?>/)[0], namedGroups: { ...date.groups }, matchWithG: "a1 b22".match(/\d+/g), pairs, swapped: "2026-10-03".replace(/(\d+)-(\d+)-(\d+)/, "$3/$2/$1"), doubled: "3 apples, 12 pears".replace(/\d+/g, (n) => String(n * 2)), unescaped: new RegExp(userInput).test("1+1"), escapedSource: escaped, escapedWorks: new RegExp(escaped).test("1+1"), };}Common mistakes
str.lengthas a character count. It counts UTF-16 code units; use[...str].lengthfor code points.- A shared
/.../gregex used withtest. Results alternate betweentrueandfalsefor the same input, as the second example shows. replace("-", "+")expecting every dash replaced. A string pattern replaces the first occurrence only.- Greedy
.+across delimiters./<.+>/on<b>x</b>matches the whole string; use.+?or a negated class[^>]+.
What interviewers ask
- Why does
"👍".lengthreturn 2? — Strings are UTF-16; that emoji is one code point stored as a surrogate pair of two code units. - What does the
gflag change abouttestandexec? — They become stateful through , continuing from the previous match.
Exercise · Pro
Mentions in a message
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade