Overview

Control-flow analysis narrows a variable to a more specific type inside a branch; prefer it to casting. The rationale is in typescript-narrowing. Snippets compile under strict.

Built-in guards

Narrow with the built-in guard that matches the check, not with a cast.

GuardNarrows to
typeof x === "string" ("number", "boolean", "bigint", "symbol", "undefined", "function")That primitive or function type.
x === null, x == nullnull; == null covers null and undefined.
if (x)Removes null, undefined, 0, "", false.
err instanceof TypeErrorThe class instance type.
"name" in objThe union members that have name.
Array.isArray(x)An array type.
a?.b in a conditionRemoves null and undefined from a in the true branch.
function show(v: string | number | null) {
  if (v === null) return;
  return typeof v === "string" ? v.length : v.toFixed(2);   // string | number narrowed
}

Type predicates

A function returning v is T narrows its argument at the call site.

function isCat(a: Cat | Dog): a is Cat { return "meow" in a; }
 
const present = [1, undefined, 2].filter((x) => x !== undefined);   // number[] (TypeScript 5.5+ infers the predicate)

Before 5.5, write the predicate by hand: .filter((x): x is number => x !== undefined). The compiler does not check that a handwritten predicate is truthful.

Discriminated unions

Give every variant a literal field (type, kind) and switch on it.

type Shape =
  | { type: "circle"; radius: number }
  | { type: "rect"; width: number; height: number };
 
function assertNever(x: never): never { throw new Error(`Unhandled: ${JSON.stringify(x)}`); }
 
function area(s: Shape): number {
  switch (s.type) {
    case "circle": return Math.PI * s.radius ** 2;
    case "rect":   return s.width * s.height;
    default:       return assertNever(s);   // compile error when a variant is missing
  }
}

Assertion functions

Use assertions at trust boundaries, and validate real data with a schema.

SignatureEffect after the call
function assert(cond: unknown): asserts condcond is truthy.
function assertDefined<T>(v: T | null | undefined): asserts v is Tv is not nullish.
declare const user: { name: string } | null;
assertDefined(user);
user.name;          // narrowed to { name: string }

Gotchas

Avoid these; each hides an unsound narrowing.

GotchaFix
typeof null === "object".Check x !== null before using an object branch.
A wrong handwritten predicate or assertion silently misleads the compiler.Keep them small, and validate untrusted data with a schema (typescript-runtime-validation).
"k" in x throws at runtime when x is a primitive or nullish.Guard x first.
A discriminant typed string or number narrows nothing.Use literal types.
assertNever catches missing variants only at compile time.At runtime it is your guard against unexpected external data.