Q12 of 38 · TypeScript
What is type narrowing in TypeScript, and what narrowing techniques are available?
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
}
}