Question

Runtime Type Checks in TypeScript: Classes, Interfaces, instanceof, and Type Guards

typescripttypecheckingtypeguards

Question

In ActionScript, you can check a value's type at runtime with the is operator:

var mySprite:Sprite = new Sprite();
trace(mySprite is Sprite); // true
trace(mySprite is DisplayObject); // true
trace(mySprite is IEventDispatcher); // true

Is there an equivalent way in TypeScript to detect whether a variable is a certain class, extends a class, or implements a specific interface?

I could not find a clear answer in the language specification, but this seems important when working with classes and interfaces.

Short Answer

By the end of this page, you will understand how TypeScript handles runtime type checks, when to use instanceof, why interfaces cannot be checked directly at runtime, and how to write custom type guards for safe and practical type detection.

Concept

TypeScript has two worlds:

  • Compile time: where TypeScript checks types for correctness
  • Runtime: where JavaScript actually runs

This distinction is the key to understanding the question.

Classes exist at runtime

A TypeScript class becomes a JavaScript constructor function or class in the emitted code. That means you can check whether an object was created from a class using runtime tools like instanceof.

class Animal {}
class Dog extends Animal {}

const d = new Dog();

console.log(d instanceof Dog);    // true
console.log(d instanceof Animal); // true

Interfaces do not exist at runtime

Interfaces are only used by TypeScript during compilation. They are removed when the code becomes JavaScript. Because of that, you cannot do this:

interface Flyable {
  fly(): void;
}

// Not possible at runtime
// value instanceof Flyable ❌

There is no Flyable value in the generated JavaScript, so there is nothing to compare against.

How to check interface-like shapes

If you want to know whether an object matches an interface, you usually check its structure manually. This is often done with a type guard.

interface Flyable {
  fly(): void;
}

function isFlyable(value: unknown): value is Flyable {
  return typeof value === "object" && value !== null && "fly" in value;
}

This works because TypeScript uses structural typing. If an object has the required shape, TypeScript can treat it as matching the interface.

Why this matters

Real programs receive data from:

  • APIs
  • user input
  • local storage
  • third-party libraries
  • browser events

At runtime, that data may not match your expected type. You need runtime checks to stay safe. In TypeScript, that usually means:

  • instanceof for classes
  • typeof for primitives
  • in for object properties
  • custom type guards for interfaces and object shapes

Mental Model

Think of TypeScript types like labels on a blueprint and JavaScript runtime values like the actual building.

  • A class is like a real building with a physical structure you can inspect later.
  • An interface is like a design note on the blueprint. It helps during planning, but it is not physically attached to the building.

So at runtime:

  • You can inspect a class-based object with instanceof
  • You cannot inspect an interface directly, because it was never part of the final JavaScript

Another way to think about it:

  • instanceof asks: "Was this object built by this constructor or one of its parents?"
  • A type guard asks: "Does this object have the properties and methods I need?"
Take Quiz

Syntax and Examples

Checking a class with instanceof

class DisplayObject {}
class Sprite extends DisplayObject {}

const mySprite = new Sprite();

console.log(mySprite instanceof Sprite);        // true
console.log(mySprite instanceof DisplayObject); // true

Use instanceof when you are working with classes and want to know whether an object was created from a class or one of its parent classes.

Checking primitives with typeof

const value = "hello";

console.log(typeof value === "string"); // true

typeof is useful for primitive values such as:

  • string
  • number
  • boolean
  • bigint
  • symbol
  • undefined
  • function

Checking object shape with in

type User = {
  name: string;
};

const value: unknown = { name: "Ava" };

if (typeof value === "object" && value !== null && "name" in value) {
  console.log("This object has a name property");
}

This does not prove the full type, but it is a common building block.

Custom type guard for an interface

interface IEventDispatcher {
  dispatchEvent(): void;
}

