[2026] Advanced TypeScript Types | Union, Intersection, and Literal Types

[2026] Advanced TypeScript Types | Union, Intersection, and Literal Types

이 글의 핵심

Master Union (|), Intersection (&), literal types, type aliases, and narrowing with typeof, instanceof, in, and custom predicates—patterns for APIs and state machines.

Introduction

Advanced types in TypeScript let you define precise, flexible models for your data.

1. Union types

Concept

A union type means a value can be one of several types. 아래 코드는 typescript를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// Syntax: Type1 | Type2 | Type3
let value: string | number;
value = "text";  // ✅
value = 123;     // ✅
// value = true;   // ❌ error

Practical examples

다음은 typescript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Function parameters
function printId(id: string | number) {
    console.log(`ID: ${id}`);
}
printId(101);        // ID: 101
printId("USER001");  // ID: USER001
// Arrays
let mixedArray: (string | number)[] = [1, "two", 3, "four"];
// Return type
function getResult(success: boolean): string | null {
    return success ? "ok" : null;
}

Type guards

With unions you narrow types with type guards before using type-specific APIs: 다음은 typescript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 함수 정의 및 구현
function processValue(value: string | number) {
    // typeof checks at runtime; the compiler narrows the type
    if (typeof value === "string") {
        // ✅ In this block, value is string
        console.log(value.toUpperCase());
        console.log(value.length);
        console.log(value.trim());
    } else {
        // ✅ In else, value is number
        console.log(value.toFixed(2));
        console.log(value * 2);
        console.log(value.toExponential());
    }
}
processValue("hello");  // HELLO, 5
processValue(3.14159);  // 3.14, 6.28318

Without type guards: 아래 코드는 typescript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

function processValueBad(value: string | number) {
    // ❌ Compile error: string | number has no shared toUpperCase
    // console.log(value.toUpperCase());
    // ❌ Compile error: string | number has no shared toFixed
    // console.log(value.toFixed(2));
    // ✅ Shared methods only
    console.log(value.toString());
    console.log(value.valueOf());
}

More narrowing patterns: 다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. typeof (primitives)
function format(value: string | number | boolean) {
    if (typeof value === "string") {
        return value.toUpperCase();
    } else if (typeof value === "number") {
        return value.toFixed(2);
    } else {
        return value ? "true" : "false";
    }
}
// 2. instanceof (class instances)
function handleError(error: Error | string) {
    if (error instanceof Error) {
        console.log(error.message);
        console.log(error.stack);
    } else {
        console.log(error);
    }
}
// 3. in operator (discriminate by property)
type Dog = { bark: () => void };
type Cat = { meow: () => void };
function makeSound(animal: Dog | Cat) {
    if ("bark" in animal) {
        animal.bark();
    } else {
        animal.meow();
    }
}
// 4. User-defined type predicate
function isString(value: unknown): value is string {
    return typeof value === "string";
}
function process(value: unknown) {
    if (isString(value)) {
        console.log(value.toUpperCase());
    }
}

2. Intersection types

Concept

An intersection type must satisfy all of the combined types. 다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Syntax: Type1 & Type2 & Type3
type Person = {
    name: string;
    age: number;
};
type Employee = {
    employeeId: string;
    department: string;
};
type Staff = Person & Employee;
const staff: Staff = {
    name: "Alice",
    age: 30,
    employeeId: "E001",
    department: "Engineering"
};

Practical example

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Mixin-style composition
type Timestamped = {
    createdAt: Date;
    updatedAt: Date;
};
type User = {
    id: string;
    name: string;
    email: string;
};
type UserWithTimestamp = User & Timestamped;
const user: UserWithTimestamp = {
    id: "U001",
    name: "Alice",
    email: "alice@test.com",
    createdAt: new Date(),
    updatedAt: new Date()
};

3. Literal types

Concept

A literal type pins a value to an exact constant. 다음은 typescript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// String literals
let direction: "left" | "right" | "up" | "down";
direction = "left";   // ✅
// direction = "top"; // ❌ error
// Numeric literals
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
diceRoll = 3;   // ✅
// diceRoll = 7; // ❌ error
// Boolean literal
let isTrue: true;
isTrue = true;   // ✅
// isTrue = false; // ❌ error

