Implement a polyfill for JSON.stringify
Recursively serialize by type: primitives (string→quoted, number/boolean→String, null→'null'), arrays → [...], objects → {...} with quoted keys. Skip undefined/functions/symbols in objects (→ omitted) but render them as null in arrays. Handle the gotchas: NaN/Infinity→null, toJSON(), circular refs throw.
A JSON.stringify polyfill is a recursive type-dispatch problem. The interview is about knowing JSON's many special cases.
The core structure
function jsonStringify(value) {
// toJSON hook — Date and custom objects use it
if (value && typeof value.toJSON === "function") {
value = value.toJSON();
}
const type = typeof value;
// primitives
if (value === null) return "null";
if (type === "number") return Number.isFinite(value) ? String(value) : "null"; // NaN/Infinity → null
if (type === "boolean") return String(value);
if (type === "string") return quote(value); // escape + wrap in ""
if (type === "undefined" || type === "function" || type === "symbol") {
return undefined; // signal "omit me" — handled by the caller
}
// arrays
if (Array.isArray(value)) {
const items = value.map((item) => {
const s = jsonStringify(item);
return s === undefined ? "null" : s; // undefined/fn/symbol in arrays → null
});
return "[" + items.join(",") + "]";
}
// plain objects
if (type === "object") {
const pairs = [];
for (const key of Object.keys(value)) {
const s = jsonStringify(value[key]);
if (s !== undefined) { // undefined/fn/symbol values → key OMITTED
pairs.push(quote(key) + ":" + s);
}
}
return "{" + pairs.join(",") + "}";
}
}
function quote(str) {
// minimal: escape \, ", control chars; wrap in double quotes
return '"' + str.replace(/[\\"]/g, "\\$&").replace(/\n/g, "\\n") + '"';
}The gotchas being tested
undefined, functions, symbols — omitted as object values (the key disappears entirely), but becomenullas array elements. Different behavior by context.NaN/Infinity/-Infinity→ serialized as"null".null→"null"(string).toJSON()— if a value has atoJSONmethod, call it and serialize the result. This is howDatebecomes an ISO string.- Strings need escaping — quotes, backslashes, newlines, control characters, then wrapped in double quotes.
- Circular references → the real
JSON.stringifythrows aTypeError. A complete polyfill tracks a visitedSet/WeakSetand throws on a cycle. - Object keys are always double-quoted strings.
BigInt→ throws aTypeError.- (Full spec also: the
replacerandspacearguments — mention them; usually out of scope for the core exercise.)
The framing
"It's recursive type dispatch. Primitives: strings get escaped and quoted, finite numbers and booleans stringify, null is 'null', and NaN/Infinity become 'null'. Arrays recurse and join in brackets. Objects recurse over Object.keys with quoted keys. The gotchas are the point: undefined/functions/symbols are omitted as object values but become null in arrays; toJSON is called if present — that's how Dates serialize; strings need proper escaping; and circular references throw a TypeError, so a complete version tracks a visited set."
Follow-up questions
- •Why does undefined behave differently in an object vs an array?
- •How does Date end up as a string — what hook is used?
- •What does JSON.stringify do with a circular reference?
- •What happens to NaN, Infinity, and BigInt?
Common mistakes
- •Treating undefined/function values the same in objects and arrays.
- •Forgetting NaN/Infinity serialize to null.
- •Not escaping special characters in strings.
- •Not handling toJSON (so Dates break).
- •Not detecting circular references — infinite recursion / stack overflow.
Performance considerations
- •O(n) over the value graph. String concatenation/joining and the visited-set lookups for cycle detection are the costs; for huge objects, building an array of parts and joining once is better than repeated concatenation.
Edge cases
- •Circular references → must throw TypeError.
- •NaN, Infinity, -Infinity → null.
- •undefined/function/symbol — omitted in objects, null in arrays.
- •Date (via toJSON), and objects with custom toJSON.
- •BigInt → throws.
- •Strings with quotes, backslashes, newlines, control characters.
Real-world examples
- •Understanding why a Date round-trips to an ISO string but not back to a Date.
- •Debugging why a function or undefined silently disappeared from serialized output.