What does JSON.stringify() do with undefined, function values and symbols, and how does the outcome differ between an object property, an array element, and the top-level argument?
answer
- three values JSON cannot express
- position decides the outcome
- objects lose the key, arrays keep the slot
- top level returns no string at all
- symbol keys never serialize
basics
~20 sJSON.stringify silently drops undefined, function and symbol object properties, turns those same values into null inside arrays, and returns the value undefined — not a string — when you pass one directly as the top-level argument.
solid answer
~40 sJSON has no grammar for `undefined`, functions or symbols, so `JSON.stringify` treats all three as unserializable and then does one of three things depending on where the value sits. As an object property the key is dropped entirely: `JSON.stringify({a: 1, b: undefined})` gives `'{"a":1}'`. As an array element it becomes `null`, because dropping it would shift every later index: `JSON.stringify([1, undefined, 3])` gives `'[1,null,3]'`. As the top-level argument the call returns the value `undefined` rather than a string at all, so `typeof JSON.stringify(undefined)` is `'undefined'`. Symbol-*keyed* properties are skipped regardless of their value, as are non-enumerable and inherited properties. The practical consequence is that a round trip is not an identity function: `{a: undefined}` comes back as `{}`, so `'a' in obj` flips from true to false.
code
javascript · 5 linesconst obj = { a: 1, b: undefined, c() {}, d: Symbol('x') };
console.log(JSON.stringify(obj)); // {"a":1}
console.log(JSON.stringify([1, undefined, 3])); // [1,null,3]
console.log(JSON.stringify(undefined)); // undefined (the value)
console.log(JSON.stringify({ [Symbol('k')]: 1 })); // {}go deeper
Be able to state plainly that undefined, functions and symbols do not survive serialization: object keys vanish, array slots become null. Show the two-line example without hesitating.
Explain why the array case differs — omitting an element would shift indices — and that only own, enumerable, String-keyed properties are visited, which is what excludes symbol keys, non-enumerable and inherited properties.
Show where the silent drop causes production bugs: an absent field versus an explicit null in a PATCH body, cache entries holding the text "undefined", and equality checks built on stringify. Say how you normalise at the boundary.
Own the contract question: decide project-wide whether absent and null mean different things on the wire, and enforce that with an explicit serialization layer rather than relying on whatever the default algorithm happens to preserve.
## The starting point: JSON's value grammar is small JSON can represent exactly six shapes: object, array, string, number, the booleans, and `null`. JavaScript has considerably more. Three everyday JavaScript values — `undefined`, functions, and symbols — have no JSON counterpart at all. The serialization algorithm produces an internal "nothing" result for them, and what you observe depends entirely on **where** in the value that nothing appeared. ## Object properties: the key disappears ```js JSON.stringify({ a: 1, b: undefined, c() {}, d: Symbol('x') }); // '{"a":1}' ``` There is no placeholder and no warning. Three of the four keys are simply not in the output. This is the case that causes real bugs, because the difference between "the field was absent" and "the field was explicitly null" is meaningful to most servers — a PATCH endpoint typically reads absent as *leave unchanged* and `null` as *clear this field*. ## Array elements: the slot becomes null ```js JSON.stringify([1, undefined, function () {}, Symbol('x')]); // '[1,null,null,null]' ``` Arrays are positional. Dropping index 1 would renumber everything after it and change the array's length, so the algorithm substitutes `null` instead. Sparse array holes behave the same way: `JSON.stringify([1, , 3])` yields `'[1,null,3]'`, so a hole and an explicit `null` are indistinguishable after a round trip. ## Top level: you get undefined, not a string ```js JSON.stringify(undefined); // undefined JSON.stringify(() => {}); // undefined typeof JSON.stringify(Symbol()); // 'undefined' ``` The return type of `JSON.stringify` is therefore `string | undefined`. Code that assumes a string will fail one step later: feeding that result straight back into `JSON.parse` coerces `undefined` to the string `"undefined"`, which is not valid JSON, so the failure surfaces at the parse call with a confusing message rather than at the stringify call where the real problem is. ## Symbol keys, non-enumerable keys, inherited keys Serialization enumerates a value's **own enumerable String-keyed** properties. Three exclusions follow directly from that phrase: ```js JSON.stringify({ [Symbol('k')]: 1 }); // '{}' const o = {}; Object.defineProperty(o, 'hidden', { value: 1, enumerable: false }); JSON.stringify(o); // '{}' JSON.stringify(Object.create({ inherited: 1 })); // '{}' ``` A symbol-keyed property is skipped even when its value is a perfectly serializable number. This is one reason symbols are a reasonable place to hang internal metadata you do not want crossing the wire. ## Why the asymmetry is not arbitrary All three positions are answering the same question — "what do I write when there is nothing to write?" — under different constraints. In an object, omitting a key is legal JSON and preserves the remaining structure. In an array, omission would corrupt indices, so a placeholder is required and `null` is the only neutral one available. At the top level there is no surrounding structure to place anything into, so the operation has no result at all. ## Where this shows up in real code - **Request bodies.** `JSON.stringify({ name, nickname })` where `nickname` is `undefined` sends `{"name":"..."}`. The server never sees the field. - **Class instances.** Methods live on the prototype and are neither own nor serializable, so an instance serializes to its data fields only, and the parsed result is a plain object with no behaviour. - **Structural comparison.** Comparing `JSON.stringify(a) === JSON.stringify(b)` treats `{x: undefined}` and `{}` as equal, and treats key insertion order as significant. Both are usually wrong for an equality check. - **Caches and logs.** A value that stringifies to `undefined` written into a cache stores the literal characters `undefined` if something concatenates it into a string. ## Making the loss explicit instead of silent The second argument to `JSON.stringify`, the replacer function, is invoked for every own enumerable key including ones whose value is `undefined`, so you can normalise them yourself: ```js JSON.stringify({ a: 1, b: undefined }, (key, value) => value === undefined ? null : value ); // '{"a":1,"b":null}' ``` That converts a silent omission into an explicit null, which is the right choice whenever the receiver distinguishes the two. The opposite discipline also works: strip `undefined` fields deliberately at the boundary so that what you send is exactly what you intended, rather than whatever happened to survive.
- If a property is dropped, how would a caller ever notice something went missing?They usually notice downstream, not at the call. `Object.keys` shrinks, an `in` check flips to false, and a server treats the field as absent rather than cleared. If you need the distinction preserved, normalise at the boundary — map `undefined` to `null` in a replacer, or validate the payload shape before sending — rather than relying on the reader to spot an absent key.
- Why does a class instance lose its methods when serialized?Methods declared in a class body live on the prototype, and serialization only visits own enumerable String-keyed properties. So instance fields survive and behaviour does not. Parsing the text back gives a plain object with the right data and no methods, which is why code that calls a method on a parsed value fails with "is not a function".
- What is the return type of JSON.stringify, and why does that matter?`string | undefined`. It returns `undefined` whenever the top-level value is itself unserializable — `undefined`, a function, or a symbol. Code that annotates or assumes a string will pass `undefined` onward, and the failure typically appears at a later parse or concatenation with a misleading message. Check the result before using it when the input is not statically known.
It is like a form scanner that skips blank fields on a checklist, but on a numbered ballot must print a blank line so the numbering still lines up.
saying these in an interview costs you the question
- Says undefined survives serialization as the literal undefined
- Thinks object properties become null like array elements do
- Assumes stringify always returns a string
- Believes symbol-keyed properties serialize when the value is serializable
- Claims stringify plus parse always reproduces the original object