Narrowing uncertain values

Narrow with in and instanceof

Use property checks for object unions and constructor checks for real runtime classes.

typeof only distinguishes primitives. Every object answers 'object', so a union of two object shapes needs a different runtime question. You have two: does this property exist, and was this built by that constructor?

Narrow with in

The JavaScript in operator checks whether a property exists on an object or its prototype chain. TypeScript uses that fact to narrow an object union:

type Success = { data: string }
type Failure = { message: string }

function print(result: Success | Failure) {
  if ('message' in result) {
    console.error(result.message)
    return
  }

  console.log(result.data)
}

Only Failure has a message property, so inside the if, result is a Failure. After the early return, TypeScript knows the remaining code deals with a Success, and result.data compiles.

Without the check, result.data fails:

Property 'data' does not exist on type 'Success | Failure'.
  Property 'data' does not exist on type 'Failure'.

One caveat. Optional properties may exist or be absent, so they can appear in both branches. If both variants declare message?: string, the in check proves nothing. When variants overlap, a required discriminant field is clearer. That is the next lesson.

Narrow with instanceof

instanceof checks the prototype chain. It answers “was this value created by this constructor?”:

function logWhen(value: Date | string) {
  if (value instanceof Date) {
    console.log(value.toISOString())
  } else {
    console.log(value)
  }
}

In the first branch value is a Date, so toISOString() is available. In the else branch it must be a string.

This works for runtime constructors such as Date, Error, URL, and your own JavaScript classes. It cannot work with a type alias or an interface, because those disappear during compilation. Write value instanceof Success and you get:

'Success' only refers to a type, but is being used as a value here.

That error is the compiler reminding you which check you need. When the variants are plain object shapes, reach for in or a discriminant field. When they are class instances, instanceof is the direct, honest check.

Which one I pick

I use in for data that came from JSON, because JSON never carries class instances. I use instanceof in catch blocks, where error instanceof Error is the only way to safely read .message from an unknown error.

Try this: create a union of Date | string, narrow it with instanceof, and format both branches.

Lesson completed