TypeScript instanceof: Classes, Arrays, Errors, Interfaces

instanceof checks whether an object was created by a class and narrows its type. Learn how it works, where it fails, and how to check interfaces with in and type guards.

The instanceof operator in TypeScript checks at runtime whether an object was created by a given class or by one of its subclasses, and returns true or false. TypeScript also uses the result for type narrowing, so inside an if (value instanceof Date) block, the compiler treats value as a Date. The operator works with our own classes and with built-in classes such as Date, Array, Map and Error.

We use instanceof when a value can be one of several classes and each class needs its own handling, such as a caught Error in a catch block or a Date that arrives next to a date string.

Java developers use instanceof the same way, with one big difference. In TypeScript, instanceof cannot test an interface, because the compiler removes interfaces from the JavaScript output, so at runtime there is nothing to compare against. For interfaces, we check the object’s properties instead.

  • The in operator checks whether a property exists.
  • A user-defined type guard function checks every property we rely on.
  • A kind field tells apart the types we design ourselves.

The following example tests our own classes and built-in classes with instanceof, compiled with TypeScript 7.0.2 in strict mode and run on Node.js 22. Steps 3 and 4 show narrowing and the in check that replaces instanceof for interfaces.

class Shape {}
class Square extends Shape {
  side = 4;
}

const square = new Square();

// 1. A class and its parent classes
const r1 = square instanceof Square;          // r1 = true
const r2 = square instanceof Shape;           // r2 = true
const r3 = square instanceof Object;          // r3 = true

// 2. Built-in classes
const r4 = new Date() instanceof Date;        // r4 = true
const r5 = [1, 2, 3] instanceof Array;        // r5 = true

// 3. Narrowing: inside the if block, value is a Date
const value: Date | string = new Date(2026, 0, 15);
if (value instanceof Date) {
  console.log(value.getFullYear());           // 2026
}

// 4. Interfaces: check a property with "in" instead
interface User { name: string; age: number; }
const lokesh: User | Square = { name: "Lokesh", age: 37 };
const isUser = "age" in lokesh;               // isUser = true

Notice that square also passes the test for its parent class Shape, because the operator follows the whole inheritance chain.

Next, we look at how instanceof decides its result and how it narrows union types. After that, we test built-in values such as arrays and caught errors, and we check interfaces without instanceof.

1. How instanceof Decides the Result

The syntax is object instanceof Constructor, where the left side is the value to test and the right side is a class or constructor function. Every object in JavaScript has a prototype (that is, another object it inherits from), and that prototype has its own prototype, which forms the prototype chain. The operator returns true when Constructor.prototype appears anywhere in the object’s chain.

class User {
  name: string;
  constructor(name: string) { this.name = name; }
}

const raj = new User("Raj");
const plain = { name: "John" };

// 1. instanceof looks for User.prototype in the prototype chain
const r1 = raj instanceof User;               // r1 = true
const r2 = Object.getPrototypeOf(raj) === User.prototype;   // r2 = true

// 2. An object literal with the same shape is not an instance
const r3 = plain instanceof User;             // r3 = false

The chain explains the results of the first example. A Square object’s chain contains Square.prototype, then Shape.prototype, then Object.prototype, so the test is true for all three classes. The object plain has the same shape as a User, and the compiler would accept it as a User, but it was not created with new User(), so its chain does not contain User.prototype.

The compiler also checks the left side, because a primitive such as number can never pass the test. If the left side has a primitive type, the build fails with error TS2358: The left-hand side of an ‘instanceof’ expression must be of type ‘any’, an object type or a type parameter.

2. Narrowing a Union Type With instanceof

TypeScript treats instanceof as a type guard, which is a check that narrows the type of a variable inside a block. For example, when a parameter of a drawing app can be a Square or a Circle, the compiler knows which one it is in each branch, so we can read side or radius without a cast.

class Square {
  side: number;
  constructor(side: number) { this.side = side; }
}

class Circle {
  radius: number;
  constructor(radius: number) { this.radius = radius; }
}

function describe(shape: Square | Circle): string {
  if (shape instanceof Square) {
    return "square with side " + shape.side;  // shape is Square here
  }
  return "circle with radius " + shape.radius;   // shape is Circle here
}

const d1 = describe(new Square(4));           // d1 = "square with side 4"
const d2 = describe(new Circle(2));           // d2 = "circle with radius 2"

After the if block returns, the only type left is Circle, so the compiler narrows shape in the rest of the function as well. Java 16 added pattern matching (if (shape instanceof Square s)) to get the same effect; in TypeScript, the narrowing happens on the original variable. When the classes share a method, such as area(), overriding that method is cleaner than checking the class.

3. Arrays, Primitives and Caught Errors

Built-in objects are instances of their built-in classes, so instanceof works with Array, Map, Set, RegExp, Date and the Error classes. Primitive values such as strings and numbers are not objects, so they are never instances of anything, not even of String or Number. For primitives, we use typeof.

// 1. Arrays: Array.isArray() is the safer check
const nums = [1, 2, 3];
const r1 = nums instanceof Array;             // r1 = true
const r2 = Array.isArray(nums);               // r2 = true

// 2. Map, Set and RegExp
const r3 = new Map() instanceof Map;          // r3 = true
const r4 = /a+/ instanceof RegExp;            // r4 = true

// 3. Primitives are not instances; use typeof
const name: unknown = "Lokesh";
const r5 = name instanceof String;            // r5 = false
const r6 = typeof name === "string";          // r6 = true

