Skip to content
Theory · Unions and literal types · lesson 1 of 8

Union types

What you'll learn

What you'll learn:

  • describe a value that can be one of several types;
  • understand why a value of type string | number allows 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.
A parking sign allows both string and number, and only the tool that suits both of them is ticked

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:

TypeScript
function printId(id: number) {
    console.log(id);
}

printId(101);
printId("A-7"); // ✗
TEXT
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:

TypeScript
function printId(id: string | number) {
    console.log(id);
}

printId(101);   // 101
printId("A-7"); // A-7

Let's walk through the example line by line:

  1. id: string | number lets the parameter take both a string and a number.
  2. printId(101) passes a number and prints 101.
  3. printId("A-7") passes a string and prints A-7.

A union goes wherever a type does:

  • on a variable: let id: string | number = 101; can later hold "A-7", but not true;
  • 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:

TypeScript
function printId(id: string | number) {
    console.log(id.toString());
    console.log(id.toUpperCase()); // ✗
}
TEXT
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:

  1. id.toString() compiles: both strings and numbers have the toString method. It is like refueling, which suits any vehicle in the lot.
  2. id.toUpperCase() is an error, and the second line of the message names the reason: number has 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:

LiveWhat TypeScript seesstep 1 of 5

The parameter id may turn out to be a string or a number.

Press "Next step": the hover shows a type or an error on the highlighted line

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:

TypeScript
function shout(text: string) {
    console.log(text.toUpperCase());
}

function printId(id: string | number) {
    shout(id); // ✗
}
TEXT
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 | number means "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 | number cannot go where only string is expected.
  • The compiler sees the type, not the value, so it finds such mistakes before the program runs.