Data types
Seven primitives and everything else is an object. The interview value is in
the edges: what typeof lies about, how coercion decides, and why copying is
harder than it looks.
The primitives
string number boolean null undefined symbol bigint
Primitives are immutable and compared by value; everything else — objects, arrays, functions, dates, Map, Set — is compared by reference:
"abc" === "abc"; // true
[1] === [1]; // false — different objects
const a = { n: 1 }, b = a;
b.n = 2; a.n; // 2 — same objectnull versus undefined is the pair to state precisely: undefined means
“no value has been assigned”, null means “assigned, deliberately, to
nothing”. A missing property, an omitted argument and a function with no
return all give undefined. null only appears because someone wrote it.
typeof lies twice
typeof null; // "object" — a bug from 1995, unfixable
typeof []; // "object" — no distinct array type
typeof function () {}; // "function"
typeof NaN; // "number"
typeof 10n; // "bigint"So typeof is only reliable for primitives other than null. The correct
checks:
Array.isArray(x); // arrays
x === null; // null
Number.isNaN(x); // NaN, without coercion
Number.isInteger(x); // integers
Object.prototype.toString.call(x); // "[object Date]" etc.Number.isNaN over the global isNaN: the global coerces first, so
isNaN("abc") is true even though a string is not NaN.
NaN, the value that is not itself
NaN === NaN; // false — the only value unequal to itself
[NaN].includes(NaN); // true — SameValueZero
[NaN].indexOf(NaN); // -1 — strict equality
Object.is(NaN, NaN); // trueThree equality algorithms are in play. === says no, Object.is and
SameValueZero (used by includes, Map, Set) say yes. That is why
Data structures can dedupe NaN and indexOf cannot.
Object.is also separates 0 and -0, which === does not.
Falsy values — all eight
false 0 -0 0n "" null undefined NaN
Everything else is truthy, including [] and {}, which is the one that
catches people:
if ([]) { } // runs
[] == false; // true — coercion, a different questionThe practical consequence is that if (value) is wrong whenever 0 or ""
are legitimate values:
if (count) { } // skips a valid 0
if (count != null) { } // catches null AND undefined, nothing else
const n = input ?? 10; // only null/undefined fall through
const n = input || 10; // 0 and "" also fall through — usually a bug?? over || for defaults is the modern rule, and this is why.
== versus ===
=== compares type then value, no coercion. == coerces, and the rules are
worth knowing only so you can avoid them:
1 == "1"; // true — string to number
null == undefined; // true — and to nothing else
null == 0; // false — null is not coerced to a number
[] == false; // true — [] -> "" -> 0
"" == 0; // trueUse === always, with one accepted exception: x == null as a shorthand
for “null or undefined”, which is idiomatic and reads well.
Copying: shallow, deep, and the modern answer
// one level only
const shallow = { ...obj };
const shallow2 = Object.assign({}, obj); // same
// the built-in answer
const deep = structuredClone(obj);Spread copies the top level; nested objects are still shared, so mutating
copy.nested.x changes the original. structuredClone is the built-in deep
copy and it handles what JSON.parse(JSON.stringify(x)) cannot:
| JSON round-trip | structuredClone |
|
|---|---|---|
Date |
becomes a string | stays a Date |
Map / Set |
becomes {} |
preserved |
undefined values |
dropped | preserved |
| Cyclic references | throws | handled |
| Functions | dropped | throws |
The JSON trick is still everywhere in older code; naming structuredClone as
its replacement is a small currency signal.
Symbol and BigInt
const id = Symbol("id");
obj[id] = 1;
// [] — symbol keys are hidden from most APIs
Object.keys(obj);
// well-known symbols hook into the language
Symbol.iterator;A symbol is a guaranteed-unique key. Its real use is metadata that must not
collide with, or be seen by, ordinary property enumeration — and the well-known
symbols (Symbol.iterator, Symbol.asyncIterator) are how you make your own
type work with for...of and spread.
9007199254740993n; // exact; a Number cannot hold this
10n + 5n; // 15n
10n + 5; // TypeError — no mixingBigInt exists because Number is a float64 and loses integer precision past
Number.MAX_SAFE_INTEGER (2⁵³−1). Database ids and money in minor units are
the usual reasons — and note that JSON.stringify throws on a BigInt, so
serialising means converting to a string first.
var, let, const
| Scope | Hoisting | Reassign | |
|---|---|---|---|
var |
function | to undefined |
yes |
let |
block | temporal dead zone | yes |
const |
block | temporal dead zone | no |
const prevents rebinding, not mutation — const a = []; a.push(1) is
fine. Use const by default, let when you must reassign, and var never;
the loop-closure consequence is in Closures.
Related
Interview angle 7
- “What does
typeof nullreturn and why?” -"object", a bug from the first implementation that cannot be fixed without breaking the web.typeofis only reliable for primitives other thannull; useArray.isArray,x === nullandObject.prototype.toString.callfor the rest. - “
nullorundefined?” -undefinedmeans no value was assigned — a missing property, an omitted argument, a barereturn.nullis an assigned absence, so it only appears because someone wrote it. - “How do you check for NaN?” -
Number.isNaN, not the globalisNaN, which coerces first and so calls"abc"a NaN.NaN === NaNis false because it is the only value unequal to itself;Object.isandincludessay true because they use different equality algorithms. - “Name the falsy values.” -
false,0,-0,0n,"",null,undefined,NaN. Note that[]and{}are truthy, and thatif (value)is wrong whenever0or""are legitimate — which is the argument for??over||. - “
==or===?” -===always, withx == nullas the one accepted exception because it catches null and undefined and nothing else.==coercion rules are worth knowing only so you can avoid relying on them. - “How do you deep copy an object?” -
structuredClone. It preserves Dates, Maps, Sets, undefined values and cycles, all of which theJSON.parse(JSON.stringify())trick loses or throws on. It does throw on functions. - “When would you use BigInt?” - when integers exceed
Number.MAX_SAFE_INTEGER, which is 2⁵³−1 because Number is a float64. Database ids and money in minor units. You cannot mix BigInt and Number in arithmetic, andJSON.stringifythrows on one.