JavaScript Type Coercion - Output-Based Questions ([] + [], NaN === NaN & Friends)
After hoisting, interviewers love dropping one-liners like: console.log([] + []); console.log([] + {}); console.log({} + []); console.log(NaN === NaN); …and watching whether you guess, freeze, or calmly walk the coercion rules. This post is only output-based type coercion / equality questions. Try each snippet yourself first. Answers are hidden - click Show answer when you’re ready.
TL;DR - what interviewers are testing
| Concept | Trap |
|---|---|
+ with objects/arrays |
Often becomes string concat, not math |
[] / {} stringification |
[] → "", {} → "[object Object]" |
Bare {} + [] |
Parser may treat {} as a block, not an object |
NaN === NaN |
Always false - use Number.isNaN / Object.is |
== vs === |
== coerces; === does not |
| Falsy vs “empty‑looking” | [] and {} are truthy |
typeof null |
Infamous "object" lie |
One‑line mental model
+ asks both sides to become primitives. If either side is a string (after that), you get concatenation. Otherwise you get number math - and weird values become NaN.
Warm‑up: how + really decides
When JS hits a + b, it roughly does:
- Convert both sides to primitives (
ToPrimitive) - If either result is a string →
String(a) + String(b)// concat - Else →
Number(a) + Number(b)// math
For plain objects / arrays, ToPrimitive usually ends up calling .toString():
| Value | String(value) |
Number(value) |
|---|---|---|
[] |
"" |
0 |
[1, 2] |
"1,2" |
NaN |
{} |
"[object Object]" |
NaN |
null |
"null" |
0 |
undefined |
"undefined" |
NaN |
true |
"true" |
1 |
false |
"false" |
0 |
That’s enough to solve most [] + {} style questions.
How to use this post
- Read the snippet
- Say the output out loud (or write it down)
- Only then open Show answer
- Read the step‑by‑step - don’t only memorize the final print
Q1 - Classic [] + []
console.log([] + []);
Show answer
Output (empty string - looks like a blank line)
Step by step
+ wants primitives from both arrays. String([]) → "" (empty array joins to empty string). "" + "" → "".
Interview tip: People often say 0 or []. Wrong. Empty array stringifies to "", so you get string concat of nothing.
Q2 - [] + {}
console.log([] + {});
Show answer
Output [object Object]
Step by step
Left: String([]) → ""
Right: String({}) → "[object Object]"
One side is a string → concat → "" + "[object Object]" → "[object Object]"
Why not math? Because after ToPrimitive, you already have a string on the left (""), so + stays in string mode.
Q3 - ({} + []) with parentheses
console.log({} + []);
console.log(({} + []));
Show answer
Output
[object Object]
[object Object]
Step by step
Inside console.log(...) / parentheses, {} is an object literal.
String({}) → "[object Object]"
String([]) → ""
Concat → "[object Object]" + "" → "[object Object]"
Same printable result as [] + {}, different operand order - both sides still stringify.
Q4 - The famous bare {} + [] trap
eval(' {} + [] ');
eval(' ({} + []) ');
Show answer
Output
0
[object Object]
Step by step
Bare {} + [] at statement start: JS can parse {} as an empty block, not an object. What’s left is unary +[]. +[] → Number([]) → 0.
Wrap it: ({} + []) forces an expression → object + array → "[object Object]".
Interview tip: Always ask: “Is this a statement or an expression?” Parentheses (or console.log) change the parse.
Q5 - Arrays with values still concat
console.log([1, 2] + [3, 4]);
Show answer
Output 1,23,4
Step by step
String([1, 2]) → "1,2"
String([3, 4]) → "3,4"
"1,2" + "3,4" → "1,23,4"
No spaces. No nested array. Just two strings glued together.
Q6 - NaN === NaN
console.log(NaN === NaN);
console.log(NaN == NaN);
Show answer
Output
false
false
Step by step
IEEE‑754 rule: NaN is never equal to anything, including itself. So both === and == return false.
Correct checks
Number.isNaN(NaN); // true ← prefer this
Object.is(NaN, NaN); // true
isNaN('hello'); // true ← coerces first (sloppy)
Number.isNaN('hello'); // false ← no coercion
Interview tip: Saying “use isNaN” is incomplete. Prefer Number.isNaN or Object.is.
Q7 - How NaN is born (and stays sticky)
console.log(Number('abc'));
console.log('abc' - 1);
console.log(undefined + 1);
console.log(NaN + 5);
console.log(typeof NaN);
Show answer
Output
NaN
NaN
NaN
NaN
"number"
Step by step
Failed number conversion → NaN. Any math with NaN → NaN (it “poisons” the expression). typeof NaN is still "number" - weird but true.
One‑liner: NaN means “invalid number,” not “not a number type.”
Q8 - Object.is vs === (NaN + -0)
console.log(Object.is(NaN, NaN));
console.log(Object.is(+0, -0));
console.log(+0 === -0);
Show answer
Output
true
false
true
Step by step
Object.is treats NaN as equal to NaN. Object.is treats +0 and -0 as different. === does the opposite on zeros: +0 === -0 is true.
Use this when they ask “when is Object.is not the same as ===?”
Q9 - + vs - with strings
console.log('5' + 2);
console.log('5' - 2);
console.log('5' * '2');
console.log('5' + '2');
Show answer
Output
52
3
10
52
Step by step
+ with a string → concat → "52".
- / * always do numeric conversion → 3, 10.
'5' + '2' is pure string concat → "52".
Rule of thumb: Only + is overloaded for strings. -, *, /, % are always math.
Q10 - Booleans in math
console.log(true + true);
console.log(true + false);
console.log(true + '1');
console.log(false - true);
Show answer
Output
2
1
"true1"
-1
Step by step
true → 1, false → 0 in numeric context.
true + true → 1 + 1 → 2.
true + '1' hits a string → "true" + "1" → "true1".
false - true → 0 - 1 → -1.
Q11 - Unary plus / Number on [] and {}
console.log(+[]);
console.log(+{});
console.log(Number([]));
console.log(Number({}));
Show answer
Output
0
NaN
0
NaN
Step by step
+[] → Number([]) → Number("") → 0.
+{} → Number("[object Object]") → NaN.
This is why bare {} + [] can print 0 (unary plus on []).
Q12 - null / undefined with +
console.log(null + 1);
console.log(undefined + 1);
console.log([] + null);
console.log(null + []);
Show answer
Output
1
NaN
"null"
"null"
Step by step
Numeric: null → 0, so null + 1 → 1.
Numeric: undefined → NaN, so undefined + 1 → NaN.
With arrays, + becomes string concat: "" + "null" → "null".
Same characters printed for the last two - both are the string "null".
Q13 - null == undefined (and friends)
console.log(null == undefined);
console.log(null === undefined);
console.log(null == 0);
console.log(undefined == 0);
console.log(null == false);
Show answer
Output
true
false
false
false
false
Step by step
Spec special case: null == undefined is true. === checks type too → false.
null / undefined do not loosely equal 0 or false. Memorize this trio: null == undefined is the exception people forget.
Q14 - Empty array vs empty string vs 0
console.log([] == 0);
console.log([] == '');
console.log('' == 0);
console.log([] == false);
console.log(false == '');
Show answer
Output
true
true
true
true
true
Step by step (high level)
== keeps coercing until it can compare primitives. [] becomes "", then often 0. false becomes 0.
So these “different‑looking” values all meet in the middle as 0 / "".
Interview tip: This is why seniors say never use == unless you mean it. Prefer ===.
Q15 - The viral [] == ![]
console.log(![]);
console.log([] == ![]);
Show answer
Output
false
true
Step by step
[] is truthy, so ![] → false. Expression becomes [] == false.
false → 0, [] → "" → 0. 0 == 0 → true.
Looks impossible. Totally consistent with coercion rules.
Q16 - Truthy / falsy surprises
console.log(Boolean([]));
console.log(Boolean({}));
console.log(Boolean(''));
console.log(Boolean(0));
console.log(Boolean(NaN));
console.log(Boolean(null));
console.log(!!'0');
Show answer
Output
true
true
false
false
false
false
true
Falsy list (memorize - only these):
false, 0, -0, 0n, '', null, undefined, NaN
Everything else is truthy - including [], {}, and '0'.
Q17 - Reference equality for objects/arrays
console.log([] === []);
console.log({} === {});
console.log([] == []);
console.log({} == {});
const a = [];
console.log(a === a);
Show answer
Output
false
false
false
false
true
Step by step
Objects/arrays compare by reference, not contents. Each {} / [] literal creates a new value in memory. Same variable refers to the same reference → true.
== does not deep‑compare objects either.
Q18 - typeof null classic
console.log(typeof null);
console.log(typeof []);
console.log(typeof {});
console.log(typeof NaN);
console.log(Array.isArray([]));
Show answer
Output
"object"
"object"
"object"
"number"
true
Step by step
typeof null === "object" is a decades‑old language bug/legacy quirk. Arrays are objects → "object"; detect with Array.isArray. NaN is a number. Safe null check: value === null (not typeof).
Q19 - Floating point equality
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
Show answer
Output
0.30000000000000004
false
Why: Binary floating point can’t represent 0.1 / 0.2 exactly. Not a coercion bug - a precision bug. Mention Number.EPSILON or decimal libraries if they push deeper.
Q20 - Mixed bag rapid fire
console.log([] + {});
console.log({} + []);
console.log([] + [] + ' ok');
console.log('' + 1 + 0);
console.log('' - 1 + 0);
console.log(true + true + true);
console.log(!![]);
console.log(Number.isNaN(NaN) && !(NaN === NaN));
Show answer
Output
[object Object]
[object Object]
ok
10
-1
3
true
true
Quick why
- First two: stringified object (inside
console.log,{}is an expression). "" + "" + "ok"→"ok"."" + 1 + 0→"10"(left‑to‑right concat)."" - 1becomes numeric-1, then-1 + 0→-1.truethrice →3.[]truthy →true.- Best NaN check +
===self‑inequality both hold →true.
Practice round (no spoilers)
Predict each line, then check in a REPL.
P1
console.log([] + []);
console.log([] + 1);
console.log([1] + [2]);
"", then "1", then "1"+"2".
P2
console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log(Number.isNaN('NaN'));
false, true, false - Number.isNaN does not coerce strings.
P3
console.log([] == ![]);
console.log({} == !{});
true, false - !{} → false → {} == false → NaN == 0 path fails → false.
P4
console.log(null + null);
console.log(undefined + undefined);
console.log(null == undefined);
0, NaN, true - null → 0; undefined → NaN; loose equal special case.
P5
console.log('10' - 5 + '5');
console.log('10' + 5 - '5');
"55", 100 - Left: (10-5) + "5" → "55". Right: "105" - "5" → 100.
Cheat sheet - say this in the interview
+with a string involved → concatenation[]→""→0|{}→"[object Object]"→NaN- Bare
{}at statement start may be a block NaN === NaN→false; useNumber.isNaN/Object.is- Prefer
===;==is a coercion maze - Only these are falsy:
false,0,-0,0n,'',null,undefined,NaN typeof null→"object"(legacy)
Wrap‑up
Coercion questions in JS interviews are almost always output prediction, not definitions. Master these four:
- How
+chooses concat vs math - What
[]/{}become as string/number - Why
NaN === NaNisfalse - Why
==makes[] == ![]“true”
Everything else is a remix of those rules.
If useful, I can do a follow‑up in the same format for this binding, closures, or optional chaining / nullish coalescing output traps.
Comments
No comments yet. Start the discussion.