TypeScript Union Types

A TypeScript union type represents a value that can belong to one of several declared types. For example, number | string accepts either a number or a string while rejecting values of other types.

Union types are useful when an application legitimately accepts more than one form of data. TypeScript still checks how the value is used: an operation is allowed only when it is valid for every possible member of the union, unless the code first narrows the value to a more specific type.

TypeScript Union Type Syntax

Separate the permitted types with the vertical bar, or pipe, operator:

</>
Copy
 var varname: datatype1|datatype2|datatype3
  • var is the variable declaration keyword used in this syntax example. Modern TypeScript generally uses let for reassigned variables and const for values that are not reassigned.
  • varname is the name of the union variable.
  • datatype1, datatype2, and the remaining entries are the permitted types, separated by vertical bars.

Whitespace around the vertical bar is optional. Writing number | string is usually easier to read than number|string.

Number or String Union Type Example

In the following example, ns can hold either a number or a string. Assigning a Boolean, object, or another unlisted type would produce a TypeScript error.

example.ts

</>
Copy
var ns : number|string
ns = 25
console.log("type of ns is : "+(typeof ns))
console.log("value of ns : "+ns)
ns = "Twenty Five"
console.log("\ntype of ns is : "+(typeof ns))
console.log("value of ns : "+ns)

After transpilation, the type annotation is removed because JavaScript does not retain TypeScript’s static type declarations.

example.js

</>
Copy
var ns;
ns = 25;
console.log("type of ns is : " + (typeof ns));
console.log("value of ns : " + ns);
ns = "Twenty Five";
console.log("\ntype of ns is : " + (typeof ns));
console.log("value of ns : " + ns);

Output

type of ns is : number
value of ns : 25
type of ns is : string
value of ns : Twenty Five

Narrowing a TypeScript Union with typeof

A union variable can initially be any member of its declared union. Before calling a member-specific method, narrow the value with a runtime check such as typeof.

</>
Copy
function formatReference(value: number | string): string {
    if (typeof value === "number") {
        return value.toFixed(2)
    }

    return value.trim().toUpperCase()
}

console.log(formatReference(18.5))
console.log(formatReference("  ab-42  "))

Inside the first branch, TypeScript treats value as a number. After that branch returns, the remaining value must be a string, so string methods can be used safely.

18.50
AB-42

TypeScript Union Types with Arrays

A TypeScript Array can participate in a union. The placement of brackets and parentheses determines whether the variable accepts different kinds of arrays, individual values, or mixed elements.

Union of a Number and a Number Array

In the following example, b can hold either one number or an array containing numbers.

example.ts

</>
Copy
var a : number
var b : number|number[]
a = 10
b = 5
var c = a + b
console.log("sum of a and b : "+c)
b = [4, 8, 9, 1, 5, 6]
var c = a
b.forEach(i => {
    c = c+i
})
console.log("sum of a and b : "+c)

When transpiled to JavaScript, the static union annotation is removed:

example.js

</>
Copy
var a;
var b;
a = 10;
b = 5;
var c = a + b;
console.log("sum of a and b : " + c);
b = [4, 8, 9, 1, 5, 6];
var c = a;
b.forEach(function (i) {
    c = c + i;
});
console.log("sum of a and b : " + c);

Output

sum of a and b : 15
sum of a and b : 43

Mixed Array versus Union of Array Types

These declarations look similar but describe different values:

</>
Copy
let mixedValues: (number | string)[] = [10, "pending", 25]

let uniformValues: number[] | string[]
uniformValues = [10, 20, 30]
uniformValues = ["open", "closed"]
  • (number | string)[] describes one array whose elements may be numbers or strings.
  • number[] | string[] describes either an all-number array or an all-string array.

Literal Union Types for Restricted Values

A union can contain literal values instead of broad primitive types. Literal unions are suitable for fields that accept a small, known set of options.

</>
Copy
type RequestState = "idle" | "loading" | "success" | "error"

function showState(state: RequestState): string {
    return `Current state: ${state}`
}

console.log(showState("loading"))
console.log(showState("success"))

A call such as showState("waiting") is rejected because "waiting" is not a member of RequestState. The alias also avoids repeating the same union throughout the codebase.

