SyntaxStudy
Sign Up
TypeScript Literal Types and Type Narrowing
TypeScript Beginner 1 min read

Literal Types and Type Narrowing

Literal types allow you to specify that a value must be exactly a particular string, number, or boolean, rather than any value of that primitive type. For example, the type "left" | "right" | "center" only accepts those three string values. Literal types are the foundation of discriminated unions and are heavily used to model state machines and configuration options. Type narrowing is the process of refining a broad type to a more specific one within a conditional block. TypeScript tracks these refinements through control flow analysis. Common narrowing techniques include typeof checks, truthiness checks, equality comparisons, the in operator for checking object properties, and instanceof checks for class instances. The satisfies operator, introduced in TypeScript 4.9, lets you validate that a value matches a type while still preserving the most specific inferred type. This is different from a type annotation, which widens the type. These features together give you precise type information without sacrificing type safety.
Example
// Literal types
type Direction = "north" | "south" | "east" | "west";
type DiceRoll  = 1 | 2 | 3 | 4 | 5 | 6;
type Done      = true; // only the value true

function move(dir: Direction, steps: number): string {
    return `Move ${steps} steps ${dir}`;
}

// Literal type inference — use 'as const' to preserve literals
const config = {
    endpoint: "https://api.example.com",
    retries: 3,
} as const;
// config.retries is 3, not number

// Narrowing with typeof
function format(value: string | number): string {
    if (typeof value === "string") {
        return value.trim();      // string here
    }
    return value.toFixed(2);      // number here
}

// Narrowing with 'in'
type Cat = { meow: () => void };
type Dog = { bark: () => void };

function makeSound(animal: Cat | Dog): void {
    if ("meow" in animal) {
        animal.meow();
    } else {
        animal.bark();
    }
}

// satisfies — validate type, keep narrow inference
const palette = {
    red:   [255, 0, 0],
    green: "#00ff00",
} satisfies Record<string, string | number[]>;

// palette.red is number[], not string | number[]
const first = palette.red[0]; // number