logodev atlas
14 min read

Type Coercion — 60 Tricky Interview Questions

Master every coercion edge case. For each question, predict the output before reading the answer.


Q1: The Classic Empty Array

javascriptconsole.log([] + []);

Output: "" (empty string)

Why: Both arrays are converted to primitives via .toString(). [].toString() is "". So "" + "" = "".


Q2: Array Plus Object

javascriptconsole.log([] + {});

Output: "[object Object]"

Why: []"", {}"[object Object]". String concatenation: "" + "[object Object]".


Q3: Object Plus Array (The Gotcha)

javascriptconsole.log({} + []);

Output: 0 (in most consoles) or "[object Object]" (when used as expression)

Why: At statement level, {} is parsed as an empty block, not an object literal. So it becomes +[]+""0. Wrap in parens to force expression: ({} + [])"[object Object]".


Q4: Boolean Arithmetic

javascriptconsole.log(true + true + true);

Output: 3

Why: true coerces to 1 in numeric context. 1 + 1 + 1 = 3.


Q5: String Wins

javascriptconsole.log(1 + '2' + 3);

Output: "123"

Why: Left-to-right: 1 + '2'"12" (string wins), then "12" + 3"123".


Q6: Subtraction Doesn't Concatenate

javascriptconsole.log('5' - 3);
console.log('5' + 3);

Output: 2 then "53"

Why: - only does numeric operations, so '5'5. But + prefers string concatenation when either side is a string.


Q7: null Arithmetic

javascriptconsole.log(null + 1);
console.log(null + '1');

Output: 1 then "null1"

Why: null0 in numeric context, but null"null" in string context.


Q8: undefined Arithmetic

javascriptconsole.log(undefined + 1);
console.log(undefined + '1');

Output: NaN then "undefined1"

Why: undefinedNaN in numeric context (not 0 like null!), but → "undefined" in string context.


Q9: The Infamous WAT

javascriptconsole.log([] == false);
console.log([] == ![]);

Output: true then true

Why:

  • [] == false: false0, []""0. 0 == 0true
  • [] == ![]: ![] is false (array is truthy, negated). Then same as above.

Q10: Truthy Array, Falsy Comparison

javascriptconsole.log(!![] === true);
console.log([] == true);

Output: true then false

Why: !![]true (array is truthy). But [] == true: true1, []""0. 0 == 1false. An array is truthy but not == true!


Q11: String to Number Edge Cases

javascriptconsole.log(Number(''));
console.log(Number(' '));
console.log(Number('\n'));
console.log(Number('0x1A'));
console.log(Number('0b11'));
console.log(Number('0o17'));
console.log(Number('123abc'));

Output: 0, 0, 0, 26, 3, 15, NaN

Why: Empty/whitespace strings → 0. Hex, binary, octal literals are parsed. Any non-numeric content → NaN.


Q12: parseInt vs Number

javascriptconsole.log(parseInt('123abc'));
console.log(Number('123abc'));
console.log(parseInt(''));
console.log(Number(''));

Output: 123, NaN, NaN, 0

Why: parseInt parses until first invalid char and stops. Number requires the entire string to be valid. But parseInt('') is NaN while Number('') is 0 — opposite behavior for empty strings!


Q13: parseInt Radix Gotcha

javascriptconsole.log(parseInt('08'));
console.log(parseInt('08', 10));
console.log(parseInt('111', 2));
console.log(parseInt('0xF'));

Output: 8, 8, 7, 15

Why: Modern engines parse '08' as decimal by default (older engines used octal). Always pass radix to be safe. parseInt('111', 2) parses as binary. '0x' prefix triggers hex.


Q14: Comparison with null

javascriptconsole.log(null > 0);
console.log(null < 0);
console.log(null == 0);
console.log(null >= 0);
console.log(null <= 0);

Output: false, false, false, true, true

Why: For >/</>=/<=, null0. So 0 > 0 false, 0 < 0 false, 0 >= 0 true, 0 <= 0 true. But == has a special rule: null only equals undefined, NOT 0.


Q15: Comparison with undefined

javascriptconsole.log(undefined > 0);
console.log(undefined < 0);
console.log(undefined == 0);
console.log(undefined == null);

Output: false, false, false, true

Why: undefinedNaN in numeric comparisons. NaN is not >, <, or == to anything. But undefined == null is a special == rule.


Q16: String Comparison Lexicographic

javascriptconsole.log('10' > '9');
console.log('10' > 9);

Output: false then true