Union Types with TypeScript Interfaces

Interfaces can be members of a union. Code that receives the union may use only properties common to all members until it performs a check that identifies the actual object shape.

</>
Copy
interface EmailContact {
    email: string
}

interface PhoneContact {
    phone: string
}

function contactLabel(contact: EmailContact | PhoneContact): string {
    if ("email" in contact) {
        return `Email: ${contact.email}`
    }

    return `Phone: ${contact.phone}`
}

The in check narrows the first branch to EmailContact and the remaining branch to PhoneContact.

Discriminated Unions in TypeScript

A discriminated union gives every object type a shared property with a different literal value. Checking that property lets TypeScript determine which member is being handled.

</>
Copy
type PaymentResult =
    | { status: "approved"; receiptId: string }
    | { status: "declined"; reason: string }
    | { status: "pending"; retryAfterSeconds: number }

function describePayment(result: PaymentResult): string {
    switch (result.status) {
        case "approved":
            return `Receipt: ${result.receiptId}`
        case "declined":
            return `Declined: ${result.reason}`
        case "pending":
            return `Retry after ${result.retryAfterSeconds} seconds`
    }
}

Here, status is the discriminant. Each branch exposes only the properties belonging to that union member, which prevents code from reading receiptId from a declined or pending result.

Union Types in TypeScript Function Parameters and Returns

Function parameters can use unions when callers may supply more than one documented input type. Return unions are appropriate when the result can have multiple meaningful forms, although a discriminated object is often clearer than unrelated primitive results.

</>
Copy
function parseQuantity(value: number | string): number | undefined {
    const quantity = typeof value === "number" ? value : Number(value)

    if (Number.isFinite(quantity)) {
        return quantity
    }

    return undefined
}

The caller must account for both possible return types before using the result as a number.

TypeScript Union versus Intersection Types

Type constructionOperatorMeaningExample
Union|The value matches at least one listed typestring | number
Intersection&The value satisfies all combined typesEmployee & Auditable

Use a union to model alternatives. Use an intersection to combine compatible requirements into one type. They are different type operations even though both can combine named types and interfaces.

Common TypeScript Union Type Errors

  • Calling a method before narrowing: A method available only on string cannot be called directly on string | number.
  • Confusing mixed arrays with alternative arrays: Use (A | B)[] for mixed elements and A[] | B[] for one array type or the other.
  • Using a broad string where a literal union is expected: A variable inferred as string is not automatically one of the permitted string literals.
  • Accessing object-specific properties too early: First narrow an object union with a discriminant, an in check, or an appropriate type guard.
  • Adding unnecessary union members: Include only values the program can meaningfully process. Adding any removes most of the checking benefit.

TypeScript Union Type FAQs

What does the vertical bar mean in a TypeScript type?

The vertical bar creates a union. A declaration such as string | number means the value may be a string or a number.

How do I access properties on a TypeScript union?

You can immediately access properties shared by every member. For member-specific properties, first narrow the union with checks such as typeof, instanceof, in, equality checks, or a discriminant property.

Can a TypeScript interface extend a union type?

An interface cannot directly extend a union because its inherited members must be statically known. Depending on the data model, use a type alias for the union, add a shared base interface to each member, or combine suitable object types with an intersection.

When should I use a discriminated union in TypeScript?

Use a discriminated union when a value has several known object variants with different fields, such as API states, command messages, payment outcomes, or form actions. A shared literal property makes each variant straightforward to identify and handle.

TypeScript Union Tutorial Summary

In this TypeScript Tutorial, we learned how to declare union variables, narrow their types, use unions with arrays and interfaces, restrict values with literal unions, and model object variants with discriminated unions. The main rule is to narrow a union before performing an operation that is not valid for every member.

TypeScript Union Tutorial QA Checklist

  • Verify that every union uses | and that each member represents an input or state the program actually supports.
  • Confirm that member-specific methods and properties are accessed only after control-flow narrowing.
  • Check parentheses in array declarations so (A | B)[] is not confused with A[] | B[].
  • Ensure discriminated union members use a shared property with distinct literal values.
  • Compile examples with strict TypeScript checking and confirm that stated output matches the executable code.
  • Verify that the explanation distinguishes union alternatives from intersection requirements.