Union types
What you'll learn:
- describe a value that can be one of several types;
- understand why a value of type
string | numberallows only what both types share; - describe a value that may be missing, and work with it through
?.and??; - limit a value to a fixed set of choices with literal types and
enum.

Picture a parking lot with a sign: "Cars or motorcycles only". You work there and walk up to a spot without knowing what is parked in it. You can refuel a car or a motorcycle: both have a tank. But you can open the trunk only of a car: if a motorcycle is in the spot, there is no trunk.
Values in programs are like that too. An order number in a shop may be the number 101 or the string "A-7". In JavaScript a function accepts either. In TypeScript a parameter has a type, and number alone is not enough here:
function printId(id: number) {
console.log(id);
}
printId(101);
printId("A-7"); // ✗error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.tsc reports that a string cannot be passed to a parameter of type number. We need a way to say "a number or a string"—the way the sign in the parking lot does.
A type for "one or the other"
A union type is written with a vertical bar: string | number reads as "a string or a number". A variable or parameter of this type takes a value of any of the listed types:
function printId(id: string | number) {
console.log(id);
}
printId(101); // 101
printId("A-7"); // A-7Let's walk through the example line by line:
id: string | numberlets the parameter take both a string and a number.printId(101)passes a number and prints101.printId("A-7")passes a string and printsA-7.
A union goes wherever a type does:
- on a variable:
let id: string | number = 101;can later hold"A-7", but nottrue; - on a function parameter, as in the example above;
- on a function's result, after the parentheses with the parameters.
A union may have more than two types: string | number | boolean.
What you can do with the value
This is where the parking sign comes back. Inside printId, TypeScript does not know what arrived this time: a number or a string. So it allows only what both types can do:
function printId(id: string | number) {
console.log(id.toString());
console.log(id.toUpperCase()); // ✗
}error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.Let's see why one line passed and the other did not:
id.toString()compiles: both strings and numbers have thetoStringmethod. It is like refueling, which suits any vehicle in the lot.id.toUpperCase()is an error, and the second line of the message names the reason:numberhas no such method. It is the trunk a motorcycle never has.
If JavaScript ran this function with a number, the program would crash with TypeError: id.toUpperCase is not a function. TypeScript finds the mistake in advance, before the program runs.
Step through the example and watch what the editor's hover says about the highlighted name on each line:
1function printId(id: string | number) {editor hoverid: string | number2 const text = id.toString();3 console.log(text);4 console.log(id.toUpperCase());5}6 7printId(101);8printId("A-7");
The parameter id may turn out to be a string or a number.
Here the picture parts ways with TypeScript in one respect. In a parking lot you can walk up and look at what is parked, while the compiler sees only the sign—the type, not the value itself. To call a method of one of the types, you first check the value, for example with typeof. Inside if (typeof id === "string") TypeScript knows that id is a string.
Interviewers like to ask this: "What can you do with a value of type A | B?" The answer: only what every type in the union has.
Common mistake
A union-typed value is passed where only one type is expected:
function shout(text: string) {
console.log(text.toUpperCase());
}
function printId(id: string | number) {
shout(id); // ✗
}error TS2345: Argument of type 'string | number' is not assignable to parameter of type 'string'.
Type 'number' is not assignable to type 'string'.The shout function takes only a string, but id may hold a number—that is what the second line of the message says. A shared method fixes it: shout(id.toString()) passes a string whatever id holds.
Key points
- The union
string | numbermeans "a string or a number": a variable or parameter of this type takes a value of either type. - A union-typed value allows only what each of its types has:
toString()works,toUpperCase()does not. - A value of type
string | numbercannot go where onlystringis expected. - The compiler sees the type, not the value, so it finds such mistakes before the program runs.