Practical examples

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// HTTP methods
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function request(url: string, method: HttpMethod) {
    console.log(`${method} ${url}`);
}
request("/api/users", "GET");   // ✅
// request("/api/users", "PATCH"); // ❌ error
// State machines
type Status = "idle" | "loading" | "success" | "error";
interface ApiState {
    status: Status;
    data: any;
    error: string | null;
}
const state: ApiState = {
    status: "loading",
    data: null,
    error: null
};

4. Type aliases

Concept

A type alias gives a name to a type. 다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Primitives
type UserId = string;
type Age = number;
let id: UserId = "U001";
let age: Age = 25;
// Object shape
type User = {
    id: UserId;
    name: string;
    age: Age;
    email: string;
};
const user: User = {
    id: "U001",
    name: "Alice",
    age: 25,
    email: "alice@test.com"
};

Function types

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;
console.log(add(10, 5));       // 15
console.log(subtract(10, 5));   // 5

5. Type narrowing

typeof guard

아래 코드는 typescript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

function processInput(input: string | number) {
    if (typeof input === "string") {
        return input.toUpperCase();
    } else {
        return input.toFixed(2);
    }
}

instanceof guard

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Dog {
    bark() {
        console.log("Woof!");
    }
}
class Cat {
    meow() {
        console.log("Meow!");
    }
}
function makeSound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}
makeSound(new Dog());  // Woof!
makeSound(new Cat());  // Meow!

in operator

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
    if ("swim" in animal) {
        animal.swim();
    } else {
        animal.fly();
    }
}

User-defined type guards

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

interface User {
    id: string;
    name: string;
}
interface Admin {
    id: string;
    name: string;
    permissions: string[];
}
function isAdmin(user: User | Admin): user is Admin {
    return "permissions" in user;
}
function greet(user: User | Admin) {
    if (isAdmin(user)) {
        console.log(`Admin ${user.name}, permissions: ${user.permissions.join(", ")}`);
    } else {
        console.log(`User ${user.name}`);
    }
}

6. Hands-on examples

Example 1: API response type

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

interface User {
    id: string;
    name: string;
}
type ApiResponse<T> = 
    | { success: true; data: T }
    | { success: false; error: string };
async function fetchUser(id: string): Promise<ApiResponse<User>> {
    try {
        const response = await fetch(`/api/users/${id}`);
        const data = await response.json();
        return { success: true, data };
    } catch (error) {
        return { success: false, error: "User not found" };
    }
}
// Usage
const result = await fetchUser("U001");
if (result.success) {
    console.log("User:", result.data.name);
} else {
    console.error("Error:", result.error);
}

Example 2: State machine

다음은 typescript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

type State = 
    | { status: "idle" }
    | { status: "loading" }
    | { status: "success"; data: any }
    | { status: "error"; error: string };
function handleState(state: State) {
    switch (state.status) {
        case "idle":
            console.log("Idle");
            break;
        case "loading":
            console.log("Loading...");
            break;
        case "success":
            console.log("Data:", state.data);
            break;
        case "error":
            console.error("Error:", state.error);
            break;
    }
}
// Usage
handleState({ status: "idle" });
handleState({ status: "loading" });
handleState({ status: "success", data: { name: "Alice" } });
handleState({ status: "error", error: "Network error" });

7. Common mistakes

Mistake 1: Misusing unions

아래 코드는 typescript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ Wrong
function getLength(value: string | number) {
    return value.length;  // error: number has no length
}
// ✅ Correct
function getLength(value: string | number) {
    if (typeof value === "string") {
        return value.length;
    }
    return value.toString().length;
}

Mistake 2: Conflicting intersections

아래 코드는 typescript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ Conflicting property types
type A = { value: string };
type B = { value: number };
type C = A & B;  // value becomes never
// ✅ Compatible intersection
type A = { name: string };
type B = { age: number };
type C = A & B;  // { name: string; age: number }

Summary

Takeaways

  1. Union: A | B (either)
  2. Intersection: A & B (all of)
  3. Literal: exact value types
  4. Type alias: reusable names
  5. Narrowing: refine unions safely

Next steps


... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3