Type System
Primitive types, typeof, null vs undefined, NaN, value vs reference, wrapper objects, and immutability.
8 questions
JuniorTheoryVery commonWhat is the difference between null and undefined?
What is the difference between null and undefined?
undefined means a variable was declared but not assigned (the default); null is an explicit no value you assign deliberately. typeof undefined is undefined but typeof null is object. Numerically null becomes 0, undefined becomes NaN. They are loosely equal (==) but not strictly (===).
Common mistakes
- ✗Thinking
null === undefinedistrue(only==holds) - ✗Expecting
typeof nullto benullinstead ofobject - ✗Assuming both coerce to the same number —
nullis0,undefinedisNaN
Follow-up questions
- →Why does
null == undefinedhold butnull == 0does not? - →When would you deliberately assign
nullinstead of leaving a valueundefined?
JuniorTheoryCommonHow many primitive types does JavaScript have, and what makes a value primitive?
How many primitive types does JavaScript have, and what makes a value primitive?
JavaScript has 7 primitive types: string, number, boolean, null, undefined, bigint, and symbol. A primitive is an immutable value compared by value, with no properties or methods of its own — everything else is an object.
Common mistakes
- ✗Counting
object, arrays, or functions as primitive types - ✗Forgetting
bigintandsymbol, listing only the original five - ✗Assuming
nullis not a primitive becausetypeof nullisobject
Follow-up questions
- →Why does
typeof nullreturnobjectinstead ofnull? - →How can a primitive like a string expose methods such as
toUpperCaseif it has none?
JuniorTheoryCommonWhat does the typeof operator return for each kind of value?
What does the typeof operator return for each kind of value?
typeof returns a type string: string, number, boolean, undefined, bigint, symbol, function for callables, and object for objects, arrays, and null. The object result for null is a long-standing language bug.
Common mistakes
- ✗Expecting
typeof nullto benullrather thanobject - ✗Expecting
typeof []to bearray— it isobject(useArray.isArray) - ✗Thinking
typeofon an undeclared variable throws — it safely returnsundefined
Follow-up questions
- →How do you reliably distinguish an array from a plain object?
- →Why is
typeofsafe on an undeclared identifier when reading it directly throws?
MiddleTheoryCommonHow do primitives and objects differ when assigned or passed to a function?
How do primitives and objects differ when assigned or passed to a function?
Primitives are copied by value: assigning or passing one duplicates the value, so changes to the copy never affect the original. Objects copy a reference to the same object, so mutating it through either binding is visible to both. JavaScript is always pass-by-value — but for objects that value is a reference (pass by sharing).
Common mistakes
- ✗Believing reassigning an object parameter changes the caller's object
- ✗Thinking object assignment deep-copies rather than sharing a reference
- ✗Saying JavaScript is pass-by-reference for objects (it is pass-by-value of a reference)
Follow-up questions
- →Why does mutating an object parameter affect the caller but reassigning it does not?
- →How do you make an independent copy of an object or array?
MiddleTheoryOccasionalAre JavaScript values mutable, and what does const actually protect?
Are JavaScript values mutable, and what does const actually protect?
Primitives are immutable — you can never change a value like 5 or 'hi', only rebind the variable to a new one. Objects and arrays are mutable: their contents can change in place. const only blocks rebinding the variable, not mutation — a const object can still have its properties changed; use Object.freeze for shallow immutability.
Common mistakes
- ✗Thinking
constmakes an object's contents immutable - ✗Believing string methods mutate the original string in place
- ✗Assuming
Object.freezeis deep — it only freezes the top level
Follow-up questions
- →How would you make a nested object deeply immutable?
- →Why does
const arr = []; arr.push(1)succeed whilearr = [1]throws?
MiddleTheoryOccasionalWhat is NaN, and how do you reliably test whether a value is NaN?
What is NaN, and how do you reliably test whether a value is NaN?
NaN (Not-a-Number) is a numeric value for an undefined math result like 0/0 or Number('x'); typeof NaN is number. It is the only value not equal to itself, so NaN === NaN is false. Test with Number.isNaN(x), which checks without coercion — unlike global isNaN, which coerces its argument to a number first.
Common mistakes
- ✗Testing with
x === NaN, which is alwaysfalse - ✗Assuming
isNaNandNumber.isNaNbehave identically — only the global one coerces - ✗Expecting
typeof NaNto be something other thannumber
Follow-up questions
- →Why does
isNaN('hello')returntruebutNumber.isNaN('hello')returnfalse? - →How could you check for
NaNwithout any built-in helper function?
MiddleTheoryOccasionalHow can a primitive like a string expose methods if a primitive has none?
How can a primitive like a string expose methods if a primitive has none?
When you access a property or method on a primitive ('hi'.toUpperCase()), the engine temporarily wraps it in the matching wrapper object — String, Number, Boolean, Symbol, or BigInt — runs the method, then discards the wrapper. null and undefined have no wrapper, so property access on them throws a TypeError.
Common mistakes
- ✗Thinking primitives store their own methods rather than borrowing from a wrapper
- ✗Believing the temporary wrapper persists and changes the value's type
- ✗Expecting
nullorundefinedto be wrapped — property access on them throws
Follow-up questions
- →Why does assigning a property to a primitive (
s.foo = 1) silently fail? - →What is the difference between
'hi'andnew String('hi')?
SeniorTheoryOccasionalHow does === compare objects, and how does Object.is differ from it?
How does === compare objects, and how does Object.is differ from it?
=== on objects compares identity, not contents — two distinct objects are never equal even with identical properties; only two references to the same object are. Object.is matches === except in two cases: Object.is(NaN, NaN) is true and Object.is(+0, -0) is false. Array.prototype.includes uses a related SameValueZero that also treats NaN as equal.
Common mistakes
- ✗Expecting two objects with equal contents to be
===(only same-reference is) - ✗Thinking
Object.isbehaves identically to===forNaNand signed zeros - ✗Assuming
===deep-compares object properties
Follow-up questions
- →How would you compare two objects by value (structural equality)?
- →Why does
[NaN].includes(NaN)returntruebut[NaN].indexOf(NaN)return-1?