Strings & Regex
String methods, template literals, immutability, comparison, and regular expressions with their flags and methods.
12 questions
JuniorTheoryVery commonAre JavaScript strings mutable, and what happens when a string method changes a string?
Are JavaScript strings mutable, and what happens when a string method changes a string?
Strings are immutable primitives. No method or index assignment can change an existing string in place — methods like toUpperCase, slice, and replace return a brand-new string and leave the original untouched. Writing str[0] = 'x' silently does nothing (it throws only in strict mode). To 'change' a string you reassign the variable to the new returned value.
Common mistakes
- ✗Believing string methods mutate the original instead of returning a new string
- ✗Expecting
str[0] = 'x'to change the first character - ✗Calling a method like
toUpperCaseand forgetting to use its return value
Follow-up questions
- →Why does
str[0] = 'x'throw in strict mode but fail silently otherwise? - →How does immutability let engines safely share or intern string data?
JuniorTheoryVery commonWhat are template literals, and how do interpolation and multiline strings work in them?
What are template literals, and how do interpolation and multiline strings work in them?
Template literals are strings written in backticks ` ` instead of quotes. They support interpolation: any expression inside ${...} is evaluated and its result inserted into the string. They are also natively multiline — a literal newline in the source becomes a \n` in the value, with no concatenation or escape needed.
Common mistakes
- ✗Thinking
${...}only accepts variable names rather than any expression - ✗Believing you still need
\nor+to build multiline strings in backticks - ✗Assuming
${...}inserts the braces' text literally instead of evaluating it
Follow-up questions
- →Can you nest a template literal inside another
${...}expression? - →How do you put a literal backtick or
${inside a template literal?
JuniorTheoryCommonWhat is a regular expression, and how does a literal /x/ differ from new RegExp?
What is a regular expression, and how does a literal /x/ differ from new RegExp?
A regular expression is a pattern describing a set of strings, used to search, test, or replace text. A literal like /abc/i is parsed once when the script loads, so it suits fixed patterns. new RegExp('abc', 'i') builds the same pattern from a string at runtime, which is needed when the pattern is dynamic — but backslashes must then be doubled.
Common mistakes
- ✗Thinking the literal and constructor forms always behave identically, ignoring backslash doubling
- ✗Forgetting
new RegExpneeds doubled backslashes because it parses a string first - ✗Assuming a literal must be built at runtime when a fixed pattern can be a literal
Follow-up questions
- →Why must
\dbecome'\\d'when written as anew RegExpstring? - →When would you choose
new RegExpover a literal in real code?
JuniorTheoryOccasionalWhat do slice, substring, indexOf, toUpperCase, and trim each do on a string?
What do slice, substring, indexOf, toUpperCase, and trim each do on a string?
slice(start, end) and substring(start, end) both return a substring between two indices. indexOf(sub) returns the first position of a substring or -1 if absent. toUpperCase() returns the string uppercased. trim() returns it with leading and trailing whitespace removed. All return new strings; none mutate the original.
Common mistakes
- ✗Thinking these methods mutate the original string instead of returning a new one
- ✗Expecting
indexOfto return0orfalsefor a missing substring instead of-1 - ✗Assuming
trimalso removes whitespace inside the string, not just at the ends
Follow-up questions
- →How do
sliceandsubstringdiffer when you pass a negative index? - →What does
indexOfreturn for an empty-string argument, and why?
JuniorTheoryOccasionalHow do includes, startsWith, endsWith, and indexOf each test for a substring?
How do includes, startsWith, endsWith, and indexOf each test for a substring?
includes(sub) returns true if the substring appears anywhere. startsWith(sub) and endsWith(sub) return true only when it appears at the start or end. indexOf(sub) returns the first index of the substring, or -1 if absent, so indexOf(sub) !== -1 is the older boolean test. All are case-sensitive and accept an optional position argument.
Common mistakes
- ✗Expecting these methods to ignore case rather than being case-sensitive
- ✗Thinking
includesreturns an index instead of a boolean - ✗Forgetting
indexOfreturns-1, so a bare truthy check on it misjudges index0
Follow-up questions
- →What does the optional second argument to
startsWithandincludescontrol? - →Why is
indexOf(sub) !== -1safer thanif (str.indexOf(sub))?
MiddleTheoryOccasionalWhat do the regex flags g, i, m, s, u, and y each control?
What do the regex flags g, i, m, s, u, and y each control?
g (global) finds all matches instead of stopping at the first. i makes matching case-insensitive. m (multiline) makes ^ and $ match at line breaks, not just string ends. s (dotAll) lets . match newlines. u enables full Unicode and code-point mode. y (sticky) anchors each match exactly at lastIndex. Flags follow the closing slash, e.g. /abc/gi.
Common mistakes
- ✗Confusing
s(dotAll,.matches newline) withm(^/$per line) - ✗Thinking
.matches newlines by default without thesflag - ✗Assuming
gandybehave the same instead ofyrequiring alastIndex-anchored match
Follow-up questions
- →How does the
ysticky flag differ fromgwhen scanning a string? - →What does the
uflag change about how.and\wtreat astral characters?
MiddleTheoryOccasionalHow do the regex methods test/exec differ from the string methods match/matchAll/replace/search?
How do the regex methods test/exec differ from the string methods match/matchAll/replace/search?
test and exec are called on the regex: test(str) returns a boolean, exec(str) returns one match array (or null) and advances lastIndex with g. match, matchAll, replace, and search are called on the string: match returns matches or groups, matchAll returns an iterator of all matches, replace substitutes, and search returns the first index or -1.
Common mistakes
- ✗Calling
test/execon the string instead of on the regex object - ✗Expecting
matchwith agflag to include capture groups likeexecdoes - ✗Thinking
searchreturns a boolean rather than an index or-1
Follow-up questions
- →Why does repeatedly calling
execwith ag-flag regex eventually returnnull? - →When should you prefer
matchAllover a loop callingexec?
MiddleTheoryOccasionalHow do === and < compare strings, and how do localeCompare and case-insensitive compares differ?
How do === and < compare strings, and how do localeCompare and case-insensitive compares differ?
=== and </> compare strings lexicographically by UTF-16 code unit, so order follows code-unit numbers — uppercase sorts before lowercase and accented letters land oddly. localeCompare instead compares per locale rules, returning a negative, zero, or positive number. For case-insensitive comparison, lowercase both sides first, or pass { sensitivity: 'base' } to localeCompare.
Common mistakes
- ✗Assuming
</>sort alphabetically rather than by raw code-unit value - ✗Thinking
localeComparereturns a boolean instead of a negative/zero/positive number - ✗Expecting
===to compare references like objects rather than character content
Follow-up questions
- →Why does
'Z' < 'a'returntrueunder the<operator? - →How does
localeCompare'ssensitivityoption enable case- or accent-insensitive ordering?
MiddleTheoryOccasionalHow do substring, slice, and the deprecated substr differ, especially with negative indices?
How do substring, slice, and the deprecated substr differ, especially with negative indices?
slice(start, end) and substring(start, end) both take a start and end index, but slice treats negative indices as offsets from the end while substring clamps any negative argument to 0. substring also swaps its arguments if start > end; slice does not and returns ''. The deprecated substr(start, length) takes a length, not an end index — prefer slice.
Common mistakes
- ✗Expecting
substringto count negative indices from the end likeslicedoes - ✗Thinking
substr's second argument is an end index rather than a length - ✗Forgetting
substringswaps arguments whenstart > end, unlikeslice
Follow-up questions
- →What does
'hello'.substring(3, 1)return, and why does it differ fromslice? - →Why is
substrdeprecated even though browsers still support it?
MiddleTheoryRareWhat is a tagged template, and what does its tag function receive — including raw strings?
What is a tagged template, and what does its tag function receive — including raw strings?
A tagged template is a function call written as ` tag... . The tag function receives the array of literal string chunks as its first argument and the interpolated ${...} values as the remaining arguments, letting it process or escape input before assembling output. The strings array also has a .raw property holding each chunk with escape sequences left uninterpreted, as String.raw` uses.
Common mistakes
- ✗Thinking the tag receives one assembled string rather than chunks plus values separately
- ✗Forgetting the strings array carries a
.rawproperty with uninterpreted escapes - ✗Mixing up which argument holds literals versus interpolated values
Follow-up questions
- →How would you reassemble the original output by interleaving the strings and values?
- →What does
String.rawproduce for `String.rawa\nb` and why?
SeniorTheoryRareHow do capture groups, greedy vs lazy quantifiers, and the g-flag lastIndex state interact?
How do capture groups, greedy vs lazy quantifiers, and the g-flag lastIndex state interact?
Parentheses create capture groups returned after the full match. Quantifiers like + and * are greedy — they match as much as possible and backtrack; adding ? (+?, *?) makes them lazy, matching as little as possible. A g-flag regex is stateful: exec/test resume from its mutable lastIndex, so reusing one shared instance across calls skips matches. matchAll sidesteps that by iterating cleanly.
Common mistakes
- ✗Reusing one
g-flag regex across calls and missing matches becauselastIndexpersists - ✗Assuming quantifiers are lazy by default rather than greedy
- ✗Thinking
matchwithgreturns capture groups the wayexecandmatchAlldo
Follow-up questions
- →Why can a stray
gflag make anif (re.test(x))inside a loop intermittently fail? - →How does converting
.*to.*?change which match a pattern produces?
SeniorTheoryRareHow do UTF-16 code units, code points, and surrogate pairs affect .length and iterating a string?
How do UTF-16 code units, code points, and surrogate pairs affect .length and iterating a string?
JS strings are sequences of UTF-16 code units, and .length counts code units, not characters. An astral character like an emoji is a surrogate pair of two code units, so '😀'.length is 2 and indexing splits it. Spread, for...of, and Array.from iterate by code point and keep it whole. normalize() reconciles equivalent forms (e.g. é composed vs decomposed) before comparing.
Common mistakes
- ✗Assuming
.lengthcounts visible characters rather than UTF-16 code units - ✗Indexing or slicing a string and splitting a surrogate pair into broken halves
- ✗Comparing visually identical strings without
normalize, so composed vs decomposed forms differ
Follow-up questions
- →Why does
[...'😀'].lengthreturn1while'😀'.lengthreturns2? - →When does
normalize('NFC')change the result of a===string comparison?