Why: When both sides are strings, comparison is lexicographic (char by char). '1' < '9' so '10' < '9'. When one side is a number, string coerces to number: 10 > 9true.


Q17: Plus Sign Unary

javascriptconsole.log(+true);
console.log(+false);
console.log(+null);
console.log(+undefined);
console.log(+[]);
console.log(+[1]);
console.log(+[1,2]);
console.log(+{});

Output: 1, 0, 0, NaN, 0, 1, NaN, NaN

Why: Unary + converts to number. []""0. [1]"1"1. [1,2]"1,2"NaN. {}"[object Object]"NaN.


Q18: Double Negation

javascriptconsole.log(!!0);
console.log(!!'0');
console.log(!!'');
console.log(!!NaN);
console.log(!!null);
console.log(!!undefined);
console.log(!!-1);
console.log(!!{});
console.log(!![]);

Output: false, true, false, false, false, false, true, true, true

Why: !! converts to boolean. The 8 falsy values: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy — including '0', {}, and [].


Q19: Template Literal Coercion

javascriptconsole.log(`${[1,2,3]}`);
console.log(`${{}}`);
console.log(`${null}`);
console.log(`${undefined}`);
console.log(`${true}`);

Output: "1,2,3", "[object Object]", "null", "undefined", "true"

Why: Template literals call .toString() on values. Array.toString joins with commas.


Q20: typeof Coercion

javascriptconsole.log(typeof 1);
console.log(typeof '1');
console.log(typeof NaN);
console.log(typeof null);
console.log(typeof undefined);
console.log(typeof []);
console.log(typeof {});
console.log(typeof function(){});

Output: "number", "string", "number", "object", "undefined", "object", "object", "function"

Why: typeof null is "object" (historical bug). typeof NaN is "number" (NaN is a number value). Arrays are objects.


Q21: Equality Chain

javascriptconsole.log('' == 0);
console.log(0 == '0');
console.log('' == '0');

Output: true, true, false

Why: == is NOT transitive! '' == 0 (both coerce to 0), 0 == '0' (both coerce to 0), but '' == '0' is same-type string comparison — different strings → false.


Q22: Boolean in Comparison

javascriptconsole.log(true == 'true');
console.log(true == '1');
console.log(true == 1);
console.log(false == '');

Output: false, true, true, true

Why: Booleans convert to numbers first: true → 1, false → 0. Then 1 == 'true''true'NaN → false. 1 == '1''1'1 → true.


Q23: Object to Primitive

javascriptconst obj = {
  valueOf() { return 42; },
  toString() { return 'hello'; }
};
console.log(obj + 1);
console.log(`${obj}`);
console.log(String(obj));

Output: 43, "hello", "hello"

Why: For + operator, valueOf() is preferred (numeric hint). For template literals and String(), toString() is preferred (string hint).


Q24: Symbol.toPrimitive

javascriptconst obj = {
  [Symbol.toPrimitive](hint) {
    if (hint === 'number') return 10;
    if (hint === 'string') return 'ten';
    return true; // default hint
  }
};
console.log(+obj);
console.log(`${obj}`);
console.log(obj + '');
console.log(obj + 0);

Output: 10, "ten", "true", 1

Why: Symbol.toPrimitive overrides both valueOf and toString. +obj → number hint. Template literal → string hint. + '' and + 0 → default hint (returns true). true + '' = "true", true + 0 = 1.


Q25: Loose Equality with Objects

javascriptconsole.log([1] == 1);
console.log(['1'] == 1);
console.log([1] == '1');
console.log(['1'] == '1');

Output: true, true, true, true

Why: Arrays convert via .toString(): [1]"1", ['1']"1". Then string/number coercion makes them all equal.


Q26: NaN Surprises

javascriptconsole.log(NaN === NaN);
console.log(NaN == NaN);
console.log(NaN > NaN);
console.log(NaN < NaN);
console.log(NaN != NaN);
console.log(isNaN('hello'));
console.log(Number.isNaN('hello'));

Output: false, false, false, false, true, true, false

Why: NaN is not equal to anything, including itself. NaN != NaN is true! isNaN() coerces first: isNaN('hello')isNaN(NaN) → true. Number.isNaN() does NOT coerce — only true for actual NaN value.


Q27: Comma in Array Constructor

javascriptconsole.log([,,,].length);
console.log([1,,3].length);
console.log([1,,3][1]);

Output: 3, 3, undefined

Why: Trailing comma doesn't add element, so [,,,] has 3 empty slots. [1,,3] has empty slot at index 1 which returns undefined when accessed.


Q28: Math.max and Math.min

