JavaScript Interview Prep
Prototypes & Inheritance

instanceof & typeof

Type Checking Done Right

LinkedIn Hook

typeof null === "object".

That's a 30-year-old bug shipped in the very first version of JavaScript in 1995. A fix was proposed for ES6 and REJECTED because too much production code depends on the broken behavior.

And that's just the start of JavaScript's type-checking weirdness:

  • typeof [] is "object" (arrays are objects)
  • "hello" instanceof String is false (primitives aren't instances)
  • An array from an iframe is NOT instanceof Array in the parent window

So how do you actually check types safely? You combine three tools: typeof for primitives, instanceof for walking the prototype chain, and Object.prototype.toString.call(value) for the one reliable answer that works everywhere.

In this lesson you'll learn exactly how instanceof walks the chain, why typeof null will never be fixed, how to write a polyfill for instanceof, and how Symbol.hasInstance lets you customize what "being an instance" even means.

Read the full lesson -> [link]

#JavaScript #InterviewPrep #TypeChecking #Prototypes #Frontend #CodingInterview #WebDevelopment


instanceof & typeof thumbnail


What You'll Learn

  • Why typeof null === "object" and why that bug will never be fixed
  • How instanceof walks the prototype chain to determine type, and how to polyfill it
  • Reliable type-check patterns using Array.isArray, Symbol.hasInstance, and Object.prototype.toString.call

typeof — Simple but Quirky

typeof returns a string indicating the type of the operand. It's straightforward for most values, but has some famous quirks.

// The normal cases:
console.log(typeof "hello");     // "string"
console.log(typeof 42);          // "number"
console.log(typeof true);        // "boolean"
console.log(typeof undefined);   // "undefined"
console.log(typeof Symbol());    // "symbol"
console.log(typeof BigInt(42));  // "bigint"
console.log(typeof function(){}); // "function"
console.log(typeof {});          // "object"
console.log(typeof []);          // "object" (arrays are objects)

// THE FAMOUS BUG:
console.log(typeof null);        // "object" <- THIS IS A BUG!

Why typeof null === "object" — The 30-Year-Old Bug

In the very first implementation of JavaScript (1995), values were stored as a type tag + value. Objects had type tag 0. null was represented as the NULL pointer (0x00). So when typeof checked the type tag of null, it saw 0 and returned "object".

This was a bug in the original engine. A fix was proposed for ES6 (typeof null === "null"), but it was rejected because too much existing code depended on the buggy behavior. It will never be fixed.

// Safe null check (typeof won't work!):
function isObject(val) {
  return val !== null && typeof val === "object";
}

console.log(isObject({}));   // true
console.log(isObject(null)); // false (correctly excluded)
console.log(isObject([]));   // true (arrays are objects)

instanceof — Walking the Prototype Chain

instanceof checks whether an object has a specific constructor's .prototype anywhere in its prototype chain.

function Animal(name) {
  this.name = name;
}

function Dog(name) {
  Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const rex = new Dog("Rex");

// instanceof walks the chain:
console.log(rex instanceof Dog);    // true  (Dog.prototype is in rex's chain)
console.log(rex instanceof Animal); // true  (Animal.prototype is in rex's chain)
console.log(rex instanceof Object); // true  (Object.prototype is in rex's chain)

// How it works internally:
// rex.__proto__ === Dog.prototype? YES -> true
// rex.__proto__.__proto__ === Animal.prototype? YES -> true
// rex.__proto__.__proto__.__proto__ === Object.prototype? YES -> true

How instanceof Actually Works

// instanceof essentially does this:
function myInstanceof(obj, Constructor) {
  let proto = Object.getPrototypeOf(obj);

  while (proto !== null) {
    if (proto === Constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }

  return false;
}

// Test it:
console.log(myInstanceof(rex, Dog));    // true
console.log(myInstanceof(rex, Animal)); // true
console.log(myInstanceof(rex, Array));  // false
console.log(myInstanceof([], Array));   // true

Polyfill for instanceof

A complete, interview-ready polyfill:

function polyfillInstanceof(obj, Constructor) {
  // Handle edge cases
  if (obj === null || obj === undefined) return false;
  if (typeof obj !== "object" && typeof obj !== "function") return false;
  if (typeof Constructor !== "function") {
    throw new TypeError("Right-hand side of instanceof is not callable");
  }

  // Check for Symbol.hasInstance first (ES6+)
  if (typeof Constructor[Symbol.hasInstance] === "function") {
    return Constructor[Symbol.hasInstance](obj);
  }

  // Walk the prototype chain
  let proto = Object.getPrototypeOf(obj);
  while (proto !== null) {
    if (proto === Constructor.prototype) {
      return true;
    }
    proto = Object.getPrototypeOf(proto);
  }
  return false;
}

Symbol.hasInstance — Custom instanceof Behavior

ES6 lets you override what instanceof does with Symbol.hasInstance:

class EvenNumber {
  static [Symbol.hasInstance](instance) {
    return typeof instance === "number" && instance % 2 === 0;
  }
}

console.log(2 instanceof EvenNumber);  // true
console.log(3 instanceof EvenNumber);  // false
console.log(4 instanceof EvenNumber);  // true
console.log("4" instanceof EvenNumber); // false (not a number)

// Practical example: type-checking validator
class Validator {
  static [Symbol.hasInstance](instance) {
    return instance !== null && instance !== undefined && typeof instance.validate === "function";
  }
}

const form = {
  validate() { return true; }
};

console.log(form instanceof Validator); // true (has validate method)
console.log({} instanceof Validator);   // false

instanceof Edge Cases and Gotchas

// 1. Primitives always return false
console.log("hello" instanceof String);  // false (it's a primitive, not an object)
console.log(42 instanceof Number);       // false
console.log(true instanceof Boolean);    // false

// 2. Wrapper objects DO work
console.log(new String("hello") instanceof String); // true
console.log(new Number(42) instanceof Number);       // true

// 3. Arrays
console.log([] instanceof Array);   // true
console.log([] instanceof Object);  // true (Array inherits from Object)

// 4. Cross-frame/realm issues
// If you create an array in an iframe, it won't be instanceof Array
// in the parent window (different Array constructors!)
// Solution: Use Array.isArray()
console.log(Array.isArray([])); // true (works across realms)

Practical Type Checking Patterns

// The robust type checker:
function getType(value) {
  if (value === null) return "null";
  if (value === undefined) return "undefined";
  if (Array.isArray(value)) return "array";
  if (value instanceof RegExp) return "regexp";
  if (value instanceof Date) return "date";
  if (value instanceof Error) return "error";
  if (value instanceof Promise) return "promise";
  return typeof value; // "string", "number", "boolean", "function", "object", etc.
}

console.log(getType(null));           // "null"
console.log(getType([]));             // "array"
console.log(getType({}));             // "object"
console.log(getType(new Date()));     // "date"
console.log(getType(/regex/));        // "regexp"
console.log(getType(Promise.resolve())); // "promise"

// The Object.prototype.toString trick (most reliable):
function preciseType(value) {
  return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

console.log(preciseType(null));       // "null"
console.log(preciseType([]));         // "array"
console.log(preciseType({}));         // "object"
console.log(preciseType(new Date())); // "date"
console.log(preciseType(/regex/));    // "regexp"
console.log(preciseType(42));         // "number"

instanceof & typeof visual 1


Common Mistakes

  • Using typeof val === "object" to check for objects without excluding null — you'll treat null as an object. Always guard with val !== null.
  • Using arr instanceof Array in code that may receive arrays from iframes, workers, or other realms — different realms have different Array constructors. Use Array.isArray(arr) instead.
  • Expecting "hello" instanceof String to return true — primitives are never instances. Only boxed objects like new String("hello") are.

Interview Questions

Q: Why does typeof null return "object"?

It's a bug from the original JavaScript implementation. Values were stored with type tags, and null was represented as a NULL pointer (0x00). Since objects had type tag 0, typeof mistakenly identified null as an object. This bug was never fixed because too much code depends on it.

Q: How does instanceof work internally?

instanceof checks if the constructor's .prototype exists anywhere in the object's prototype chain. It walks up the chain via __proto__ links, comparing each prototype with Constructor.prototype. Returns true if found, false if it reaches null.

Q: Why doesn't "hello" instanceof String return true?

Because "hello" is a primitive, not an object. instanceof only works on objects. The primitive string is auto-boxed temporarily for method calls, but it's not actually an instance of String. Use typeof for primitive checks and instanceof for object checks.

Q: What is Symbol.hasInstance?

It's a well-known Symbol that lets you customize the behavior of instanceof. By defining a static [Symbol.hasInstance] method on a class, you can control what instanceof returns for that class.


Quick Reference — Cheat Sheet

TYPE CHECKING — QUICK MAP

typeof
  - checks the internal type tag
  - returns "string" | "number" | "boolean" | "undefined"
          | "symbol" | "bigint" | "function" | "object"
  - typeof null === "object"   (BUG, never fixed)
  - typeof []   === "object"   (arrays are objects)

instanceof
  - walks the __proto__ chain
  - obj instanceof Fn
      -> is Fn.prototype anywhere in obj's chain?
  - primitives always return false
  - breaks across realms/iframes (different constructors)

Reliable patterns
  Array.isArray(val)                             // cross-realm safe
  Object.prototype.toString.call(val)            // "[object Date]" etc.
  val !== null && typeof val === "object"        // true object check

Customize instanceof
  class C { static [Symbol.hasInstance](v){ ... } }

Previous: ES6 Classes (Syntactic Sugar) Next: First-Class Functions


This is Lesson 5.5 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page