String Tokenizer
intermediateSplit source text into tokens — a warm-up for parsing questions like JSON.parse.
Implement String.prototype.trim in JavaScript with the full Unicode whitespace set, two-pointer scanning, and the catastrophic-regex pitfall interviewers listen for.
Implement
trim(str)— strip leading and trailing whitespace — without using the built-intrim.
It looks like a warm-up, and it is — but it has two teeth hidden in it:
' ', '\t', '\n', '\r' — it includes \v, \f, the no-break space \u00A0, the BOM \uFEFF, and every Unicode Space_Separator character (em-space, thin space, ideographic space…). An implementation that hardcodes four characters fails on '\u00A0hello\u00A0' — which is exactly the string an interviewer will paste, because shows up constantly in scraped HTML.// The spec's WhiteSpace + LineTerminator sets, written as \uXXXX escapes
// so every member is visible. \s matches exactly this same set — see below.
const WHITESPACE = new Set([
" ", "\t", "\n", "\r", "\v", "\f",
"\u00A0", // no-break space — catches real-world HTML
"\u1680", // ogham space mark
"\u2000", "\u2001", "\u2002", // en quad, em quad, en space
"\u2003", "\u2004", "\u2005", // em space, three-per-em, four-per-em
"\u2006", "\u2007", "\u2008", // six-per-em, figure space, punctuation space
"\u2009", "\u200A", // thin space, hair space
"\u2028", "\u2029", // line & paragraph separators
"\u202F", "\u205F", // narrow no-break space, math space
"\u3000", // ideographic (CJK) space
"\uFEFF", // byte-order mark
]);
function trim(str) {
let start = 0;
let end = str.length - 1;
while (start <= end && WHITESPACE.has(str[start])) start++;
while (end >= start && WHITESPACE.has(str[end])) end--;
return str.slice(start, end + 1);
}Two pointers, one pass over the trimmed regions, O(n) time, no backtracking possible. In an interview you don't recite the table — you write the six ASCII entries, add \u00A0 and \uFEFF, and say "the full set is Unicode Space_Separator plus the BOM; in real code I'd lean on \s, which matches exactly this set."
trim(" hello "); // "hello"
trim("\u00A0\thi\n\u00A0"); // "hi" — the nbsp case the 4-char version fails
trim("no-op"); // "no-op"
trim(" \n\t "); // "" — all whitespace
trim(""); // ""The one-liner everyone reaches for:
const trim = (str) => str.replace(/^\s+|\s+$/g, "");Correct output — but \s+$ is a known catastrophic-adjacent pattern: on a long string with a huge run of interior whitespace before a non-whitespace tail ("a" + " ".repeat(50000) + "b"), the $-anchored alternative forces the engine to attempt a whitespace match at every one of those 50,000 positions and fail at each. It degrades to O(n²) — this exact shape caused Cloudflare's 2019 global outage and is a recurring source of ReDoS advisories in npm trim-adjacent packages. The fixes, in order of preference:
str.replace(/^\s+/, "").replace(/\s+$/, ""); // still O(n²) worst case on the tail
str.replace(/^\s*(.*?)\s*$/s, "$1"); // lazy middle — better, still regex-subtle
// or: don't use a regex for the tail — scan backwards (the two-pointer version)The interview point isn't memorizing which variant is safe — it's knowing that anchored \s+$ on untrusted input is a DoS-shaped pattern and being able to offer the linear-scan alternative. That single sentence is worth more than the implementation.
\u00A0 (nbsp) — pasted from any web page; the four-character version silently fails."", not a crash or the original string.\u200B zero-width space is NOT whitespace — \s doesn't match it, native trim doesn't strip it. If your mental model is "invisible characters," you'll over-trim. A fantastic gotcha to name unprompted.trimStart / trimEnd — your two loops are literally these two functions; factoring them out is a natural refactor the interviewer may request.slice(start, end + 1) — the off-by-one on end is the most common silent bug in the two-pointer version.' ', '\t', '\n', '\r' and calling it done.str.split("").filter(...) approaches that remove interior whitespace too.slice(start, end) instead of end + 1./^\s+|\s+$/g and having nothing to say when asked "any performance concerns with that regex?" — that question is only ever asked when the answer is yes.charCodeAt(i) <= 32 loops — strips ASCII control characters that aren't whitespace, and misses everything above \u007F.trimStart only, then build trim from the pieces."\s match in JavaScript?" — the spec WhiteSpace set plus LineTerminators — the same set native trim uses; that equivalence is why the regex version is correct (just not always fast).\u200B–\u200D; note you're now beyond trim semantics and should name the function accordingly (stripInvisible).trim is the smallest member of that family.Split source text into tokens — a warm-up for parsing questions like JSON.parse.
Implement reduce including the no-initial-value case and empty-array TypeError.
Write a small recursive-descent parser — the deepest polyfill question in the set.
Serialize values by hand: undefined, functions, cycles, and all the special cases JSON defines.