javascriptconsole.log(Math.max());
console.log(Math.min());
console.log(Math.max() < Math.min());

Output: -Infinity, Infinity, true

Why: Math.max() with no args returns -Infinity (identity for max). Math.min() returns Infinity (identity for min). -Infinity < Infinitytrue.


Q29: Negation and Coercion

javascriptconsole.log(-'1');
console.log(-true);
console.log(-null);
console.log(-undefined);
console.log(-[]);
console.log(-[5]);

Output: -1, -1, -0, NaN, -0, -5

Why: Unary - coerces to number then negates. null0-0. undefinedNaN. []""0-0. [5]"5"5-5.


Q30: Double Equals with Boolean

javascriptconsole.log([] == false);
console.log({} == false);
console.log('' == false);
console.log('0' == false);
console.log('1' == true);
console.log('2' == true);

Output: true, false, true, true, true, false

Why: Boolean coerces to number first: false → 0, true → 1. Then: [] → "" → 0 == 0 ✓. {} → "[object Object]" → NaN == 0 ✗. '2' → 2 == 1 ✗ — only '1' equals true!


Q31: Addition Operator Ambiguity

javascriptconsole.log(1 + 2 + '3');
console.log('1' + 2 + 3);
console.log(1 + +'2' + 3);

Output: "33", "123", 6

Why: Left-to-right evaluation. 1+2=3, then 3+'3'="33". '1'+2="12", then "12"+3="123". +'2'=2 (unary), then 1+2+3=6.


Q32: Increment and Coercion

javascriptlet x = '5';
x++;
console.log(x, typeof x);

let y = '5';
y = y + 1;
console.log(y, typeof y);

Output: 6 "number" then "51" "string"

Why: ++ always converts to number first. But + 1 triggers string concatenation because y is a string.


Q33: Comparison Coercion Chain

javascriptconsole.log('2' > '10');
console.log(2 > '10');
console.log('02' == 2);

Output: true, false, true

Why: String vs string: lexicographic, '2' > '1'. Number vs string: string → number, 2 > 10 false. '02'2, 2 == 2 true.


Q34: Object Equality

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

Output: false, false, false, false

Why: Objects/arrays are compared by reference, not by value. Two different literals are two different references.


Q35: Weird valueOf

javascriptconst a = {
  val: 0,
  valueOf() { return ++this.val; }
};

console.log(a == 1);
console.log(a == 2);
console.log(a == 3);

Output: true, true, true

Why: Each == comparison calls valueOf(), which increments val. So a equals 1, then 2, then 3. This is the famous "make a == 1 && a == 2 && a == 3 true" puzzle.


Q36: if Coercion

javascriptif ('false') console.log('A');
if ('0') console.log('B');
if (-1) console.log('C');
if ({}) console.log('D');
if ([]) console.log('E');
if ('') console.log('F');
if (0) console.log('G');

Output: A B C D E

Why: Only falsy values skip the block. 'false' and '0' are non-empty strings → truthy. {} and [] are objects → truthy. '' and 0 are falsy.


Q37: Bitwise Coercion

javascriptconsole.log(~~'5.7');
console.log(~~true);
console.log(~~null);
console.log(~~undefined);
console.log(~~[]);
console.log(~~NaN);

Output: 5, 1, 0, 0, 0, 0

Why: ~~ (double NOT) truncates to 32-bit integer. Coerces to number first, then truncates. undefinedNaN0 (bitwise treats NaN as 0).


Q38: OR and AND Return Values

javascriptconsole.log(0 || 'hello');
console.log(1 || 'hello');
console.log(0 && 'hello');
console.log(1 && 'hello');
console.log('' || 0 || null || 'found');
console.log(1 && 2 && 3);
console.log(1 && 0 && 3);

Output: "hello", 1, 0, "hello", "found", 3, 0

Why: || returns first truthy value (or last value). && returns first falsy value (or last value). They return the actual value, not a boolean!


Q39: Nullish Coalescing vs OR

javascriptconsole.log(0 ?? 'default');
console.log('' ?? 'default');
console.log(null ?? 'default');
console.log(undefined ?? 'default');
console.log(0 || 'default');
console.log('' || 'default');

Output: 0, "", "default", "default", "default", "default"

Why: ?? only triggers on null/undefined. || triggers on any falsy value. 0 and "" are falsy but not nullish — so ?? keeps them, || replaces them.


Q40: Implicit toString in Object Keys

javascriptconst obj = {};
const a = {};
const b = {};
obj[a] = 1;
obj[b] = 2;
console.log(obj[a]);
console.log(Object.keys(obj));

