TypeScript: TypeScript Union Types and Literal Types

Last updated: 2026-08-26

Union types and literal types are the first step in TypeScript's advancement over JavaScript—they allow you to express that "this value can be either A or B" and that "this value can only be one of these options."

1. Union Types

A union type uses | to combine multiple types, indicating that "the value can be any one of these types":

(1) Basic Syntax

TYPESCRIPT
let id: number | string;

id = 42;        // ✅ number Legal
id = "ABC";     // ✅ string Legal
id = true;      // ❌ boolean Not in a composite type

(2) Common Uses: Function Parameters

The most common use of union types is to allow functions to accept parameters of various types:

TYPESCRIPT
function printId(id: number | string) {
  console.log("YourID: :" + id);
}

printId(42);       // ✅
printId("ABC");    // ✅
printId(true);     // ❌

(3) Restrictions on Union Types

Variables of a union type can only access members common to all types:

TYPESCRIPT
function process(value: number | string) {
  // value.toString()   // ✅ number and string All of them toString
  // value.toUpperCase() // ❌ number None toUpperCase
  // value.toFixed(2)    // ❌ string None toFixed
}
📌 Reason: TypeScript must ensure that the code remains safe even when value is any union member. Only properties and methods shared by all members can be called directly. To access a member of a specific type, you must first use "type narrowing" (see Section 3).



2. Literal Types

Literal types restrict a variable's value to specific literals—not "any string," but "only these specific strings."

(1) Three Types of Literals

Literal Types Syntax Example
String Literal "value1" | "value2" "north" | "south" | "east" | "west"
Numeric Literals 1 | 2 | 3 0 | 1 | 2
Boolean literal true | false Actually equals boolean (only two values)

(2) String Literal Types

TYPESCRIPT
let direction: "north" | "south" | "east" | "west";

direction = "north";    // ✅
direction = "up";       // ❌ "up" Not in a composite type

(3) Numeric Literal Types

TYPESCRIPT
type HttpStatusCode = 200 | 301 | 404 | 500;

let code: HttpStatusCode = 200;   // ✅
let code2: HttpStatusCode = 201;  // ❌ 201 Not in a composite type

(4) Boolean Literal Types

TYPESCRIPT
type YesNo = true | false;  // equivalent to boolean
type StrictTrue = true;     // It can only be true
💡 Tip: The boolean literal type doesn't have much meaning on its own (true | false is simply a boolean), but it's useful when combined with other types in union types: string | true represents "a string or true."

▶ Example: Defining Role Permissions Using Literal Types

TYPESCRIPT
type Role = "admin" | "editor" | "viewer";

function checkPermission(role: Role) {
  if (role === "admin") {
    console.log("Administrator:Full Access");
  } else if (role === "editor") {
    console.log("Editor:Editable Content");
  } else {
    console.log("Viewers:Read-only access");
  }
}

checkPermission("admin");   // ✅
checkPermission("editor");  // ✅
checkPermission("guest");   // ❌ Compilation Error——"guest" Not legal Role
▶ Try it Yourself

Output:

TEXT 📖 Display only
Administrator:Full Access
Editor:Editable Content


3. typenarrowing(Type Narrowing)

Variables of a union type cannot directly access members of a specific type, but the scope can be narrowed through "type narrowing."

(1) typeof narrowing

TYPESCRIPT
function process(value: number | string) {
  if (typeof value === "string") {
    // In this branch,TypeScript knows value is string
    console.log(value.toUpperCase());  // ✅ string Methods
  } else {
    // In this branch,TypeScript knows value is number
    console.log(value.toFixed(2));     // ✅ number Methods
  }
}

(2) Narrowing of Equality

TYPESCRIPT
function compare(a: string | number, b: string | boolean) {
  if (a === b) {
    // a and b equal,Their intersection type is string
    console.log(a.toUpperCase());  // ✅ Here a It must be string
  }
}

(3) Narrowing of the in operator

TYPESCRIPT
function process(input: { name: string } | { age: number }) {
  if ("name" in input) {
    console.log(input.name);  // ✅ Has name property,Inferred as { name: string }
  } else {
    console.log(input.age);   // ✅ None name,Inferred as { age: number }
  }
}

(4) instanceof narrowing

TYPESCRIPT
function processDate(value: Date | string) {
  if (value instanceof Date) {
    console.log(value.toISOString());  // ✅ Date Methods
  } else {
    console.log(value.toUpperCase());  // ✅ string Methods
  }
}

▶ Example: A Complete Demonstration of Type Narrowing

TYPESCRIPT
function formatValue(value: number | string | boolean): string {
  if (typeof value === "number") {
    return "Numbers:" + value.toFixed(2);
  } else if (typeof value === "string") {
    return "String:" + value.toUpperCase();
  } else {
    return "Boolean:" + value;
  }
}

console.log(formatValue(3.14159));
console.log(formatValue("hello"));
console.log(formatValue(true));
▶ Try it Yourself

Output:

TEXT 📖 Display only
Numbers:3.14
String:HELLO
Boolean:true


