What do JSON.stringify() and JSON.parse() each do in JavaScript, and how does the value returned by JSON.parse() relate to the object that was originally stringified?
answer
- one side is text, one side is value
- stringify returns a string, not an object
- parse allocates everything anew
- nested objects are new references too
- aliasing and prototypes do not survive
basics
~20 sJSON.stringify() converts a JavaScript value into a string of JSON text; JSON.parse() turns such a string back into a JavaScript value. The result is a brand-new object graph that shares no references with the original.
solid answer
~50 s`JSON.stringify(value)` walks a value and returns a **string** of JSON text — by default with no whitespace at all. `JSON.parse(text)` does the reverse: it reads JSON text and builds a JavaScript value from it. The important part for interviews is that parsing allocates everything fresh: `JSON.parse(JSON.stringify(obj))` gives you an object whose every nested object and array is a new reference, so mutating the copy cannot touch the original — and, conversely, two properties that pointed at the *same* object before the round trip come back as two separate objects. It is also worth being precise about types: the output of `stringify` is a string, not "a JSON object", so you cannot read properties off it until you parse it. And `JSON.parse` accepts any JSON value at the top level, not just objects — `JSON.parse('42')` is the number `42`.
code
javascript · 21 linesconst original = { id: 1, tags: ['a', 'b'], nested: { ok: true } };
const text = JSON.stringify(original);
console.log(typeof text, text);
// string {"id":1,"tags":["a","b"],"nested":{"ok":true}}
const copy = JSON.parse(text);
console.log(copy.nested.ok); // true
console.log(copy === original); // false
console.log(copy.nested === original.nested); // false
// Aliasing is not preserved: one object in, two objects out.
const shared = { n: 1 };
const withAlias = { a: shared, b: shared };
const round = JSON.parse(JSON.stringify(withAlias));
console.log(withAlias.a === withAlias.b); // true
console.log(round.a === round.b); // false
// Any JSON value may sit at the top level.
console.log(JSON.parse('42'), JSON.parse('"hi"'), JSON.parse('null'));
// 42 hi nullgo deeper
Be able to say plainly that stringify produces a string of JSON text and parse turns that text back into a value, and that the parsed result is a new object rather than the original one.
Explain the mechanics: stringify recurses over own enumerable string-keyed properties and emits no whitespace by default, and parse allocates every object and array fresh, so nested references differ from the source.
Show that you know what the round trip costs and what it silently changes in production code — full serialize plus parse on every call, prototypes gone, shared references duplicated — and that comparing stringified output is not a safe equality test.
Own the design call: decide where in a system data is allowed to exist as JSON text versus as live objects, and argue why converting through text at a boundary is a deliberate contract choice rather than a convenience for copying data internally.
## The two directions JSON (JavaScript Object Notation) is a **text** format. It is a way of writing data down as characters so it can be stored in a file, put in a log line, or sent over a network. JavaScript values live in memory as objects, arrays, numbers and strings; JSON text is a sequence of characters. The built-in `JSON` object provides the two functions that move between those worlds: - `JSON.stringify(value)` — takes a JavaScript value and returns a **string** containing JSON text. - `JSON.parse(text)` — takes a string containing JSON text and returns a **JavaScript value** built from it. ```js const text = JSON.stringify({ id: 1, tags: ['a'] }); typeof text; // 'string' text; // '{"id":1,"tags":["a"]}' JSON.parse(text).id; // 1 ``` ## What stringify actually walks Starting from the value you pass, `JSON.stringify` recurses through the structure. For an object it visits its own enumerable string-keyed properties; for an array it visits the elements by index. Strings are emitted with JSON escaping (quotes, backslashes and control characters become escape sequences). From ES2019 onward, engines also escape lone surrogate code units as `\uD800`-style sequences — the "well-formed JSON.stringify" behaviour — so the returned string is always valid, printable JSON text. By default the output carries **no whitespace whatsoever**: no newlines, no space after the colon. That is why raw stringified payloads look like one dense line. ## Parse builds a completely fresh graph This is the part candidates get wrong. `JSON.parse` does not hand back anything that existed before; it constructs new objects and arrays as it reads the text. So: ```js const original = { nested: { ok: true } }; const copy = JSON.parse(JSON.stringify(original)); copy === original; // false copy.nested === original.nested; // false copy.nested.ok = false; // original is untouched ``` A consequence worth stating out loud: **aliasing is not preserved**. If two properties of the original pointed at the *same* object, the JSON text simply contains that object's data written out twice, and after parsing you have two independent objects. Identity is not part of the format. ## JSON text is not a JavaScript object A very common junior confusion is calling the result of `stringify` "a JSON object" and then trying to read properties off it. The result is a string; `text.id` is `undefined`, and `text.length` is the number of characters. Symmetrically, the argument to `JSON.parse` must be text — if you already have an object, there is nothing to parse. ## Any JSON value can sit at the top level The format is not restricted to objects and arrays at the outermost position: ```js JSON.parse('42'); // 42 JSON.parse('"hi"'); // 'hi' JSON.parse('true'); // true JSON.parse('null'); // null JSON.stringify(42); // '42' ``` The argument to `JSON.parse` is also coerced to a string first, which is why `JSON.parse(null)` quietly returns `null` — `null` becomes the text `"null"`, which happens to be valid JSON. ## Key order, and why you should not compare JSON strings `JSON.stringify` emits an object's own enumerable string keys in that object's own property order — the same order `Object.keys` reports. Two objects with identical contents but different insertion order therefore produce **different** text. Comparing `JSON.stringify(a) === JSON.stringify(b)` as a deep-equality test is a real source of false negatives. ## The round trip is a conversion, not a copy JSON's value model is a small subset of JavaScript's. The round trip preserves whatever the format can express and silently drops or reshapes whatever it cannot, so treat `JSON.parse(JSON.stringify(x))` as "give me plain data" rather than as a general-purpose deep clone. One consequence you can see immediately: class instances come back as plain objects, because JSON text carries no notion of a prototype. ## Cost Both functions are implemented natively, but both do work proportional to the size of the graph, and `stringify` allocates a string as large as the whole payload. Using the round trip to clone large structures on a hot path is a pattern that shows up plainly in CPU profiles — it is convenient, not cheap.
- If JSON.parse always builds fresh objects, why is JSON.parse(JSON.stringify(x)) still a poor general-purpose deep clone?Because it is a conversion through a text format whose value model is much smaller than JavaScript's: only what JSON can express comes back, class instances arrive as plain objects, and object identity inside the graph is lost. It also pays for a full serialize plus a full parse, which is far more work than a purpose-built copy of the same structure.
- Why can two objects with the same contents produce different JSON.stringify output?Because stringify emits an object's own enumerable string keys in that object's own property order — the same order `Object.keys` reports. `{a:1,b:2}` and `{b:2,a:1}` therefore serialize to different text. That is why comparing stringified output as a deep-equality check produces false negatives; compare the values structurally instead.
Stringify is like writing a piece of furniture down as flat-pack instructions; parse is building a new piece of furniture from those instructions. You get an identical-looking item, but it is not the same item, and anything the instructions could not describe is missing.
saying these in an interview costs you the question
- Says JSON.parse hands back the same object you serialized
- Calls the output of JSON.stringify a JSON object rather than a string
- Thinks JSON.stringify mutates or annotates the object it is given
- Assumes only objects and arrays can be parsed at the top level
- Uses stringified output as a deep-equality comparison