← JavaScript core concepts for interviews

Lesson 1 of 5

Values, types and the traps of coercion

2 min read

JavaScript has primitive types (string, number, bigint, boolean, undefined, null, symbol) and objects (everything else: arrays, functions, dates, maps). Primitives are copied by value; objects are passed by reference.

js
let a = 5; let b = a; b++;          // a is still 5
const x = { n: 1 }; const y = x; y.n = 2; // x.n is now 2: same object

typeof quirks

js
typeof null        // "object"  (a historic bug)
typeof []          // "object"  → use Array.isArray([])
typeof function(){} // "function"
typeof NaN         // "number"

== vs ===

=== compares value and type with no conversion. == converts types first, using rules almost nobody remembers fully:

js
0 == ""          // true
"1" == 1         // true
null == undefined // true
null == 0        // false
[] == false      // true
NaN == NaN       // false (NaN is never equal to anything; use Number.isNaN)

Answer to give: "I always use ===. The only == I'd accept is x == null, which checks for both null and undefined."

Truthy and falsy

Falsy values: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is truthy, including "0", "false", [] and {}.

|| returns the first truthy value; ?? (nullish coalescing) returns the right side only for null or undefined:

js
const qty = 0;
qty || 10  // 10  (0 is falsy: probably a bug)
qty ?? 10  // 0
user?.address?.city // optional chaining: undefined instead of a crash

Numbers

All numbers are 64-bit floating point. 0.1 + 0.2 === 0.30000000000000004. For money, store integers (paise) or use a decimal library. Number.MAX_SAFE_INTEGER is 2^53 − 1; beyond that use BigInt.

var, let and const

ScopeHoistingRe-assignRe-declare
varfunctionhoisted, initialised to undefinedyesyes
letblockhoisted, but in the temporal dead zone until declaredyesno
constblocksame as letnono

const stops re-assignment, not mutation: const arr = []; arr.push(1) is fine.

js
console.log(a); // undefined (var is hoisted)
var a = 1;
console.log(b); // ReferenceError: cannot access 'b' before initialization
let b = 2;

Function declarations are hoisted completely (you can call them before they appear); function expressions and arrow functions assigned to let/const are not.

Check yourself

  • What does typeof null return and why is it surprising?
  • Give three falsy values that beginners think are truthy, and one truthy value they think is falsy.
  • When would || give the wrong default where ?? is right?

Sign up free to save your progress.