Output: 2, ["[object Object]"]

Why: Object keys must be strings (or Symbols). Both a and b coerce to "[object Object]" — same key! So obj[b] = 2 overwrites obj[a] = 1.


Q41: Array Coercion in Comparison

javascriptconsole.log([0] == false);
console.log([1] == true);
console.log([2] == true);
console.log([''] == false);
console.log([null] == false);
console.log([undefined] == false);

Output: true, true, false, true, true, true

Why: Arrays → .toString(): [0]→"0"→0, [1]→"1"→1, [2]→"2"→2. Boolean → number: false→0, true→1. So [2]==true is 2==1 → false. [null]→""→0, [undefined]→""→0.


Q42: String Multiplication

javascriptconsole.log('3' * '4');
console.log('3' * true);
console.log('foo' * 1);
console.log(null * undefined);

Output: 12, 3, NaN, NaN

Why: * always converts both sides to numbers. '3'→3, '4'→4, true→1, 'foo'→NaN, null→0, undefined→NaN. 0*NaN=NaN.


Q43: JSON.stringify Coercion

javascriptconsole.log(JSON.stringify(undefined));
console.log(JSON.stringify(null));
console.log(JSON.stringify(NaN));
console.log(JSON.stringify(Infinity));
console.log(JSON.stringify({ a: undefined, b: null, c: NaN }));

Output: undefined, "null", "null", "null", '{"b":null,"c":null}'

Why: JSON.stringify(undefined) returns JS undefined (not the string). In objects, undefined values are omitted. NaN and Infinity become null in JSON.


Q44: Array Holes vs undefined

javascriptconst a = [1, , 3];
const b = [1, undefined, 3];

console.log(a.map(x => 'v'));
console.log(b.map(x => 'v'));
console.log(0 in a);
console.log(1 in a);

Output: ["v", empty, "v"], ["v", "v", "v"], true, false

Why: Array holes (sparse slots) are skipped by .map(), .forEach(), etc. undefined is an actual value and is iterated. 1 in a checks if index exists — holes don't have the index.


Q45: toString and valueOf Priority

javascriptconst a = {
  toString() { return '10'; },
  valueOf() { return 5; }
};

console.log(a + 1);
console.log(a + '1');
console.log(`${a}`);
console.log(Number(a));
console.log(String(a));

Output: 6, "51", "10", 5, "10"

Why: For + operator (default hint), valueOf() is called first → 5. For template literals and String(), toString() is called → "10". Number() uses valueOf()5.


Q46: Infinity Edge Cases

javascriptconsole.log(Infinity + Infinity);
console.log(Infinity - Infinity);
console.log(Infinity * 0);
console.log(Infinity / Infinity);
console.log(1 / 0);
console.log(-1 / 0);
console.log(0 / 0);

Output: Infinity, NaN, NaN, NaN, Infinity, -Infinity, NaN

Why: Indeterminate forms (∞ - ∞, ∞ × 0, ∞ / ∞, 0 / 0) all produce NaN. Division by zero gives ±Infinity (not an error!).


Q47: BigInt Coercion

javascriptconsole.log(1n + 2n);
// console.log(1n + 2); // TypeError!
console.log(typeof 1n);
console.log(1n == 1);
console.log(1n === 1);

Output: 3n, "bigint", true, false

Why: BigInt and Number cannot be mixed with +. But == does coerce between them (1n == 1 is true). === doesn't coerce, so different types → false.


Q48: Automatic Semicolon Insertion

javascriptfunction foo() {
  return
  {
    value: 42
  }
}
console.log(foo());

Output: undefined

Why: ASI inserts a semicolon after return, making it return;. The object literal is never reached. Always keep the opening brace on the same line as return.


Q49: void Operator

javascriptconsole.log(void 0);
console.log(void 'hello');
console.log(void (1 + 2));
console.log(typeof void 0);

Output: undefined, undefined, undefined, "undefined"

