In JavaScript, what does String.prototype.replace do when you pass a plain string as the pattern, and how do replaceAll and a global regex differ from it?
answer
- one occurrence unless told otherwise
- the g flag is what repeats it
- replaceAll takes a literal string
- non-global regex into replaceAll throws
- dollar signs in the replacement expand
basics
~20 sreplace() with a string pattern, or with a regex that lacks the g flag, substitutes only the first occurrence. Replacing every occurrence needs replaceAll() or a /g regex; replaceAll throws a TypeError if handed a non-global regex.
solid answer
~40 s`String.prototype.replace(pattern, replacement)` returns a new string — strings are immutable — and it replaces exactly one occurrence when the pattern is a plain string or a regex without the `g` flag. That is the classic surprise: `'a-b-c'.replace('-', '+')` gives `'a+b-c'`. To hit every occurrence you either add the global flag (`'a-b-c'.replace(/-/g, '+')`) or use `replaceAll`, added in ES2021, which takes a string pattern and replaces all matches with no regex and no escaping of metacharacters. `replaceAll` deliberately throws a `TypeError` if you pass it a regex without `g`, because "replace all, but only the first one" is a contradiction. One more thing to know: the replacement string is not literal — `$&`, `$1`, `$$` and friends are expanded, so a `$` coming from user data can splice unexpected text into the result.
code
javascript · 13 linesconst s = 'a.b.c';
console.log(s.replace('.', '-')); // 'a-b.c' first only
console.log(s.replaceAll('.', '-')); // 'a-b-c' literal dot
console.log(s.replace(/\./g, '-')); // 'a-b-c' escaped dot, global
console.log(s.replace(/./g, '-')); // '-----' unescaped dot = any char
console.log(s); // 'a.b.c' never mutated
try {
s.replaceAll(/\./, '-');
} catch (e) {
console.log(e.constructor.name); // 'TypeError'
}go deeper
Know that replace() with a string pattern changes only the first occurrence and returns a new string. Say plainly that replaceAll() or a /g regex is what replaces all of them.
Explain why replaceAll rejects a non-global regex, and why replacing a variable substring with a hand-built regex needs metacharacter escaping while replaceAll with a string does not.
Show the $-expansion trap: replacement strings that come from data can splice matched text into the output. Demonstrate the two fixes — doubling the dollar signs or using a replacer function.
Own the codebase-wide rule: literal replacements go through replaceAll with a string, patterns go through reviewed regexes, and no untrusted value is ever concatenated into a pattern or a replacement string.
## What replace actually is `String.prototype.replace(pattern, replacement)` takes a pattern (a string or a `RegExp`) and a replacement (a string or a function) and **returns a new string**. It never modifies the receiver, because JavaScript strings are immutable values — if you ignore the return value, nothing appears to happen: ```js const s = 'a-b-c'; s.replace('-', '+'); console.log(s); // 'a-b-c' — unchanged console.log(s.replace('-', '+')); // 'a+b-c' ``` ## A string pattern replaces exactly one occurrence When `pattern` is a plain string, `replace` finds the **first** occurrence and substitutes it. There is no "all" option and no third argument — `s.replace('-', '+', 'g')` silently ignores the extra argument and still replaces one. This is the single most common beginner bug with the method, and it is easy to miss because the code looks right and works on inputs that happen to contain one match. ## The global flag makes it repeat If the pattern is a `RegExp` carrying the `g` flag, `replace` walks the whole string and substitutes every non-overlapping match, left to right: ```js 'a-b-c'.replace(/-/g, '+'); // 'a+b+c' 'a-b-c'.replace(/-/, '+'); // 'a+b-c' ``` Under the hood, `replace` delegates to the pattern's `Symbol.replace` method when the pattern has one, which is how `RegExp` customises the operation. That implementation sets the regex's `lastIndex` to 0 before it starts when the regex is global, so a shared global regex does not make `replace` skip matches the way it can make `test` and `exec` behave oddly. ## replaceAll (ES2021) `replaceAll` gives you "every occurrence" without a regex: ```js '1.2.3'.replaceAll('.', '-'); // '1-2-3' '1.2.3'.replace(/./g, '-'); // '-----' — `.` is "any character" ``` That second line is the reason `replaceAll` was added. Before it existed, replacing every occurrence of a *variable* substring meant constructing a regex from that string and escaping every character that means something special in a pattern; forget the escaping and the substitution quietly does the wrong thing, as `.` does above. `replaceAll` with a string pattern compares literally, so no escaping question arises. `replaceAll` also accepts a `RegExp`, but only a global one. Passing a non-global regex throws: ```js 'aaa'.replaceAll(/a/, 'b'); // TypeError: replaceAll must be called with a global RegExp ``` The guard exists because the two halves of the call would contradict each other, and a silent choice between them would be a bug factory. ## The replacement string is not taken literally Both methods expand `$` sequences in a replacement **string**: `$$` inserts a literal `$`, `$&` the matched substring, `` $` `` the text before the match, `$'` the text after it, `$1`…`$n` the numbered captures, and `$<name>` a named capture. So a replacement value that came from user input or from a password generator can inject text you never intended: ```js 'price'.replace('price', "$&$&"); // 'priceprice' ``` Two safe routes: double every `$` in the untrusted value (`value.replaceAll('$', '$$$$')`), or pass a **function** as the replacement — its return value is used verbatim, with no `$` expansion at all. ## Choosing between them - Replacing one known occurrence, or you genuinely want only the first: `replace` with a string. - Replacing every occurrence of a literal substring, especially one held in a variable: `replaceAll` with a string. - The pattern is a real pattern (classes, quantifiers, alternation): a regex — add `g` when you want all of them. - The replacement depends on what matched: a replacer function, with either method. ## Availability `replace` has been in the language since ES3. `replaceAll` is ES2021 — universally available in current browsers and Node, but it is the one to check if you target a very old runtime; the fallback is `split(sep).join(rep)` for a literal separator or an escaped global regex.
- Why does replaceAll throw on a non-global regex instead of just replacing everything anyway?Because the call would be self-contradictory: the method name says every match, the missing `g` flag says one. Rather than silently picking a winner — which would hide a real mistake in the caller's pattern — the spec makes it a `TypeError` you see immediately. A global regex expresses the same intent unambiguously, so nothing is lost.
- Before replaceAll existed, what was the idiomatic way to replace every occurrence of a substring held in a variable?Either `str.split(sep).join(rep)`, which compares literally and needs no escaping, or building a global regex from the variable with every metacharacter escaped first. The second is what people usually reached for and usually got wrong: an unescaped `.`, `+`, `(` or `?` from the variable silently changes the pattern's meaning. `replaceAll` removed the whole problem.
- How do you insert a literal dollar sign through the replacement string?Write `$$`. In a JavaScript source literal that is `'$$'`, so `'x'.replace('x', '$$')` yields `'$'`. If the replacement text is data rather than something you wrote, double every `$` in it first, or pass a replacer function — a function's return value is used verbatim with no `$` expansion.
saying these in an interview costs you the question
- Thinks replace always replaces every occurrence
- Passes 'g' as a third argument to replace
- Uses replace(/./g, x) expecting a literal dot
- Assumes replace mutates the original string
- Thinks replaceAll silently works with a non-global regex