Dev.to WebDev 🛠 Dev 👁 0 📖 7 min read

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

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:

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

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([] + []);

Show answer
Output
""

(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([] + {});

Show answer
Output
[object Object]

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(({} + []));

Show answer
Output
[object Object]
[object Object]

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('({} + [])');

Show answer
Output
0
[object Object]

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]);

Show answer
Output
1,23,4

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);

Show answer
Output
false
false

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

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

  1. Failed number conversion → NaN.
  2. Any math with NaN → NaN (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);

Show answer
Output
true
false
true

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');

Show answer
Output
52
3
10
52

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);

Show answer
Output
2
1
true1
-1

Step by step

  1. true → 1, false → 0 in numeric context.
  2. true + true → 1 + 1 → 2.
  3. true + '1' hits a string → "true" + "1" → "true1".
  4. 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

  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 + []);

Show answer
Output
1
NaN
null
null

Step by step

  1. Numeric: null → 0, so null + 1 → 1.
  2. Numeric: undefined → NaN, so undefined + 1 → NaN.
  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);

Show answer
Output
true
false
false
false
false

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 == '');

Show answer
Output
true
true
true
true
true

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([] == ![]);

Show answer
Output
false
true

Step by step

  1. [] is truthy, so ![] → false.
  2. Expression becomes [] == false.
  3. false → 0, [] → "" → 0.
  4. 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

  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([]));

Show answer
Output
object
object
object
number
true

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);

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

  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]);

Show answer

1
12

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


P2

console.log(NaN === NaN);
console.log(Object.is(NaN, NaN));
console.log(Number.isNaN('NaN'));

Show answer
false
true
false

Number.isNaN does not coerce strings.


P3

console.log([] == ![]);
console.log({} == !{});

Show answer
true
false

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


P4

console.log(null + null);
console.log(undefined + undefined);
console.log(null == undefined);

Show answer
0
NaN
true

null → 0; undefined → NaN; loose equal special case.


P5

console.log('10' - 5 + '5');
console.log('10' + 5 - '5');

Show answer
55
100

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)

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.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.