JavaScript Type Coercion — Output-Based Questions ([] + [], NaN === NaN & Friends)
DEV Community

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:

  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

  • 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 NaNNaN (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

true1, false0 in numeric context.
true + true1 + 12.
true + '1' hits a string → "true" + "1""true1".
false - true0 - 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: null0, so null + 11.
Numeric: undefinedNaN, so undefined + 1NaN.

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.
false0, []""0. 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');

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

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

"", 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{} == falseNaN == 0 path fails → false.

P4

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

0, NaN, true - null0; undefinedNaN; 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

  1. + with a string involved → concatenation
  2. []""0 | {}"[object Object]"NaN
  3. Bare {} at statement start may be a block
  4. NaN === NaNfalse; 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:

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