Skip to Content
Language reference

Values and casts

Narrow unions, check a runtime type, and express nullability explicitly.

A declaration of type string requires a string. Use string | null when the caller may supply null. An ordinary control-flow check narrows the value:

export function labelLength(label: string | null): int {
  if (label === null) return 0
  return label.length
}

Checked assertions

value as T evaluates value once and checks the part of T that the compiler cannot prove. A failed check throws at the cast and can be caught by TypeScript code. The target type determines whether null and undefined are valid.

export function requireLabel(value: string | int | null): string {
  return value as string
}

requireLabel("header")  // returns "header"
requireLabel(null)      // throws at the cast

Casting the same input to string | null allows null and still rejects an integer. A narrowing that depends on erased type arguments requires independent proof or validation. Checking the class of a list alone cannot establish the type of every element. Use a numeric conversion such as int(value) when changing a numeric representation.

Objects and unions

An object literal has reference identity. Two references to the same object observe each other's writes. A literal tag narrows a union when different object shapes share a field:

type State =
  | { kind: "loading" }
  | { kind: "ready", rows: int }

export function rowCount(state: State): int {
  if (state.kind === "ready") return state.rows
  return 0
}

satisfies checks compatibility while preserving the inferred type of the expression. An as const assertion gives literals narrow types and array literals readonly tuple types. It adds no runtime freeze, so mutations through an existing mutable alias remain observable.

On this page