Q12 of 38 · TypeScript

What is type narrowing in TypeScript, and what narrowing techniques are available?

TypeScriptMidtypescripttype-narrowingtype-guardsdiscriminated-unioncontrol-flow

Short answer

Short answer: Type narrowing is TypeScript's ability to refine a broad type to a more specific one inside a conditional block. Techniques include: `typeof` checks, `instanceof`, `in` operator, equality narrowing, truthiness checks, discriminated unions, and user-defined type guards with `is` predicates.

Detail

When a value has a union type, TypeScript tracks which branch of a conditional was taken and narrows the type within that scope — this is the control flow analysis feature.

typeof narrowing: typeof x === 'string' narrows x to string inside the if block.

instanceof narrowing: err instanceof Error narrows to Error — essential for catch blocks.

in narrowing: 'email' in user narrows to types that have an email property.

Equality narrowing: x === null, x !== undefined, x === 'active' all narrow the type.

Truthiness narrowing: if (x) removes null, undefined, 0, '', false from the type.

Discriminated unions: A shared literal type property (kind, type) on each variant lets TypeScript narrow automatically in a switch/if — the most powerful pattern.

User-defined type guards: A function with return type x is SomeType acts as a custom narrowing predicate. TypeScript trusts the function's claim.

Assertion functions: function assert(cond): asserts cond narrows based on a thrown exception.

// EXAMPLE

type ApiResult = { kind: "ok"; data: User } | { kind: "error"; message: string };

// Discriminated union narrowing
function handle(result: ApiResult) {
  if (result.kind === "ok") {
    console.log(result.data.name); // TypeScript knows data exists
  } else {
    console.error(result.message); // TypeScript knows message exists
  }
}

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

// instanceof narrowing in catch
try {
  await page.goto(url);
} catch (err) {
  if (err instanceof Error) {
    console.error(err.message); // safe
  }
}

// in narrowing
function processEvent(e: MouseEvent | KeyboardEvent) {
  if ("key" in e) {
    console.log(e.key); // KeyboardEvent narrowed
  }
}

// WHAT INTERVIEWERS LOOK FOR

Multiple narrowing techniques with examples. Discriminated unions as the most powerful pattern. User-defined type guards for custom predicates. Applying narrowing to catch-block `unknown` errors — this is a very practical QA scenario.

// COMMON PITFALL

Forgetting that type narrowing is not persisted across asynchronous boundaries — after an `await`, TypeScript resets its narrowed knowledge because the type may have changed during the async gap.