TypeScript Narrowing
Narrowing is the process of cutting a union type down to one member inside a branch. A check in the branch tells the compiler which member remains.
Narrowing with typeof
The typeof check splits the union into two branches. Each branch uses methods of one type only.
function format(value: string | number): void {
if (typeof value === "number") {
console.log(value.toString().padStart(4, "0"));
} else {
console.log(value.toUpperCase());
}
}
format(7);
format("hi");Narrowing with instanceof
instanceof asks which class built a value. Each branch calls a method of one class only.
class Cat {
meow(): string {
return "meow";
}
}
class Dog {
bark(): string {
return "woof";
}
}
function speak(pet: Cat | Dog): void {
if (pet instanceof Cat) {
console.log(pet.meow());
} else {
console.log(pet.bark());
}
}
speak(new Cat());
speak(new Dog());Narrowing with the in operator
The in operator tests whether a property exists on a value. The test narrows the union inside the branch.
interface Fish {
swim: string;
}
interface Bird {
fly: string;
}
function move(pet: Fish | Bird): void {
if ("swim" in pet) {
console.log(pet.swim);
} else {
console.log(pet.fly);
}
}
const fish: Fish = { swim: "swimming" };
const bird: Bird = { fly: "flying" };
move(fish);
move(bird);Example
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
side: number;
}
type Shape = Circle | Square;
function area(shape: Shape): number {
if (shape.kind === "circle") {
return 3.14 * shape.radius * shape.radius;
}
return shape.side * shape.side;
}
console.log(area({ kind: "circle", radius: 2 }));
console.log(area({ kind: "square", side: 3 }));Key points
- A typeof check narrows a value by its basic type.
- An instanceof check narrows a value by the class that built it.
- The in operator narrows a value by a property it holds.
- A shared literal field can tag each member of a union.
- Reading the tag narrows a discriminated union in each branch.
In this example, Circle and Square share a kind field with different literal values. Reading kind tells the compiler which shape is present. The circle branch uses radius, and the other branch uses side.
Note
Narrowing lasts only inside the branch where the check passes.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท TypeScript Playground
Exercise
What does narrowing do inside a branch?