Coercion & Mutation Traps
Key Learning Objectives
Master object property key coercion: understand why plain objects stringify to '[object Object]' when used as keys.
Understand reference passing vs parameter reassignment in JavaScript function calls.
Analyze binary plus operand coercion with Array and Object instances via ToPrimitive and toString.
Master abstract equality (==) special rules for null and undefined compared to strict equality (===).
Confidently solve complex multi-paradigm interview output prediction puzzles.
The Interview Problem
What is logged to the console when the following object property indexing, array parameter mutation, and primitive coercion expressions execute?
1const a = {};2const b = { key: 'b' };3const c = { key: 'c' };45a[b] = 123;6a[c] = 456;78const arr = [1, 2, 3];9function mutate(list, item) {10 list.push(item);11 list = [99];12 list.push(100);13}14mutate(arr, 4);1516const res1 = a[b];17const res2 = arr.length;18const res3 = [] + {};19const res4 = (null == undefined) + '-' + (null === undefined);2021console.log(res1, res2, res3, res4);
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
456 4 [object Object] true-false
123 5 [object Object] true-true
456 5 [object Object] false-false
undefined 4 {} true-false
V8 Engine Execution Trace
Step 1 of 7 (Line 1)Allocates plain objects a, b, and c in heap memory.
Deep Technical Breakdown
1. Object Property Key Coercion
In standard JavaScript objects ({}), property keys can only be Strings or Symbols. When an object (b) is passed into bracket notation a[b], JavaScript invokes the internal abstract operation ToPropertyKey(b):
b.toString()is called, which evaluates to"[object Object]"viaObject.prototype.toString.- Therefore,
a[b] = 123executes asa["[object Object]"] = 123. - When
a[c] = 456runs,calso coerces to"[object Object]"! - This directly overwrites the key, resulting in
a[b] === 456.
(Note: To use object references as distinct keys without string coercion, use an ES6 Map instead).
2. Pass-by-Sharing & Parameter Reassignment
JavaScript is strictly pass-by-value. When passing an object reference to a function:
- The parameter (
list) receives a copy of the reference pointer pointing to the same array in heap memory. - Mutating the object via
list.push(item)mutates the shared heap object (arr). - Reassigning the parameter (
list = [99]) merely updates the local variable pointerlistto point to a newly allocated array. It does not reassign the caller's variablearr. - Subsequent mutations on
listonly affect the new array.
3. Binary Plus & Type Coercion
In binary expressions A + B, if either operand is an object, JavaScript applies ToPrimitive(hint: "default"):
[].toString()produces""(empty string).({}).toString()produces"[object Object]"."" + "[object Object]"concatenates into"[object Object]".
4. Nullish Equality Rules
- Abstract Equality (
null == undefined): The ECMAScript specification (§ 7.2.14, Step 2-3) explicitly defines thatnull == undefinedistrue, and neithernullnorundefinedcoerces to anything else in abstract equality. - Strict Equality (
null === undefined):nullhas typeobject(historical bug) whileundefinedhas typeundefined. Because their primitive types differ, strict equality is unconditionallyfalse.
Common Traps & Mistakes
Assuming plain JavaScript objects support object identity as keys. Object keys are always converted to strings unless they are Symbols.
Believing that reassigning a function parameter (`list = []`) reassigns the caller's variable.
Expecting `null == 0` to be true. While `Number(null)` is `0`, abstract equality comparison specifically dictates that `null` only loosely equals `undefined` and nothing else.
Confusing `{}` evaluated in statement position (interpreted as an empty block) with `{}` evaluated as an expression operand.
FAANG Follow-Up Probes
Probe #1
What does Map use instead of string coercion for object keys, and what equality algorithm powers it?
Probe #2
Why does [] == ![] evaluate to true in JavaScript?
Probe #3
How can you make a custom object return a custom primitive value during type coercion using Symbol.toPrimitive?
