Skip to content

TypeScript - Union

Union types allow you to define a variable or function parameter that can hold values of more than one specific type. They provide flexibility while still maintaining a degree of type safety, as the value must be one of the types listed in the union.

A union type is created using the pipe symbol (|) between two or more type annotations.

let variableName: Type1 | Type2 | Type3 | ...;
let identifier: string | number;
identifier = 12345; // OK
console.log(`Numeric identifier: ${identifier}`);
identifier = "user-abc-123"; // OK
console.log(`String identifier: ${identifier}`);
// identifier = true; // Error: Type 'boolean' is not assignable to type 'string | number'.

In this example, identifier can hold either a string or a number.

When you have a value of a union type, TypeScript will only allow you to access members (properties or methods) that are common to all types in the union without performing a type check first.

function printValue(value: string | number) {
// console.log(value.toUpperCase()); // Error: Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.
// console.log(value.toFixed(2)); // Error: Property 'toFixed' does not exist on type 'string | number'. Property 'toFixed' does not exist on type 'string'.
// toString() is common to both string and number (via prototype chain), so this is allowed:
console.log(value.toString());
}

To work with type-specific members, you need to narrow the union type down to a specific type within a certain code block. This is achieved using Type Guards.

Common type guards include:

  • typeof checks: For primitive types like string, number, boolean, symbol, bigint, undefined, function.
  • instanceof checks: For checking if an object is an instance of a specific class.
  • Property presence checks (in operator): Checking if an object has a specific property.
  • Equality checks: Comparing against specific literal values or null/undefined.
  • Custom Type Guard Functions: Functions that return a type predicate (parameterName is Type).

Example: Union Type and Function Parameter with Type Guards

Section titled “Example: Union Type and Function Parameter with Type Guards”
function processInput(input: string | string[] | null) {
if (typeof input === 'string') {
// Here, TypeScript knows 'input' is a string
console.log(`Processing string: ${input.toUpperCase()}`);
}
// Using Array.isArray for array check (better than typeof which returns 'object')
else if (Array.isArray(input)) {
// Here, TypeScript knows 'input' is a string[]
console.log(`Processing array: Found ${input.length} items.`);
input.forEach((item, index) => console.log(` Item ${index}: ${item}`));
}
// Checking for null explicitly
else if (input === null) {
console.log("Input is null.");
}
// Note: No else needed here if null is the only remaining option
}
processInput("hello");
console.log("---");
processInput(["Apple", "Banana", "Cherry"]);
console.log("---");
processInput(null);

Union types can be applied to array elements or object properties.

To allow an array to contain elements of different types from the union, use parentheses:

// Array that can hold strings OR numbers
let mixedArray: (string | number)[] = [1, "two", 3, "four"];
mixedArray.push(5);
mixedArray.push("six");
// mixedArray.push(true); // Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
console.log("Mixed Array:", mixedArray);
// Process mixed array using type guards
mixedArray.forEach(item => {
if (typeof item === 'string') {
console.log(`String item: ${item.toUpperCase()}`);
} else {
console.log(`Number item: ${item.toFixed(1)}`);
}
});

This defines a variable that can hold EITHER an array of numbers OR an array of strings, but not a mix of both in the same array.

let list: number[] | string[];
list = [1, 2, 3, 4]; // OK - Array of numbers
console.log("Numeric Array:", list);
list = ["a", "b", "c"]; // OK - Array of strings
console.log("String Array:", list);
// list = [1, "a"]; // Error: Type '(string | number)[]' is not assignable to type 'number[] | string[]'.
// Type '(string | number)[]' is not assignable to type 'string[]'.

Union types can be combined with literal types to restrict values to a specific set of constants.

type Alignment = "left" | "right" | "center";
let textAlign: Alignment;
textAlign = "center"; // OK
textAlign = "left"; // OK
// textAlign = "justify"; // Error: Type '"justify"' is not assignable to type 'Alignment'.

Union types are a powerful feature for modeling situations where a value can legitimately be one of several types, enhancing code flexibility while guiding developers to handle each possibility correctly using type guards.