DEV Community

Cover image for JavaScript Type Coercion — Output-Based Questions ([] + [], NaN === NaN & Friends)
Amit Kumar
Amit Kumar

Posted on

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);
Enter fullscreen mode Exit fullscreen mode

…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:

1) Convert both sides to primitives (ToPrimitive)
2) If either result is a string → String(a) + String(b)   // concat
3) Else → Number(a) + Number(b)                         // math
Enter fullscreen mode Exit fullscreen mode

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

  1. Read the snippet
  2. Say the output out loud (or write it down)
  3. Only then open Show answer
  4. Read the step-by-step — don’t only memorize the final print

Q1 — Classic [] + []

console.log([] + []);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
""
Enter fullscreen mode Exit fullscreen mode

(empty string — looks like a blank line)

Step by step

  1. + wants primitives from both arrays.
  2. String([])"" (empty array joins to empty string).
  3. "" + """".

Interview tip: People often say 0 or []. Wrong. Empty array stringifies to "", so you get string concat of nothing.



Q2 — [] + {}

console.log([] + {});
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
[object Object]
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Left: String([])""
  2. Right: String({})"[object Object]"
  3. 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(({} + []));
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
[object Object]
[object Object]
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Inside console.log(...) / parentheses, {} is an object literal.
  2. String({})"[object Object]"
  3. String([])""
  4. Concat → "[object Object]" + """[object Object]"

Same printable result as [] + {}, different operand order — both sides still stringify.



Q4 — The famous bare {} + [] trap

eval('{} + []');
eval('({} + [])');
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
0
[object Object]
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Bare {} + [] at statement start: JS can parse {} as an empty block, not an object.
  2. What’s left is unary +[].
  3. +[]Number([])0.
  4. 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]);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
1,23,4
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. String([1, 2])"1,2"
  2. String([3, 4])"3,4"
  3. "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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
false
false
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. IEEE-754 rule: NaN is never equal to anything, including itself.
  2. 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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
NaN
NaN
NaN
NaN
number
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Failed number conversion → NaN.
  2. Any math with NaNNaN (it “poisons” the expression).
  3. 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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
true
false
true
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Object.is treats NaN as equal to NaN.
  2. Object.is treats +0 and -0 as different.
  3. === 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');
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
52
3
10
52
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. + with a string → concat"52".
  2. - / * always do numeric conversion → 3, 10.
  3. '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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
2
1
true1
-1
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. true1, false0 in numeric context.
  2. true + true1 + 12.
  3. true + '1' hits a string → "true" + "1""true1".
  4. false - true0 - 1-1.


Q11 — Unary plus / Number on [] and {}

console.log(+[]);
console.log(+{});
console.log(Number([]));
console.log(Number({}));
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
0
NaN
0
NaN
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. +[]Number([])Number("")0.
  2. +{}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 + []);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
1
NaN
null
null
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Numeric: null0, so null + 11.
  2. Numeric: undefinedNaN, so undefined + 1NaN.
  3. 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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
true
false
false
false
false
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Spec special case: null == undefined is true.
  2. === checks type too → false.
  3. 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 == '');
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
true
true
true
true
true
Enter fullscreen mode Exit fullscreen mode

Step by step (high level)

  1. == keeps coercing until it can compare primitives.
  2. [] becomes "", then often 0.
  3. false becomes 0.
  4. 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([] == ![]);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
false
true
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. [] is truthy, so ![]false.
  2. Expression becomes [] == false.
  3. false0, []""0.
  4. 0 == 0true.

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');
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
true
true
false
false
false
false
true
Enter fullscreen mode Exit fullscreen mode

Falsy list (memorize — only these):

false, 0, -0, 0n, '', null, undefined, NaN
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
false
false
false
false
true
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. Objects/arrays compare by reference, not contents.
  2. Each {} / [] literal creates a new value in memory.
  3. 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([]));
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
object
object
object
number
true
Enter fullscreen mode Exit fullscreen mode

Step by step

  1. typeof null === "object" is a decades-old language bug/legacy quirk.
  2. Arrays are objects → "object"; detect with Array.isArray.
  3. 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);
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
0.30000000000000004
false
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

Show answer
Output
[object Object]
[object Object]
ok
10
-1
3
true
true
Enter fullscreen mode Exit fullscreen mode

Quick why

  1. First two: stringified object (inside console.log, {} is an expression).
  2. "" + "" + "ok""ok".
  3. "" + 1 + 0"10" (left-to-right concat).
  4. "" - 1 becomes numeric -1, then -1 + 0-1.
  5. true thrice → 3.
  6. [] truthy → true.
  7. 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]);
Enter fullscreen mode Exit fullscreen mode

Show answer

1
12
Enter fullscreen mode Exit fullscreen mode

"", then "1", then "1"+"2".


P2

console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log(Number.isNaN('NaN'));
Enter fullscreen mode Exit fullscreen mode

Show answer
false
true
false
Enter fullscreen mode Exit fullscreen mode

Number.isNaN does not coerce strings.


P3

console.log([] == ![]);
console.log({} == !{});
Enter fullscreen mode Exit fullscreen mode

Show answer
true
false
Enter fullscreen mode Exit fullscreen mode

!{}false{} == falseNaN == 0 path fails → false.


P4

console.log(null + null);
console.log(undefined + undefined);
console.log(null == undefined);
Enter fullscreen mode Exit fullscreen mode

Show answer
0
NaN
true
Enter fullscreen mode Exit fullscreen mode

null0; undefinedNaN; loose equal special case.


P5

console.log('10' - 5 + '5');
console.log('10' + 5 - '5');
Enter fullscreen mode Exit fullscreen mode

Show answer
55
100
Enter fullscreen mode Exit fullscreen mode

Left: (10-5)+"5""55". Right: "105" - "5"100.



Cheat sheet — say this in the interview

1. + with a string involved → concatenation
2. [] → "" → 0   |   {} → "[object Object]" → NaN
3. Bare {} at statement start may be a block
4. NaN === NaN → false; use Number.isNaN / Object.is
5. Prefer ===; == is a coercion maze
6. Only these are falsy: false, 0, -0, 0n, '', null, undefined, NaN
7. typeof null → "object" (legacy)
Enter fullscreen mode Exit fullscreen mode

Wrap-up

Coercion questions in JS interviews are almost always output prediction, not definitions.

Master these four:

  1. How + chooses concat vs math
  2. What [] / {} become as string/number
  3. Why NaN === NaN is false
  4. 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.

Top comments (0)