function isEventDispatcher(value: unknown): value is IEventDispatcher {
  return (
    typeof value === "object" &&
    value !== null &&
    "dispatchEvent" in value &&
    typeof (value as { dispatchEvent: unknown }).dispatchEvent === "function"
  );
}

const obj: unknown = {
  dispatchEvent() {
    console.log("event dispatched");
  }
};

if (isEventDispatcher(obj)) {
  obj.dispatchEvent();
}

This is the usual TypeScript way to check whether a value satisfies an interface-like contract.

Step by Step Execution

Consider this example:

class Animal {}
class Dog extends Animal {
  bark() {
    console.log("Woof");
  }
}

function handlePet(value: unknown) {
  if (value instanceof Dog) {
    value.bark();
  } else {
    console.log("Not a Dog");
  }
}

const pet = new Dog();
handlePet(pet);

Step by step

  1. Animal is defined as a base class.
  2. Dog extends Animal, so every Dog is also an Animal.
  3. handlePet accepts unknown, which means TypeScript requires us to narrow the type before using it.
  4. Inside the if, value instanceof Dog performs a runtime check.
  5. If the check is true, TypeScript narrows value to Dog inside that block.
  6. Because value is now known to be a Dog, calling value.bark() is allowed.
  7. pet is created with new Dog(), so the runtime check succeeds.
  8. The program prints Woof.

What if it were an interface?

interface Barkable {
  bark(): void;
}

You cannot write:

// if (value instanceof Barkable) { ... } // ❌

Instead, you would write a custom check:

function isBarkable(value: unknown): value is Barkable {
  return (
    typeof value === "object" &&
    value !== null &&
    "bark" in value &&
    typeof (value as { bark: unknown }).bark === "function"
  );
}

Now TypeScript can narrow the type based on your function.

Real World Use Cases

Validating API responses

When data comes from an API, TypeScript types are not automatically enforced at runtime.

interface Product {
  id: number;
  name: string;
}

function isProduct(value: unknown): value is Product {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value
  );
}

This helps prevent runtime errors when external data is incomplete or malformed.

Handling different class instances

In UI frameworks or game code, you may receive different object instances and need to behave differently depending on the class.

class Button {}
class TextField {}

function renderControl(control: Button | TextField) {
  if (control instanceof Button) {
    console.log("Render button");
  } else {
    console.log("Render text field");
  }
}

Working with errors

try {
  throw new Error("Something went wrong");
} catch (err) {
  if (err instanceof Error) {
    console.log(err.message);
  }
}

This is one of the most common uses of instanceof in real applications.

Feature detection

Sometimes you do not care about the exact class. You only care whether an object supports a method.

function saveIfPossible(value: unknown) {
  if (
    typeof value === "object" &&
    value !== null &&
    "save" in value &&
    typeof (value as { save: unknown }).save === "function"
  ) {
    (value as { save(): void }).save();
  }
}

This is interface-style checking using structure instead of inheritance.

Take Quiz

Real Codebase Usage

In real TypeScript codebases, developers usually combine several narrowing patterns.

Guard clauses

A common pattern is to exit early if a value is invalid.

function printUserName(user: unknown) {
  if (typeof user !== "object" || user === null || !("name" in user)) {
    return;
  }

  console.log(user.name);
}

This keeps the main logic clean.

Early return after instanceof

class ApiError extends Error {
  code: number;

  constructor(message: string, code: number) {
    super(message);
    this.code = code;
  }
}

function handleError(err: unknown) {
  if (!(err instanceof ApiError)) {
    console.log("Unknown error");
    return;
  }

  console.log(err.code, err.message);
}

Reusable type guard functions

Instead of repeating checks everywhere, teams often create helper functions.

interface User {
  id: number;
  email: string;
}

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "email" in value
  );
}

This improves readability and reuse.

Validation before mapping or filtering

const values: unknown[] = [
  { id: 1, email: "a@example.com" },
  { bad: true }
];

const users = values.filter(isUser);

This pattern is common when processing arrays of uncertain data.

