Learn TypeScript type guards and type narrowing with runnable examples. Switch-case pattern matching, typeof, instanceof, and custom predicate guards.
Type guards are expressions that narrow a value's type within a code branch. TypeScript's control-flow analysis recognizes typeof, instanceof, equality checks, the `in` operator, and user-defined type predicates, and uses them to refine the type as you go. This is how you take a union like string | number and end up in a branch where the compiler knows you have just a string.
typeof x === 'string' narrows x to string in the true branch. x instanceof Error narrows to Error. Both are recognized by TypeScript's control-flow analysis with zero ceremony.
Give each variant a literal tag property ('kind', 'type'), then switch on it. Inside each case, TypeScript narrows to that specific variant — including its extra fields.
function isUser(x: unknown): x is User { ... } — the `x is User` return type tells TypeScript that the function's true return value implies the argument is a User. Lets you encapsulate complex narrowing logic.
if ('length' in obj) narrows obj to the subset of the union that has a length property. Handy for duck-typed unions where there's no tag field.
Use discriminated unions for modelling 'one of N known shapes' — API responses, Redux actions, result types. Use type predicates when the narrowing logic is complex or needs to be reused. Use typeof/instanceof for everyday narrowing inside a single function.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rect":
return shape.width * shape.height;
case "triangle":
return 0.5 * shape.base * shape.height;
default: {
// Exhaustiveness check: fails compilation if a case is missing
const _exhaustive: never = shape;
return _exhaustive;
}
}
}
console.log(area({ kind: "circle", radius: 5 }).toFixed(2));
console.log(area({ kind: "rect", width: 4, height: 6 }));
console.log(area({ kind: "triangle", base: 4, height: 3 }));
// User-defined type predicate
function isNonEmptyString(x: unknown): x is string {
return typeof x === "string" && x.length > 0;
}
const values: unknown[] = ["hello", "", 42, null];
const strings = values.filter(isNonEmptyString);
console.log(strings); // inferred as string[]
Open this example in the TryJS playground to edit and run the code instantly in your browser — no signup needed.
A type guard is an expression whose truthiness narrows a value's type in the branch it guards. Examples: typeof x === 'string', x instanceof Error, user-defined predicates like isUser(x).
A type guard narrows the type through runtime logic that TypeScript can verify. A type assertion (x as T) just tells the compiler 'trust me, it's a T' — no runtime check. Guards are safer; assertions are escape hatches.
A function returning `x is T`. TypeScript uses its return value to narrow the argument's type. Example: function isString(x: unknown): x is string { return typeof x === 'string' }.
Add a default case that assigns the value to a variable of type never. If any case is missing, the assignment fails to compile. const _exhaustive: never = value.