Hint
Narrowing the type in a branch — typeof, instanceof, in operator, or a user-defined predicate function
Type guards narrow a broad type to a more specific one within a conditional block.
Built-in type guards:
function process(val: string | number | null) {
if (typeof val === 'string') { /* val is string */ }
if (typeof val === 'number') { /* val is number */ }
if (val !== null) { /* val is string | number */ }
}
// instanceof — for class instances
function handleEvent(e: MouseEvent | KeyboardEvent) {
if (e instanceof KeyboardEvent) {
console.log(e.key); // safe
}
}
// in operator — property existence check
type Cat = { meow(): void };
type Dog = { bark(): void };
function speak(animal: Cat | Dog) {
if ('meow' in animal) animal.meow();
else animal.bark();
}
User-defined type guard — a function returning val is T:
interface User { id: number; name: string; }
// The return type "val is User" is the type predicate
function isUser(val: unknown): val is User {
return (
typeof val === 'object' &&
val !== null &&
'id' in val &&
'name' in val &&
typeof (val as User).id === 'number' &&
typeof (val as User).name === 'string'
);
}
// Usage — TypeScript now knows it's a User after the check
const data: unknown = fetchJSON();
if (isUser(data)) {
console.log(data.name); // safe — TypeScript trusts your predicate
}
val is T) are a contract you make with TypeScript. If your implementation is wrong, TypeScript won't catch it — always thoroughly validate all expected fields.