// 4. Errors in a catch block
try {
  JSON.parse("{ bad json");
} catch (e) {
  if (e instanceof SyntaxError) {
    console.log("invalid JSON:", e.name);     // invalid JSON: SyntaxError
  }
}

For arrays, Array.isArray() is the recommended check. An array created in another global environment, such as an iframe or a Node.js vm context, has a different Array constructor, so instanceof Array returns false for it, while Array.isArray() returns true.

The catch block is the most common place for instanceof in modern code. With strict mode, the caught value e has the type unknown, because JavaScript can throw any value, not only Error objects. So reading e.message without a check fails with error TS18046: ‘e’ is of type ‘unknown’, and we first narrow e to the error class with an instanceof check, as in step 4. For example, an import job that parses uploaded JSON files reports a SyntaxError as a bad file. The same check works for custom error classes that extend Error.

4. Why instanceof Does Not Work With Interfaces

An interface describes a shape for the compiler and produces no JavaScript, so there is no constructor and no prototype to look for. Writing lokesh instanceof User, where User is an interface, fails at compile time with error TS2693.

error TS2693: 'User' only refers to a type, but is being used as a value here.

The error follows from structural typing. TypeScript decides whether a value is a User by its properties, not by how it was created, so the runtime check also has to look at properties. TypeScript offers two tools for a property check, namely the in operator and user-defined type guards.

interface User {
  name: string;
  age: number;
}

interface Shape {
  area(): number;
}

// 1. The "in" operator narrows a union of interfaces
function describe(value: User | Shape): string {
  if ("age" in value) {
    return value.name + " is " + value.age;   // value is User here
  }
  return "area " + value.area();              // value is Shape here
}

const d1 = describe({ name: "Lokesh", age: 37 });   // d1 = "Lokesh is 37"
const d2 = describe({ area: () => 16 });            // d2 = "area 16"

// 2. A user-defined type guard for unknown data
function isUser(value: unknown): value is User {
  return typeof value === "object" && value !== null
    && "name" in value && typeof value.name === "string"
    && "age" in value && typeof value.age === "number";
}

const data: unknown = JSON.parse('{"name":"Raj","age":35}');
if (isUser(data)) {
  console.log(data.name);                     // Raj
}

const bad = isUser({ name: "John" });         // bad = false

The in operator is enough when the value already has a union type and one property exists in only one member of the union, because the compiler narrows the union based on that property. A type that has the property as optional stays in both branches.

A user-defined type guard is a function whose return type has the form value is User, so when it returns true, the compiler treats the argument as a User in that branch. We write one for data of type unknown, such as parsed JSON, and check every property we rely on. The compiler does not check that the body of a guard tests what its return type claims, so a careless guard (for example, one that checks only name) gives wrong types without any error.

Since TypeScript 5.5, the compiler also infers simple guards by itself. For example, [37, undefined, 40].filter((x) => x !== undefined) has the type number[].

5. A kind Field Instead of a Class Check

When we control the data, a common pattern is to give every interface in a union a property with a fixed literal value, often called kind or type, which makes it a discriminated union. For example, a payment API can send a kind of “card” or “refund” with each transaction. A switch on that property narrows the type in each case, and it works for plain objects and class instances alike.

interface Square {
  kind: "square";
  side: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

type Shape = Square | Rectangle;

function area(shape: Shape): number {
  switch (shape.kind) {
    case "square":
      return shape.side * shape.side;         // shape is Square
    case "rectangle":
      return shape.width * shape.height;      // shape is Rectangle
  }
}

const a1 = area({ kind: "square", side: 4 });                   // a1 = 16
const a2 = area({ kind: "rectangle", width: 2, height: 3 });    // a2 = 6

The compiler accepts the function without a final return, because the two cases cover every possible kind. If someone adds a Circle to the union later, the function no longer returns a number on every path, and the compiler reports error TS2366: Function lacks ending return statement and return type does not include ‘undefined’. Each kind value is a literal type, and Shape is a union type of the two interfaces.

6. Choosing the Right Check

Each check fits a different kind of value. We use instanceof for class instances and typeof for primitives, whereas values described by an interface need a property check.

Value to checkUseExample
Instance of our own classinstanceofshape instanceof Square
Built-in object or caught errorinstanceofe instanceof SyntaxError
ArrayArray.isArray()Array.isArray(nums)
Primitivetypeoftypeof name === “string”
Union of interfacesin“age” in value
unknown data such as JSONType guard functionisUser(data)
Union we design ourselveskind fieldshape.kind === “square”

Two less common cases can also change the result.

  • A class can define a static Symbol.hasInstance method to customize instanceof.
  • An object created with Object.create() can have any prototype we give it.

We rarely see either of them in application code.

7. Running the instanceof Examples

All snippets are in the instanceof-example folder on GitHub. The project targets Node.js 22 or newer and TypeScript 7.0.2.

npm install
npm start

The output lists every value from the snippet comments, grouped by section.

8. Conclusion

The TypeScript instanceof operator checks whether a class’s prototype is in an object’s prototype chain, and it narrows union types in the checked branch. It works with our own classes and built-in classes, but it returns false for primitives and for plain objects that only look like a class instance.

The operator does not compile with interfaces. For interface types, we check properties with in or with a type guard function, and for the types we design ourselves, a kind field is the cleanest option.

9. References

MDN documents the runtime behavior of the operator, and the TypeScript Handbook describes how each check narrows types.

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.