JAVASCRIPT / VALUES, TYPES, AND COERCION
Checking types with typeof
Predict what typeof returns for every kind of JavaScript value, and recognise the cases where its answer is too coarse or too forgiving to trust.
What you will learn
- Predict typeof for numbers, strings, symbols, bigints, functions, arrays, and null
- Compare against the exact lowercase strings typeof returns, never 'array' or 'Number'
- Follow a typeof 'object' result with a === null check or Array.isArray
- Probe optional globals with typeof, avoiding a ReferenceError on undeclared names
Understanding Checking types with typeof
typeof is a prefix operator, not a function; the parentheses people habitually write around its operand are just grouping. It always hands back a string drawn from a closed list of eight: 'undefined', 'boolean', 'number', 'bigint', 'string', 'symbol', 'object', and 'function'. Because that list is fixed by the language, typeof can never report a type you defined yourself: a Map, a Date, a RegExp, and an instance of your own class all come back as 'object'.
The mental model that makes the results predictable is that typeof reports where a value sits in the primitive-versus-object split. Every primitive type gets its own string, every object collapses to 'object', and the single exception is that callable objects report 'function', because the language considers callability worth surfacing. typeof null returning 'object' is a leftover from the very first implementation, where null shared an internal tag with object references, and it was never fixed because too much existing code relies on it. So read 'object' as 'not a primitive, keep looking' rather than as an identification.
typeof has one further oddity: it is the only operator that tolerates a name that does not exist. The specification evaluates its operand as a reference and, if that reference cannot be resolved, short-circuits to 'undefined' instead of reading a value, which is why guards like typeof fetch === 'function' can safely probe for an optional global. The exemption is narrow, though: it applies to a bare identifier with no binding at all, so a let or const declared later in the same scope still throws, because the binding exists and is merely uninitialised.
console.log(typeof 42);
console.log(typeof '42');
console.log(typeof true);
console.log(typeof undefined);
console.log(typeof null);
console.log(typeof Symbol('id'));
console.log(typeof 42n);
console.log(typeof [1, 2, 3]);
console.log(typeof function () {});
console.log(typeof notDeclaredAnywhere);typeof names each primitive type exactly but flattens every object to 'object', with 'function' as the only exception, so it is precise about primitives and nearly blind to object kinds.
Worked examples
Getting past 'object'
Shows the two checks you have to add around typeof before an 'object' result is usable.
function kindOf(value) {
if (value === null) return 'null';
if (Array.isArray(value)) return 'array';
return typeof value;
}
console.log(kindOf(null));
console.log(kindOf([1, 2]));
console.log(kindOf(new Date(0)));
console.log(kindOf(kindOf));
console.log(kindOf(7));Example explained
Line 1The null test comes first because typeof null would otherwise report 'object' and hide it.
Line 2Array.isArray is needed because an array is an ordinary object to typeof, so it also reports 'object'.
Line 3new Date(0) still ends up as plain 'object': typeof knows nothing about built-in object kinds.
Line 4Passing kindOf itself returns 'function', the one non-primitive typeof singles out.
Where typeof still throws
Demonstrates that typeof's tolerance for unknown names does not extend to a let binding in its temporal dead zone.
console.log(typeof neverDeclared);
try {
console.log(typeof later);
} catch (err) {
console.log(err.name);
}
let later = 1;
console.log(typeof later);Example explained
Line 1neverDeclared has no binding anywhere, so typeof resolves nothing and yields the string 'undefined'.
Line 2later does have a binding, hoisted to the top of the scope but uninitialised, so reading it throws.
Line 3err.name prints ReferenceError, proving typeof is not a general 'is this safe to touch' test.
Line 4Once the let statement has run the binding holds 1, and typeof reports 'number'.
How tightly typeof binds
Shows why typeof needs parentheses around an expression but not around a comparison.
const n = 5;
console.log(typeof n + ' values');
console.log(typeof (n + ' values'));
console.log(typeof typeof n);
console.log(typeof n === 'number');Example explained
Line 1typeof binds tighter than +, so line 2 produces 'number' first and then concatenates ' values'.
Line 2Parentheses on line 3 force the concatenation to happen first, so typeof inspects the string '5 values'.
Line 3typeof typeof n is always 'string', because the inner operator has already produced a string.
Line 4=== binds looser than typeof, so the usual comparison idiom needs no parentheses at all.
Important notes
The undeclared-name exemption covers a lone identifier only; typeof config.debug still throws if config itself is undeclared.
typeof class {} is 'function' and typeof new String('x') is 'object', so typeof describes the value in front of you, not what it represents.
Common mistakes
Testing typeof list === 'array': arrays report 'object', so the condition is never true and the array-handling branch is silently skipped.
Reading a property once typeof value === 'object' passes: null takes that branch too, and you get TypeError: Cannot read properties of null.
Comparing against 'Number' or 'Undefined': typeof returns lowercase strings only, so the guard is permanently false and does nothing.
Try it yourself
Change, predict, then run
In a browser console, build an array holding one value for each of the eight strings typeof can return, then log values.map(v => typeof v) and confirm you get eight distinct entries with no repeats.
Open the JavaScript workspaceCheck your understanding
A helper is written as: if (typeof value === 'object') { return value.length; } else { return 0; } Which single call makes it throw a TypeError?
- f([1, 2, 3])
- f(null)
- f('abc')
- f(undefined)
Show answer
typeof null is 'object', so null is the one argument that enters the branch, and reading .length on null throws. 'abc' is tempting because it does have a length, but typeof gives 'string', so it takes the else branch and returns 0; the array enters the branch and safely returns 3.