TypeScript Union Types and Narrowing
Accept more than one type and narrow it safely with typeof.
TypeScript
type Id = number | string;
function format(id: Id): string {
if (typeof id === "number") {
return `#${id.toFixed(0)}`;
}
return id.toUpperCase();
}
console.log(format(42));
console.log(format("ab7"));Output
#42 AB7
Explanation
A union type such as number | string says a value may be any one of the listed types. You can only use operations that are valid for every member until you narrow the type.
Inside an if that tests typeof id === "number", TypeScript knows id is a number, and in the rest of the function it knows id is a string. This is called narrowing, and it lets one function handle several kinds of input without casts.
Explore the dry run
Guided walkthroughReady
1
type Id = number | string;2
3
function format(id: Id): string {4
if (typeof id === "number") {5
return `#${id.toFixed(0)}`;6
}7
return id.toUpperCase();8
}9
10
console.log(format(42));11
console.log(format("ab7"));Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Id can hold either a number or a string. | — | — | |
| Test the runtime type; inside this block id is a number. | — | — | |
| 42 is a number, so the first branch formats it. | id = 42 | #42 | |
| The string reaches the last line, where id is known to be a string. | id = "ab7" | #42 AB7 |
Cost and common mistakes
Narrowing is checked at compile time. Each call is O(1).
- Calling toUpperCase on the whole union is a compile error; narrow first.
- typeof null is "object", so narrow null and undefined with explicit checks.
Try it yourself
Predict format(7.9).
Reveal answer and reasoning
toFixed(0) rounds to "8", so it prints #8.