Episode 1 — Fundamentals / 1.18 — Operators and Type System
1.18.e — The typeof Operator and Type Checking
In one sentence: The
typeofoperator returns a string naming a value's type, but its results include the infamoustypeof null === "object"bug and the inability to distinguish arrays from objects — so production code often combinestypeofwithinstanceof,Array.isArray(), and other patterns for reliable type checking.
Navigation: ← 1.18.d — Assignment Operators · 1.18.f — Truthy and Falsy Values →
1. typeof syntax
typeof is a unary operator (not a function). Parentheses are optional but common:
typeof 42 // "number"
typeof(42) // "number" (same thing — parens just group the operand)
2. typeof return values for all types
| Expression | Result | Notes |
|---|---|---|
typeof undefined | "undefined" | |
typeof true | "boolean" | |
typeof 42 | "number" | Includes NaN and Infinity |
typeof 42n | "bigint" | ES2020 BigInt |
typeof "hello" | "string" | |
typeof Symbol() | "symbol" | ES2015 Symbol |
typeof null | "object" | BUG (see below) |
typeof {} | "object" | |
typeof [] | "object" | Arrays are objects |
typeof function(){} | "function" | Special case |
typeof class{} | "function" | Classes are functions |
console.log(typeof undefined); // "undefined"
console.log(typeof true); // "boolean"
console.log(typeof 42); // "number"
console.log(typeof "hello"); // "string"
console.log(typeof Symbol("id")); // "symbol"
console.log(typeof 10n); // "bigint"
console.log(typeof null); // "object" (!)
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (!)
console.log(typeof function(){}); // "function"
console.log(typeof NaN); // "number" (!)
console.log(typeof Infinity); // "number"
3. The typeof null === "object" bug
This is the most famous bug in JavaScript. It has existed since the language's creation in 1995 and will never be fixed because too much existing code depends on it.
Historical explanation
In the original JavaScript engine, values were stored as a type tag + value. Objects had a type tag of 0. null was represented as the NULL pointer (0x00), which also had a type tag of 0. So typeof null checked the tag, saw 0, and returned "object".
How to properly check for null
const value = null;
// Wrong: typeof says "object"
console.log(typeof value === "object"); // true (misleading!)
// Correct: explicit null check
console.log(value === null); // true
// Robust pattern: check for "real" objects
function isObject(val) {
return val !== null && typeof val === "object";
}
console.log(isObject({})); // true
console.log(isObject([])); // true (arrays are objects)
console.log(isObject(null)); // false (correctly excluded)
console.log(isObject("hello")); // false
4. typeof for undeclared variables
Unlike most operators, typeof does not throw a ReferenceError for variables that have never been declared:
// This would throw: console.log(myVar);
// ReferenceError: myVar is not defined
// But typeof is safe:
console.log(typeof myVar); // "undefined" (no error!)
Feature detection pattern
This is genuinely useful for checking if a global feature exists:
// Check if running in a browser
if (typeof window !== "undefined") {
console.log("Running in a browser");
}
// Check if running in Node.js
if (typeof process !== "undefined" && process.versions?.node) {
console.log("Running in Node.js");
}
// Check if a library is loaded
if (typeof jQuery !== "undefined") {
console.log("jQuery is available");
}
// Check for modern API support
if (typeof IntersectionObserver !== "undefined") {
// Use IntersectionObserver
} else {
// Fallback
}
Note: With let/const, accessing a variable in its temporal dead zone (TDZ) still throws, even with typeof:
// typeof tdzVar; // ReferenceError (if tdzVar is declared with let/const below)
// let tdzVar = 5;
However, truly undeclared variables (never declared anywhere) are safe with typeof.
5. typeof for functions
Functions get the special return value "function" even though they are technically objects:
console.log(typeof function() {}); // "function"
console.log(typeof (() => {})); // "function"
console.log(typeof class MyClass {}); // "function"
console.log(typeof Math.max); // "function"
console.log(typeof console.log); // "function"
console.log(typeof Array.isArray); // "function"
This is very useful for callback validation:
function executeCallback(callback) {
if (typeof callback === "function") {
callback();
} else {
console.warn("Expected a function, got:", typeof callback);
}
}
executeCallback(() => console.log("Hello!")); // "Hello!"
executeCallback("not a function"); // warning
6. instanceof for objects and constructors
While typeof tells you the primitive type, instanceof checks if an object is an instance of a specific constructor (checks the prototype chain).
console.log([] instanceof Array); // true
console.log([] instanceof Object); // true (Array extends Object)
console.log({} instanceof Object); // true
console.log(new Date() instanceof Date); // true
console.log(new Map() instanceof Map); // true
console.log(/regex/ instanceof RegExp); // true
// Primitives are NOT instances
console.log("hello" instanceof String); // false (primitive, not String object)
console.log(42 instanceof Number); // false
console.log(true instanceof Boolean); // false
How instanceof works
instanceof walks the prototype chain of the left operand and checks if the right operand's .prototype appears anywhere in that chain:
class Animal {}
class Dog extends Animal {}
const rex = new Dog();
console.log(rex instanceof Dog); // true
console.log(rex instanceof Animal); // true (Animal is in the chain)
console.log(rex instanceof Object); // true (Object is always at the top)
Limitations of instanceof
// 1. Does not work across frames/iframes (different global objects)
// An array from iframe1 is NOT instanceof Array in the parent window
// 2. Does not work with primitives
console.log("hello" instanceof String); // false
// 3. Can be fooled by prototype manipulation
const fake = Object.create(Array.prototype);
console.log(fake instanceof Array); // true (but it's not a real array)
7. Array.isArray() — why typeof fails for arrays
const arr = [1, 2, 3];
// typeof fails — arrays are "object"
console.log(typeof arr); // "object"
console.log(typeof arr === "object"); // true (but so are plain objects!)
// instanceof works but has cross-frame issues
console.log(arr instanceof Array); // true
// Array.isArray() — the BEST way
console.log(Array.isArray(arr)); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray("hello")); // false
console.log(Array.isArray(new Array())); // true
console.log(Array.isArray(Array.prototype)); // true (it is actually an array)
Why Array.isArray() is preferred
| Method | Cross-frame safe? | Handles edge cases? |
|---|---|---|
typeof arr === "object" | Yes | No — objects also match |
arr instanceof Array | No | Mostly |
Array.isArray(arr) | Yes | Yes |
Rule: Always use Array.isArray() to check for arrays.
8. Type-checking patterns in production code
Pattern 1: Comprehensive type checker
function getType(value) {
if (value === null) return "null";
if (Array.isArray(value)) return "array";
return typeof value;
}
console.log(getType(42)); // "number"
console.log(getType("hello")); // "string"
console.log(getType(null)); // "null"
console.log(getType([1, 2])); // "array"
console.log(getType({})); // "object"
console.log(getType(undefined)); // "undefined"
console.log(getType(() => {})); // "function"
Pattern 2: Using Object.prototype.toString
The most robust type-checking technique uses the internal [[Class]] tag:
function toRawType(value) {
return Object.prototype.toString.call(value).slice(8, -1);
}
console.log(toRawType(42)); // "Number"
console.log(toRawType("hello")); // "String"
console.log(toRawType(true)); // "Boolean"
console.log(toRawType(null)); // "Null"
console.log(toRawType(undefined)); // "Undefined"
console.log(toRawType([])); // "Array"
console.log(toRawType({})); // "Object"
console.log(toRawType(new Date())); // "Date"
console.log(toRawType(/regex/)); // "RegExp"
console.log(toRawType(new Map())); // "Map"
console.log(toRawType(new Set())); // "Set"
console.log(toRawType(() => {})); // "Function"
Pattern 3: Validation helpers
const is = {
string: (v) => typeof v === "string",
number: (v) => typeof v === "number" && !Number.isNaN(v),
boolean: (v) => typeof v === "boolean",
fn: (v) => typeof v === "function",
array: (v) => Array.isArray(v),
object: (v) => v !== null && typeof v === "object" && !Array.isArray(v),
nil: (v) => v === null || v === undefined,
defined: (v) => v !== undefined,
};
console.log(is.number(42)); // true
console.log(is.number(NaN)); // false (NaN excluded)
console.log(is.object({})); // true
console.log(is.object([])); // false (array excluded)
console.log(is.object(null)); // false (null excluded)
console.log(is.nil(null)); // true
console.log(is.nil(undefined)); // true
console.log(is.nil(0)); // false
Pattern 4: Defensive function parameters
function processData(data, options = {}) {
if (!Array.isArray(data)) {
throw new TypeError(`Expected array, got ${typeof data}`);
}
if (typeof options !== "object" || options === null) {
throw new TypeError(`Expected object for options, got ${typeof options}`);
}
if (options.callback && typeof options.callback !== "function") {
throw new TypeError("callback must be a function");
}
// Safe to proceed
const results = data.map(item => item * 2);
if (typeof options.callback === "function") {
options.callback(results);
}
return results;
}
9. Quick reference: choosing the right check
| Question | Best check |
|---|---|
| Is it a string? | typeof x === "string" |
| Is it a number (not NaN)? | typeof x === "number" && !Number.isNaN(x) |
| Is it a boolean? | typeof x === "boolean" |
| Is it undefined? | typeof x === "undefined" or x === undefined |
| Is it null? | x === null |
| Is it null or undefined? | x == null |
| Is it an array? | Array.isArray(x) |
| Is it a plain object? | x !== null && typeof x === "object" && !Array.isArray(x) |
| Is it a function? | typeof x === "function" |
| Is it a Date? | x instanceof Date |
| Is it a RegExp? | x instanceof RegExp |
| Is it a Map/Set? | x instanceof Map / x instanceof Set |
| What is it exactly? | Object.prototype.toString.call(x) |
Key takeaways
typeofreturns a string ("number","string","boolean","undefined","object","function","symbol","bigint").typeof null === "object"is a historical bug that will never be fixed.typeofon undeclared variables returns"undefined"without throwing — useful for feature detection.typeofcannot distinguish arrays from objects — useArray.isArray().instanceofchecks the prototype chain but does not work across frames or with primitives.Object.prototype.toString.call(value)is the most robust type-checking mechanism.- In production, combine multiple checks into helper functions for clean, reliable type validation.
Explain-It Challenge
Explain without notes:
- Why does
typeof nullreturn"object"? Is this intentional? - How is
typeofuseful for checking if a global variable exists? - Why does
typeof []return"object", and what should you use instead to check for arrays? - What is the difference between
typeof x === "function"andx instanceof Function? - Write a function that returns the accurate type name for any value, including
"null"and"array".
Navigation: ← 1.18.d — Assignment Operators · 1.18.f — Truthy and Falsy Values →