Why is typeof null === "object" in JavaScript?

Why is typeof null === "object" in JavaScript?

Let's learn everything about typeof in JavaScript.


So, What does the typeof operator do?

The typeof operator is a unary operator (i.e, an operator with single operand), that returns a string indicating the type of the operand's value.

console.log(typeof 42); // "number"
console.log(typeof "blubber"); // "string"
console.log(typeof true); // "boolean"

The following table summarizes the possible return values of typeof.

TypeResult
undefined"undefined"
Null"object"
Boolean"boolean"
Number"number"
BigInt"bigint"
String"string"
Symbol"symbol"
Function"function"
Any other object"object"

typeof null === object is a bug?

Yes, this is actually a bug and one that unfortunately can’t be fixed because it would break the existing code.

In the first version of Javascript, values were stored in 32- bit units, which consisted of a small type tag (1–3 bits) and the actual data of the value. The type tags were stored in the lower bits of the units. There were five of them:

  • 000: object. The data is a reference to an object.

  • 1: int. The data is a 31-bit signed integer.

  • 010: double. The data is a reference to a double floating point number.

  • 100: string. The data is a reference to a string.

  • 110: boolean. The data is a boolean.

That is, the lowest bit was either one, then the type tag was only one bit long. Or it was zero, then the type tag was three bits in length, providing two additional bits, for four types.

Two values were special:

  • undefined (JSVAL_VOID) was the integer −230 (a number outside the integer range).

  • null (JSVAL_NULL) was the machine code NULL pointer. Or: an object type tag plus a reference that is zero.

It should now be obvious why typeof thought that null was an object: it examined its type tag and the type tag said “object”. A fix was proposed for ECMAScript (via an opt-in), but was rejected. It would have resulted in typeof null === "null".

console.log(typeof null); // "object"

With new operator

All constructor functions called with the "new" will return non-primitives (object or function).

const str = new String("String");
const num = new Number(100);

typeof str; // "object"
typeof num; // "object"

const func = new Function();

typeof func; // "function"

Need for parentheses in syntax

The typeof operator has higher precedence than binary operators like addition (+). Therefore, parentheses are needed to evaluate the type of an addition result.

// Parentheses can be used for determining the data type of expressions.
const someData = 99;

typeof someData + " Wisen"; // "number Wisen"

typeof (someData + " Wisen"); // "string"

With undeclared and uninitialized variables

Even with undeclared identifiers, typeof will return "undefined" instead of throwing an error.

However, using typeof on lexical declarations (let const, and class) in the same block before the line of declaration will throw a ReferenceError. Block scoped variables are in a temporal dead zone from the start of the block until the initialization is processed, during which it will throw an error if accessed.

typeof newLetVariable; // ReferenceError
typeof newConstVariable; // ReferenceError
typeof newClass; // ReferenceError

let newLetVariable;
const newConstVariable = "hello";
class newClass {}

Exceptional Behaviour of document.all

typeof document.all === "undefined";

The case of document.all having type "undefined" is classified in the web standards as a "willful violation" of the original ECMAScript standard for web compatibility.


Interesting examples of using typeof

// Numbers
typeof 37 === "number";
typeof 3.14 === "number";
typeof Math.LN2 === "number";
typeof Infinity === "number";
typeof NaN === "number"; // Despite being "Not-A-Number"
typeof Number("1") === "number"; // Number tries to parse things into numbers
typeof Number("shoe") === "number"; // including values that cannot be type coerced to a number

typeof 42n === "bigint";

// Strings
typeof "" === "string";
typeof "bla" === "string";
typeof `template literal` === "string";
typeof "1" === "string"; // note that a number within a string is still typeof string
typeof typeof 1 === "string"; // typeof always returns a string
typeof String(1) === "string"; // String converts anything into a string, safer than toString

// Booleans
typeof true === "boolean";
typeof false === "boolean";
typeof Boolean(1) === "boolean"; // Boolean() will convert values based on if they're truthy or falsy
typeof !!1 === "boolean"; // two calls of the ! (logical NOT) operator are equivalent to Boolean()

// Symbols
typeof Symbol() === "symbol";
typeof Symbol("foo") === "symbol";
typeof Symbol.iterator === "symbol";

// Undefined
typeof undefined === "undefined";
typeof declaredButUndefinedVariable === "undefined";
typeof undeclaredVariable === "undefined";

// Objects
typeof { a: 1 } === "object";

// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays
typeof [1, 2, 4] === "object";

typeof new Date() === "object";
typeof /regex/ === "object";

// The following are confusing, dangerous, and wasteful. Avoid them.
typeof new Boolean(true) === "object";
typeof new Number(1) === "object";
typeof new String("abc") === "object";

// Functions
typeof function () {} === "function";
typeof class C {} === "function";
typeof Math.sin === "function";