4. Classic Patterns for Union Types and Literal Types

Combining union types and literal types can enable very powerful type constraints. Here are three classic patterns:

(1) Pattern 1: Discriminated Union

Distinguish union members using different literal values for the same property:

TYPESCRIPT
interface Circle {
  kind: "circle";       // Identifiable Attributes
  radius: number;
}

interface Rectangle {
  kind: "rectangle";    // Identifiable Attributes
  width: number;
  height: number;
}

type Shape = Circle | Rectangle;

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;   // ✅ Accessible radius
  } else {
    return shape.width * shape.height;     // ✅ Accessible width、height
  }
}
📌 Key Point: Each interface has a kind property, but its value is a different literal type. TypeScript uses the value of kind to narrow the type and safely access the properties specific to that interface.

(2) Pattern 2: Optional values + undefined

TYPESCRIPT
type Result = string | undefined;

function search(query: string): Result {
  if (query.length === 0) return undefined;
  return "Found:" + query;
}

let result = search("TypeScript");
if (result !== undefined) {
  console.log(result.toUpperCase());  // ✅ narrowed to string
}

(3) Pattern 3: State Machine

TYPESCRIPT
type RequestStatus = "idle" | "loading" | "success" | "error";

interface RequestState {
  status: RequestStatus;
  data?: string;
  error?: string;
}

function renderState(state: RequestState): string {
  switch (state.status) {
    case "idle":
      return "Waiting......";
    case "loading":
      return "Loading......";
    case "success":
      return "Success:" + state.data;    // ✅ success in that state data Existence
    case "error":
      return "Error:" + state.error;   // ✅ error in that state error Existence
  }
}


5. The never Type and Exhaustive Checks

When TypeScript narrows down to an "impossible scenario," the type becomes never:

TYPESCRIPT
type Shape = "circle" | "square";

function getIcon(shape: Shape): string {
  switch (shape) {
    case "circle": return "⭕";
    case "square": return "⬜";
    default:
      // If all case It's all taken care of.,shape Here, the type is never
      const _exhaustiveCheck: never = shape;  // ✅ If omitted case,An error will occur here
      return _exhaustiveCheck;
  }
}
💡 Tip: never's exhaustive checking pattern—assign a value to a never-typed variable in the switch statement's default branch. If you add a new member to Shape in the future but forget to add a corresponding case, TypeScript will throw an error in the default branch, prompting you to add it.

▶ Example: The Practical Effect of Exhaustive Checking

TYPESCRIPT
type TrafficLight = "red" | "yellow" | "green";

function getAction(light: TrafficLight): string {
  switch (light) {
    case "red": return "Stop";
    case "yellow": return "Note";
    // Intentional omission "green" 's case
    default:
      // If you add the next line,TypeScript It will throw an error:
      // You cannot convert the type "green" Assignment to a Type "never"
      const exhaustive: never = light;
      return "Unknown";
  }
}
▶ Try it Yourself

Output:

TEXT 📖 Display only
Error: Type '"green"' is not assignable to type 'never'.

❓ FAQ

Q What is the difference between union types and intersection types?
A A union type (A | B) means “either A or B”—it takes the union, so a value only needs to satisfy one of them. An intersection type (A & B) means “both A and B”—it takes the intersection, so a value must satisfy both. Lesson 20 will explain intersection types in detail.
Q Which should I use: literal types or enums?
A Prefer union literal types. They are lighter than enums (no extra code is generated at compile time) and are fully compatible with JavaScript’s native strings and numbers. Enums are suitable for scenarios that require reverse mapping (value → name) or where you need to iterate over all members at runtime. Lesson 12 will compare the two.
Q Can typeof detect all types?
A No. typeof can only identify the following types: "number," "string," "boolean," "symbol," "bigint," "object," "function," and "undefined." For reference types such as null (typeof null returns "object"), arrays, and Date, you need to use instanceof or other methods to narrow the type. See Lesson 17 for a detailed explanation.
Q When is the never type used?
A The never type primarily appears in two scenarios: (1) It appears automatically when a type is narrowed to an impossible branch, and (2) when a function will never return a value (such as when an exception is thrown or an infinite loop occurs). Beginners only need to understand the "exhaustive check" pattern—using never in the switch’s default clause to check for any missing cases.

📖 Summary

📝 Exercises

  1. Basic Problem (Difficulty ⭐): Define a union type Status = "active" | "inactive" | "banned" and write a function getStatusText(status: Status) that returns the corresponding Chinese description.
  2. Advanced Problem (Difficulty ⭐⭐): Define a distinguishable union type Result = SuccessResult | ErrorResult, where SuccessResult consists of success: true and data: string, and ErrorResult consists of success: false and error: string. Write a function that processes Result and, based on the value of success, safely accesses either data or error.
  3. Challenge Problem (Difficulty ⭐⭐⭐): Write a function describeDay(day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun") using exhaustive checking to handle all 7 days in a switch statement. Then, intentionally omit one day and observe the error message generated by the “never” exhaustive check.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