Question
Runtime Type Checks in TypeScript: Classes, Interfaces, instanceof, and Type Guards
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:
instanceoffor classestypeoffor primitivesinfor 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:
instanceofasks: "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?"
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:
stringnumberbooleanbigintsymbolundefinedfunction
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
Animalis defined as a base class.DogextendsAnimal, so everyDogis also anAnimal.handlePetacceptsunknown, which means TypeScript requires us to narrow the type before using it.- Inside the
if,value instanceof Dogperforms a runtime check. - If the check is true, TypeScript narrows
valuetoDoginside that block. - Because
valueis now known to be aDog, callingvalue.bark()is allowed. petis created withnew Dog(), so the runtime check succeeds.- 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.
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
| Tool | Works for | Runtime? | Example | Notes |
|---|---|---|---|---|
instanceof | Classes | Yes | value instanceof User | Checks prototype chain |
typeof | Primitives | Yes | typeof value === "string" | Limited to primitive categories |
in | Object properties | Yes | "name" in value | Good for shape checks |
| Custom type guard | Interfaces, object shapes, unions | Yes | isUser(value) | Most flexible option |
| Type assertion | Compile-time only | No | value as User | Does not validate anything |
instanceof vs interface checking
| Situation | Best choice |
|---|---|
| You created the value with a class | instanceof |
| You need to detect an inherited class | instanceof |
| You want to validate API JSON | Custom type guard |
| You want to check a primitive | typeof |
| You only need to know whether a property exists | in |
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.
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
instanceofworks with classes, not interfaces- Interfaces are erased during compilation
typeof null === "object", so always checkvalue !== nullas SomeTypedoes not validate data- Use type guards for runtime-safe narrowing
Common narrowing tools
instanceof→ classestypeof→ primitivesin→ 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.
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
- Create a class called
SystemMessagewith atextproperty. - Create an interface called
UserMessagewithuserandtextproperties. - Write a custom type guard that checks whether an unknown value is a
UserMessage. - Write a
printMessagefunction that usesinstanceofforSystemMessageand the custom type guard forUserMessage. - Print a fallback message for invalid input.
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.