C.W.K.
Stream
Lesson 02 of 05 · published

`instanceof` and `in` Narrowing

~9 min · narrowing, instanceof, in, classes

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"For classes, instanceof. For property existence, in. Both narrow exactly what they say."

instanceof for class instances

x instanceof Foo at runtime checks whether x's prototype chain includes Foo.prototype. TypeScript reads this as narrowing: inside the if, x is type Foo. Outside, x is the union minus Foo. This works for any class — built-in (Date, Error, Array, Map) or user-defined.

The classic use: distinguishing class-instance variants of a union. x: Date | string can be narrowed with if (x instanceof Date) — inside, x.toISOString(); outside, x.toUpperCase().

in for property existence

'prop' in x at runtime checks whether x has a property named 'prop' (anywhere in its prototype chain). TypeScript reads this as narrowing: inside the if, x is the variant of the union that has prop. Outside, x is the variants without it.

The in operator is the cleanest way to narrow on shape when the variants don't share a discriminator field. x: { name: string } | { id: number } can be narrowed with if ('name' in x).

When to choose which

instanceof: when the variants are class instances (Date vs Error vs RegExp). Class identity is the discriminator.

in: when the variants are plain object shapes without a shared discriminator. Property existence is the discriminator.

typeof: primitives.

discriminator equality: when shapes share a kind/type field with literal values — the cleanest, most explicit option.

The four narrowing operators cover almost every real case. Custom type predicates (next lesson) fill the rest — sophisticated checks that no single operator can express.

Code

instanceof for classes, in for shapes·typescript
// instanceof — class instance narrowing.
function stamp(x: Date | Error): string {
  if (x instanceof Date) {
    return x.toISOString();        // x: Date
  }
  return x.message;                // x: Error
}

// instanceof works with user-defined classes too.
class NetworkError extends Error { constructor(public status: number) { super() } }
class TimeoutError extends Error { constructor(public elapsed: number) { super() } }

function handle(err: NetworkError | TimeoutError): string {
  if (err instanceof NetworkError) {
    return `HTTP ${err.status}`;   // err: NetworkError
  }
  return `Timed out after ${err.elapsed}ms`;  // err: TimeoutError
}

// `in` — property existence narrowing.
function printId(item: { id: number } | { name: string }) {
  if ('id' in item) {
    return item.id;                // item: { id: number }
  }
  return item.name;                // item: { name: string }
}

External links

Exercise

Write a serialize function that takes Date | Error | { id: number } and returns a string representation. Use instanceof for Date and Error, then in for the plain object. Confirm the compiler narrows each branch and you can access type-specific methods/properties.
Hint
Date → toISOString(). Error → message. The plain object branch narrows to { id: number } after 'id' in x (since Date and Error don't have a plain id).

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.