Why: void evaluates the expression but always returns undefined. void 0 is a reliable way to get undefined (can't be overridden in old JS).


Q50: Labeled Statements Confusion

javascriptconst result = (() => {
  foo: {
    console.log('A');
    break foo;
    console.log('B');
  }
  console.log('C');
  return 'done';
})();
console.log(result);

Output: A, C, "done"

Why: foo: is a label, not an object key. break foo exits the labeled block, skipping 'B', and continues to 'C'.


Q51: Chained Assignment Coercion

javascriptlet a = '10';
let b = a - 5;
let c = a + 5;
console.log(b, typeof b);
console.log(c, typeof c);

Output: 5 "number" then "105" "string"

Why: - forces numeric: '10' - 5 = 5. + prefers string: '10' + 5 = '105'.


Q52: Boolean Constructor vs Literal

javascriptconst a = new Boolean(false);
console.log(a == false);
console.log(!!a);
console.log(typeof a);

if (a) {
  console.log('truthy!');
}

Output: true, true, "object", "truthy!"

Why: new Boolean(false) creates an object wrapper. Objects are truthy, so if (a) is true! But a == false coerces the object to its primitive (false), so it equals false. Never use new Boolean().


Q53: Array.isArray vs typeof

javascriptconsole.log(typeof []);
console.log(typeof {});
console.log(Array.isArray([]));
console.log(Array.isArray({}));
console.log([] instanceof Array);
console.log([] instanceof Object);

Output: "object", "object", true, false, true, true

Why: typeof can't distinguish arrays from objects (both "object"). Use Array.isArray(). Arrays are instances of both Array and Object.


Q54: String Comparison Edge Cases

javascriptconsole.log('a' > 'A');
console.log('Z' > 'a');
console.log('banana' > 'cherry');
console.log('2' > '12');

Output: true, false, false, true

Why: String comparison uses Unicode code points. Lowercase letters have higher code points than uppercase. Lexicographic comparison goes char by char: '2'(50) > '1'(49).


Q55: Spread and Coercion

javascriptconsole.log([...'hello']);
console.log([...123]);     // TypeError!

Output: ["h","e","l","l","o"] then TypeError

Why: Spread works on iterables. Strings are iterable (char by char). Numbers are not iterable.


Q56: delete and undefined

javascriptconst obj = { a: 1, b: undefined };
console.log('a' in obj);
console.log('b' in obj);

delete obj.a;
console.log('a' in obj);
console.log(obj.a);
console.log(obj.b);

Output: true, true, false, undefined, undefined

Why: in checks if property exists — b exists even though its value is undefined. After delete obj.a, property is removed. Accessing deleted/non-existent property returns undefined, same as explicit undefined — but they're semantically different.


Q57: Map Keys vs Object Keys

javascriptconst map = new Map();
map.set(1, 'number');
map.set('1', 'string');
map.set(true, 'boolean');
console.log(map.size);

const obj = {};
obj[1] = 'number';
obj['1'] = 'string';
obj[true] = 'boolean';
console.log(Object.keys(obj).length);

Output: 3, 2

Why: Map preserves key types — 1, '1', and true are three different keys. Object coerces all keys to strings: 1→"1", true→"true". So obj["1"] is overwritten twice, and we have keys "1" and "true".


Q58: Ternary and Comma

javascriptconst x = (1, 2, 3);
console.log(x);

const y = true ? 1, 2 : 3; // SyntaxError!

Output: 3 then SyntaxError

Why: Comma operator evaluates left-to-right, returns last value. But it can't be used directly in ternary without parens: true ? (1, 2) : 3 would work and return 2.


Q59: String and Number Methods

javascriptconsole.log(1.toString()); // SyntaxError!
console.log(1..toString());
console.log((1).toString());
console.log(1 .toString());

Output: SyntaxError, "1", "1", "1"

Why: 1. is parsed as the number 1.0 — the parser expects more digits, not a method call. 1..toString() works because first dot is decimal point, second is property access. Parens or space also fix it.


Q60: Implicit Coercion in Switch

javascriptconst x = '1';

switch (x) {
  case 1:
    console.log('number');
    break;
  case '1':
    console.log('string');
    break;
}

switch (true) {
  case x == 1:
    console.log('loose');
    break;
  case x === 1:
    console.log('strict');
    break;
}

Output: "string" then "loose"

Why: switch uses === (strict equality) for matching. So '1' === 1 fails, '1' === '1' matches. In the second switch, true === (x == 1)true === true matches first.


Quick Reference: Coercion Rules

Expression Result Rule
+[] 0 [] → "" → 0
+{} NaN {} → "[object Object]" → NaN
+null 0 null → 0
+undefined NaN undefined → NaN
+'' 0 "" → 0
+'0' 0 "0" → 0
+true 1 true → 1
+false 0 false → 0
[] + [] "" Both → "", concat
[] + {} "[object Object]" "" + "[object Object]"
{} + [] 0 {} parsed as block, +[] = 0
null == undefined true Special rule
NaN == NaN false NaN ≠ anything
[] == false true [] → "" → 0 == 0
[] == ![] true ![] = false → 0; [] → 0
[prev·next]