JSTAcademy
0 XP
Dashboard
Crash Courses
Type Guards & Narrowing
11 min
PhD+200 XP
Crash Courses · PhD

Type Guards & Narrowing

Write type-safe code by narrowing types at runtime through guards and discriminants.
11 min read+200 XP on completionCert: TypeScript Crash Course
Tap any word in the text below to start reading from there.

Type Guards & Narrowing

TypeScript narrows types based on control flow inside a conditional, the type is automatically refined.

Built-in Guards

function process(value: string | number | null) {
  if (value === null) { return }

  if (typeof value === 'string') {
    return value.toUpperCase()  // value is string here
  }
  return value * 2               // value is number here
}

instanceof Guard

try {
  const res = await fetch(url)
  return await res.text()
} catch (err) {
  if (err instanceof Error) {
    return `Error: ${err.message}`  // err.message is typed
  }
  return 'Unknown error'
}

in Operator Guard

interface Dog { bark(): void }
interface Cat { meow(): void }
type Pet = Dog | Cat

function makeNoise(pet: Pet) {
  if ('bark' in pet) {
    pet.bark()  // TypeScript: pet is Dog
  } else {
    pet.meow()  // TypeScript: pet is Cat
  }
}

Custom Type Predicate

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

function processResponse(data: unknown) {
  if (isUser(data)) {
    console.log(data.email)  // fully typed
  }
}

Assertion Functions

function assertDefined<T>(val: T | null | undefined, name: string): asserts val is T {
  if (val == null) throw new Error(`${name} is required`)
}

function render(userId: string | null) {
  assertDefined(userId, 'userId')
  fetchUser(userId)  // TypeScript: userId is string here
}
0%