TS Narrowing
Narrowing is how TypeScript refines a wide type (like string | number) down to a specific one inside a branch. The compiler tracks what you've ruled out.
typeof guards
TS
function format(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // value: string
}
return value.toFixed(2); // value: number
}
truthiness narrowing
TS
function len(s?: string): number {
if (!s) return 0; // s narrowed to undefined here
return s.length; // s: string
}
Equality narrowing
TS
function example(x: string | number, y: string | boolean) {
if (x === y) {
// x and y both narrowed to string
return x.toUpperCase();
}
}
in operator
TS
type Fish = { swim(): void };
type Bird = { fly(): void };
function move(animal: Fish | Bird) {
if ('swim' in animal) {
animal.swim(); // Fish
} else {
animal.fly(); // Bird
}
}
instanceof
TS
function check(d: Date | string) {
return d instanceof Date ? d.toISOString() : d;
}
Discriminated unions
The most powerful pattern — give each union member a literal tag:
TS
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rect'; w: number; h: number };
function area(s: Shape) {
switch (s.kind) {
case 'circle': return Math.PI * s.radius ** 2;
case 'rect': return s.w * s.h;
}
}
Tip: If a switch over a discriminated union forgets a case, TS will warn — assign the variable to a
never in the default branch to make the check explicit.Example
Example
function format(value: string | number): string {
if (typeof value === 'string') {
return value.toUpperCase(); // narrowed to string
}
return value.toFixed(2); // narrowed to number
}
console.log(format('hi'), format(3.14));
Try it Yourself »
Exercise
Operator that narrows a primitive in an if.
if (
value === 'string') {}
Six letters.
Discussion
Loading…