Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Union Types: Values That Can Be One of Several Types

Learn TypeScript union types with the | operator: unions of primitives, unions with null and undefined, what you can do with a union before narrowing, unions of object types, arrays of unions, and how unions model real data.

Intermediate6 min readUpdated
On this page

A union type describes a value that can be one of several types. It is written with a vertical bar: string | number means "a string or a number". Unions are everywhere in real TypeScript code, because real data often comes in more than one form.

A first union#

TypeScript
function formatId(id: string | number): string {
  return `ID-${id}`;
}

console.log(formatId(42));
console.log(formatId("A7"));
Output
ID-42
ID-A7

Any value that matches one of the members is accepted. Anything else is rejected:

TypeScript
function formatId(id: string | number): string {
  return `ID-${id}`;
}
formatId(true);
Compiler error
error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.

You can only use what all members share#

With a union, TypeScript only allows operations that are valid for every member:

TypeScript
function shout(value: string | number) {
  return value.toUpperCase();
}
Compiler error
error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.

toUpperCase exists on strings but not numbers. Methods both types have, such as toString(), are allowed. To use type-specific features, first narrow the union with a check:

TypeScript
function shout(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase();      // value is string here
  }
  return value.toFixed(1);           // value is number here
}

console.log(shout("hello"), shout(3));
Output
HELLO 3.0

Narrowing is so central to unions that it has its own lesson: Narrowing.

Unions with null and undefined#

A very common union marks a value as possibly missing:

TypeScript
function findUser(id: number): { name: string } | undefined {
  const users = [{ id: 1, name: "Asha" }];
  return users.find((u) => u.id === id);
}

const user = findUser(2);
console.log(user?.name ?? "No such user");
Output
No such user

The return type tells every caller that the user might not be found, and strict mode makes them handle it. See null, undefined and Strict Null Checks.

Unions of literal values#

Unions of specific values are an excellent way to restrict a string to a known set:

TypeScript
type Size = "small" | "medium" | "large";

function price(size: Size): number {
  return size === "small" ? 120 : size === "medium" ? 160 : 200;
}

console.log(price("medium"));
Output
160
TypeScript
type Size = "small" | "medium" | "large";
const s: Size = "extra-large";
Compiler error
error TS2322: Type '"extra-large"' is not assignable to type 'Size'.

Editors also auto-complete the allowed values. More in Literal Types.

Unions of object types#

TypeScript
type Card = { method: "card"; last4: string };
type Upi = { method: "upi"; upiId: string };
type Payment = Card | Upi;

function describe(p: Payment): string {
  if (p.method === "card") return `Card ending ${p.last4}`;
  return `UPI ${p.upiId}`;
}

console.log(describe({ method: "card", last4: "4242" }));
console.log(describe({ method: "upi", upiId: "asha@okbank" }));
Output
Card ending 4242
UPI asha@okbank

The shared method property, with a different literal value in each member, lets TypeScript tell the members apart. This pattern, a discriminated union, is one of the most useful in TypeScript; see Discriminated Unions.

Arrays and unions#

Watch the parentheses:

TypeMeans
(string | number)[]An array whose items can each be a string or a number
string[] | number[]Either an array of strings or an array of numbers
string | number[]A string, or an array of numbers

Practice#

Practice exercise

Write toArray(value: string | string[]): string[] that always returns an array, and lengthOf(value: string | unknown[] | null): number that returns the length, or 0 for null. Test both.

Show solution
TypeScript
function toArray(value: string | string[]): string[] {
  return typeof value === "string" ? [value] : value;
}

function lengthOf(value: string | unknown[] | null): number {
  if (value === null) return 0;
  return value.length;   // both strings and arrays have length
}

console.log(toArray("a"), toArray(["b", "c"]));
console.log(lengthOf("hello"), lengthOf([1, 2]), lengthOf(null));
Output
[ 'a' ] [ 'b', 'c' ]
5 2 0

After ruling out null, value.length is allowed because every remaining member has a length.

Key takeaway#

Finished this lesson?