TypeScript Literal Types

Literal types narrow a primitive down to one exact value, letting TypeScript check that only a specific, known set of values is ever used.

What Are Literal Types?

Normally `string` accepts any string and `number` accepts any number. A literal type narrows that down to a single exact value, like `"GET"` or `200`. Literal types are rarely used alone - they're almost always combined into a union so a variable can hold one of a small, closed set of values, and TypeScript will reject anything outside that set.

Basic Literal Unions

let direction: "up" | "down" | "left" | "right";
direction = "up";
// direction = "north"; // Error: not assignable

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function request(url: string, method: HttpMethod): void {
  console.log(`${method} ${url}`);
}

request("/users", "GET");

Literal Types with Discriminated Unions

Literal types become especially powerful when one property acts as a tag that distinguishes between several object shapes. This pattern, called a discriminated union, lets TypeScript narrow the whole object inside a `switch` or `if` just by checking that one literal field.

Discriminated Union

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
  }
}

console.log(area({ kind: "circle", radius: 2 }));

const Assertions

By default, TypeScript widens literals declared with `let` or inferred from array/object literals to their general type. Adding `as const` tells the compiler to keep the narrowest possible literal types instead, which is exactly what you want for fixed lists of routes, statuses, or configuration keys.

Deriving a Union from an Array

const routes = ["/home", "/about", "/contact"] as const;
type Route = (typeof routes)[number];

function navigate(route: Route): void {
  console.log(`Navigating to ${route}`);
}

navigate("/about");
// navigate("/missing"); // Error: not a valid Route
  • Discriminated unions for modeling shapes, actions, or states
  • Redux/reducer action `type` fields
  • CSS-like property values ("solid" | "dashed" | "dotted")
  • Configuration keys pulled from a fixed set of environment names
  • Template literal types built from string literal unions
Literal kindExample typeTypical use
String literal"GET" | "POST"HTTP methods, action tags
Numeric literal200 | 404 | 500Status codes
Boolean literaltrueStrict flags in config types
Template literal`on${string}`Event handler names
Note: If a literal seems to "widen" back to `string` or `number` unexpectedly, check whether the value came from a mutable `let` binding or a plain object/array literal - switching to `const` and `as const` usually restores the narrow type.
Note: Function parameters typed as a literal union still need a runtime check (like a switch with no fallthrough) if the value could come from outside your program - such as JSON from an API - because literal types are a compile-time guarantee only.

Exercise: TypeScript Literal Types

What values does a variable typed as the string literal "red" accept?