Configuration and environment parsing

Environment values are strings at runtime, even if your TypeScript type says otherwise. Developers often validate them before use.

The key idea is simple: TypeScript helps before runtime, but your code still needs runtime checks for real data.

Common Mistakes

Mistake 1: Trying to use instanceof with an interface

interface Person {
  name: string;
}

// if (value instanceof Person) {} // ❌

Why it fails

Interfaces do not exist at runtime.

Fix

Use a custom type guard:

function isPerson(value: unknown): value is Person {
  return typeof value === "object" && value !== null && "name" in value;
}

Mistake 2: Forgetting null when checking objects

function isPerson(value: unknown): boolean {
  return typeof value === "object" && "name" in value; // ❌
}

Why it fails

typeof null is `

Comparisons

Runtime type checking options in TypeScript

ToolWorks forRuntime?ExampleNotes
instanceofClassesYesvalue instanceof UserChecks prototype chain
typeofPrimitivesYestypeof value === "string"Limited to primitive categories
inObject propertiesYes"name" in valueGood for shape checks
Custom type guardInterfaces, object shapes, unionsYesisUser(value)Most flexible option
Type assertionCompile-time onlyNovalue as UserDoes not validate anything

instanceof vs interface checking

SituationBest choice
You created the value with a classinstanceof
You need to detect an inherited classinstanceof
You want to validate API JSONCustom type guard
You want to check a primitivetypeof
You only need to know whether a property existsin

Type assertion vs type guard

const user = value as User;

This tells TypeScript to trust you. It does not check anything at runtime.

if (isUser(value)) {
  // safe to use as User
}

This performs a real runtime check and narrows the type safely.

Take Quiz

Cheat Sheet

Quick reference

Check a class

if (value instanceof MyClass) {
  // value is MyClass here
}

Check a primitive

if (typeof value === "string") {
  // value is string
}

Check whether a property exists

if (typeof value === "object" && value !== null && "name" in value) {
  // value has a name property
}

Custom type guard for an interface

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

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value
  );
}

Important rules

  • instanceof works with classes, not interfaces
  • Interfaces are erased during compilation
  • typeof null === "object", so always check value !== null
  • as SomeType does not validate data
  • Use type guards for runtime-safe narrowing

Common narrowing tools

  • instanceof → classes
  • typeof → primitives
  • in → property existence
  • custom function returning value is Type → interfaces and complex shapes

FAQ

Can TypeScript check interfaces at runtime?

No. Interfaces are removed during compilation, so they do not exist in JavaScript runtime code.

What is the TypeScript equivalent of ActionScript is?

For classes, the closest equivalent is instanceof. For interfaces, use a custom type guard.

Does instanceof work with inherited classes?

Yes. If Child extends Parent, then an instance of Child is also instanceof Parent.

Can I use typeof to check objects?

Only in a limited way. typeof can tell you whether something is an object, but not whether it matches a specific interface.

Is as MyType a runtime check?

No. It only tells the TypeScript compiler to treat a value as a certain type.

What should I use for API response validation?

Use runtime validation with property checks, custom type guards, or a validation library if the data shape is complex.

Why does TypeScript remove interfaces?

Because interfaces are a compile-time feature for static checking. JavaScript itself has no interface construct.

Take Quiz

Mini Project

Description

Build a small runtime checker for a messaging system. Some messages are class instances, while others are plain objects from external data. This project demonstrates when to use instanceof and when to use a custom type guard.

Goal

Create a function that safely handles both class-based messages and interface-shaped messages.

Requirements

  1. Create a class called SystemMessage with a text property.
  2. Create an interface called UserMessage with user and text properties.
  3. Write a custom type guard that checks whether an unknown value is a UserMessage.
  4. Write a printMessage function that uses instanceof for SystemMessage and the custom type guard for UserMessage.
  5. Print a fallback message for invalid input.
Take Quiz

Keep learning

Related questions

View more