Advanced Types: Mastering the Type System

Advanced Types: Mastering the Type System

Once you understand the basics of TypeScript, you can leverage its more advanced features to handle complex logic, data transformations, and state management with extreme precision.


1. Type Narrowing and Type Guards

TypeScript is excellent at narrowing down a broad type (like string | number) into a specific one based on your logic.

  • typeof: Used for primitive types.
  • instanceof: Used for class instances.
  • User-Defined Type Guards: Using the is keyword.
function isString(value: unknown): value is string {
    return typeof value === "string";
}

function process(input: string | number) {
    if (isString(input)) {
        console.log(input.toUpperCase()); // TS knows input is string
    }
}

2. Discriminated Unions

A Discriminated Union uses a common property (a "literal" or "tag") to distinguish between different types in a union. This is the gold standard for state management (e.g., Redux actions).

interface Success {
    status: "success";
    data: string[];
}

interface Failure {
    status: "error";
    message: string;
}

type ApiResponse = Success | Failure;

function handleResponse(response: ApiResponse) {
    if (response.status === "success") {
        console.log(response.data); // OK
    } else {
        console.log(response.message); // OK
    }
}

3. Mapped Types and Utility Types

TypeScript provides several built-in "Utility Types" that allow you to transform one type into another.

  • Partial<T>: Makes all properties in T optional.
  • Readonly<T>: Makes all properties in T read-only.
  • Pick<T, K>: Creates a type by picking a set of properties K from T.
  • Omit<T, K>: Creates a type by removing properties K from T.
  • Record<K, T>: Creates an object type with keys K and values T.
interface Todo {
    title: string;
    description: string;
    completed: boolean;
}

// Partial
const update: Partial<Todo> = { completed: true };

// Pick
type TodoPreview = Pick<Todo, "title" | "completed">;

4. Conditional Types

Conditional types allow you to select one of two types based on a condition expressed as a type relationship test.

type IsString<T> = T extends string ? "Yes" : "No";

type A = IsString<string>; // "Yes"
type B = IsString<number>; // "No"

5. Template Literal Types

Built on top of string literal types, these allow you to create complex string patterns.

type World = "world";
type Greeting = `hello ${World}`; // "hello world"

type Color = "red" | "blue";
type Intensity = "light" | "dark";
type Palette = `${Intensity}-${Color}`; // "light-red" | "light-blue" | "dark-red" | "dark-blue"

6. The keyof and typeof Operators

  • keyof: Takes an object type and produces a string or numeric literal union of its keys.
  • typeof: Used in a type context to refer to the type of a variable or property.
const config = { width: 100, height: 200 };
type ConfigKeys = keyof typeof config; // "width" | "height"

7. Summary

  • Type Guards make your logic safer by narrowing types.
  • Discriminated Unions provide a clear way to handle multiple states.
  • Utility Types save you from writing repetitive interface definitions.
  • Mapped and Conditional Types allow for advanced API and library design.