stackademic

The leading education platform for anyone with an interest in software development.

TypeScript Type Guards

Narrow types with typeof, instanceof, in, and custom predicates

Overview

Type guards narrow a union to a specific type inside a conditional block. Built-in guards use typeof, instanceof, and in. User-defined type predicates (value is T) encapsulate reusable checks.

Syntax / Usage

function process(value: string | number) {
  if (typeof value === 'string') {
    return value.trim()
  }
  return value.toFixed(2)
}

class Dog { bark() {} }
class Cat { meow() {} }

function speak(pet: Dog | Cat) {
  if (pet instanceof Dog) pet.bark()
  else pet.meow()
}

interface Fish { swim(): void }
interface Bird { fly(): void }

function move(animal: Fish | Bird) {
  if ('swim' in animal) animal.swim()
  else animal.fly()
}

// User-defined type guard
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'name' in value
  )
}

Examples

Filter with type predicate:

const items: (string | null)[] = ['a', null, 'b']

const strings = items.filter((x): x is string => x !== null)
// string[]

Assert non-null after check:

function getUser(id: string | undefined): User {
  if (!id) throw new Error('Missing id')
  // id is string here
  return fetchUser(id)
}

Common Mistakes

  • Type predicates that lie—runtime check must actually guarantee the type
  • Using as casts instead of real guards when data is external
  • typeof null === 'object' historical quirk—check value !== null
  • Narrowing lost when values are captured in closures—re-check inside callbacks

See Also

unions basic-types interfaces utility-types