instanceof narrowing

Narrows a value to a class type by testing its prototype chain at runtime.

Signature
value instanceof Constructor
  • Added in TypeScript 1.4

Parameters

ParameterTypeDescription
$valueunknownThe expression being tested. Primitives always yield false, including a string tested against String.
$ConstructorFunctionAny value with a prototype property, normally a class or constructor function. Narrows to its instance type, or to the type declared by a `[Symbol.hasInstance]` predicate if one exists.

Return value

booleanA boolean. True when Constructor.prototype appears anywhere in value's prototype chain; the true branch narrows value to the constructor's instance type.

Errors

TypeError at runtime if the right operand is not callable. tsc errors if the right operand is not of type `any` or assignable to `Function`.

Changelog

  • PHP 1.4instanceof type guards introduced alongside typeof.
  • PHP 4.4An instanceof check stored in a const is tracked by control flow analysis.
  • PHP 5.3A `[Symbol.hasInstance]` method declared with a type predicate return type is honoured by instanceof narrowing.

instanceof narrows by walking the prototype chain, so it is the right tool for anything created with new: your own classes, built-ins like Date, Map and RegExp, and error hierarchies.

When to reach for it

  • A catch block, where the caught value is unknown and you want to separate your HttpError from a stray TypeError from a thrown string.
  • Distinguishing a Date from an ISO string in a parameter that accepts both.
  • Any union whose members are classes rather than object literal types.

What trips people up

It does not work on plain object shapes. A type Fish = { swim(): void } has no constructor, so there is nothing to test against — reach for a type predicate or the in operator instead. This is the single most common reason people ask why instanceof "does not work with interfaces".

It also fails across realms: an object from an iframe or a worker has a different Array, so x instanceof Array is false while Array.isArray(x) is true.

Subclassing Error when targeting ES5 breaks the chain unless you call Object.setPrototypeOf(this, new.target.prototype) in the constructor.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Separating your own error class from unexpected throws inside catch (e: unknown)
  • Accepting a Date object or an ISO string and normalising both to a timestamp
  • Walking a union of AST node classes without adding a discriminant field

Frequently asked questions

Why doesn't instanceof work with an interface?

Interfaces and type aliases are erased at compile time, so there is no constructor to test against at runtime. Use a type predicate function or the `in` operator to narrow plain object shapes.

Why is x instanceof Array false for an array?

Almost always a realm boundary — an array created in an iframe, worker, or another Node vm context has a different Array constructor. Use Array.isArray, which checks the internal slot instead of the prototype chain.

How do I narrow the error in a catch block?

The caught value is typed unknown under useUnknownInCatchVariables, so test it: `if (e instanceof Error)` gives you `.message`, and a check against your own subclass first gives you its extra fields.

More examples

Classes narrow with instanceof; plain shapes need a predicate

Prints 3.1416, 9, then true false. The Circle/Square union narrows with instanceof because both are classes; Point cannot, because `type Point` is erased at compile time and leaves nothing to test against at runtime.

Example · typescript
class Circle { constructor(public radius: number) {} }
class Square { constructor(public side: number) {} }

// A plain object shape — no constructor, so instanceof is impossible.
type Point = { x: number; y: number };
function isPoint(v: object): v is Point {
  return "x" in v && "y" in v;
}

function area(s: Circle | Square): number {
  return s instanceof Circle ? Math.PI * s.radius ** 2 : s.side ** 2;
}

console.log(area(new Circle(1)).toFixed(4));
console.log(area(new Square(3)));
console.log(isPoint({ x: 1, y: 2 }), isPoint(new Circle(1)));

Ordering matters with subclasses

Prints field(email), validation, generic. Because instanceof walks the whole prototype chain, a FieldError passes the ValidationError test too — putting the base class first would swallow every subclass and you would never reach `e.field`.

Example · typescript
class ValidationError extends Error {}
class FieldError extends ValidationError {
  constructor(public field: string) { super(`bad field: ${field}`); }
}

function classify(e: Error): string {
  // Most specific first: a FieldError is also a ValidationError.
  if (e instanceof FieldError) return `field(${e.field})`;
  if (e instanceof ValidationError) return "validation";
  return "generic";
}

console.log(classify(new FieldError("email")));
console.log(classify(new ValidationError("nope")));
console.log(classify(new Error("boom")));

Discussion

  • Be the first to comment on this lesson.