Suggest an editImprove this articleRefine the answer for “Literal types in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Literal types** in TypeScript restrict a variable to exact values like `"up"` or `404`, not just broad types like `string` or `number`. ```typescript type Status = "loading" | "success" | "error"; function setStatus(s: Status) {} setStatus("banana"); // Error - only the three values above are valid setStatus("success"); // OK ``` **Key point:** bad values are caught at compile time, not at runtime.Shown above the full answer for quick recall.Answer (EN)Image**Literal types** in TypeScript pin a variable to exact values - like `"up"` or `404` - instead of broad categories like `string` or `number`. ## Theory ### TL;DR - Literal types are like labeled parking spots: your variable fits only `"up"`, not any `string` - Main difference: `string` accepts `"banana"`; `"up" | "down"` rejects it at compile time - Use when you have 3-10 fixed options known at dev time - TypeScript widens literals in loose contexts (arrays, plain objects) unless you use `as const` ### Quick example ```typescript // Without literal - accepts any string type Status = string; function setStatus(s: Status) {} setStatus("banana"); // No error // With literal - only exact values type Status = "loading" | "success" | "error"; function setStatus(s: Status) {} setStatus("banana"); // Error: '"banana"' is not assignable to type 'Status' setStatus("success"); // OK ``` The compiler checks the exact string token, not just the type category. Bad values are blocked before the code runs. ### Key difference A plain `string` accepts any sequence of characters. `"loading" | "success" | "error"` accepts only those three tokens. You trade flexibility for a guarantee. TypeScript matches the exact value during type checking and erases everything at compile time, so the JavaScript output sees plain strings. No runtime cost. One thing that trips up most developers the first time: TypeScript widens literals automatically in some contexts. Write `{ dir: "up" }` and TypeScript infers `{ dir: string }`, not `{ dir: "up" }`. The literal gets lost. To keep it, use `as const`. ### When to use - UI states: `type Status = "draft" | "published" | "archived"` - HTTP status codes: `type Code = 200 | 201 | 404` - Alignment and config keys: `type Align = "left" | "center" | "right"` - Skip for user input (email, name) - use `string` - Skip for boolean flags - use `boolean`, unless you specifically need `true` only ### as const: keeping literals from widening ```typescript // Without as const - TypeScript widens to string[] const directions = ["up", "down"]; type Dir = typeof directions[0]; // string, not "up" // With as const - stays readonly ["up", "down"] const directions = ["up", "down"] as const; type Dir = typeof directions[number]; // "up" | "down" ``` This matters whenever you derive types from runtime arrays or config objects. ### How the compiler handles this TypeScript treats literal types as branded primitives during type checking. It matches the exact token `"up"` against union members. At compile time, literals become plain JS values - Node.js and the browser see only raw strings or numbers. No overhead. Widening happens in covariance contexts (array literals, return types) unless `as const` freezes the inference. ### Common mistakes **Forgetting `as const` on arrays** ```typescript const roles = ["admin", "user"]; // infers string[] type Role = typeof roles[number]; // string, not "admin" | "user" // Fix: const roles = ["admin", "user"] as const; type Role = typeof roles[number]; // "admin" | "user" ``` **Assuming literals enforce values at runtime** ```typescript function validate(code: 200 | 404) { return true; } // TypeScript erases types on compile. // JS sees: function validate(code) { return true; } // Fix: pair with Zod: z.union([z.literal(200), z.literal(404)]) ``` **Widening the return type by accident** ```typescript // Returns string, not "red" | "green" function getColor(type: "error" | "success"): string { return type === "error" ? "red" : "green"; } // Fix: pin the return type function getColor(type: "error" | "success"): "red" | "green" { return type === "error" ? "red" : "green"; } ``` If the value comes from user input or a database, it is `string` at runtime regardless of the literal type. Use a type guard or Zod to validate it. ### Real-world usage - React / shadcn-ui: `variant: "primary" | "outline"` in button props - tRPC: `type Method = "GET" | "POST"` in route definitions - TanStack Query: `"pending" | "success" | "error"` status types - Zod: `z.literal("published")` for API schema validation ### Follow-up questions **Q:** What is the difference between `type Foo = "a"` and `const foo = "a" as const`? **A:** `type` defines a type alias that exists only at the type level and disappears at compile time. `as const` creates a const variable whose inferred type is the literal `"a"`. Both give the type `"a"`, but `as const` also produces a runtime value. **Q:** Can you use literal types with generics? **A:** Yes. `function identity<T extends "foo" | "bar">(x: T): T` constrains the generic to a literal union. The caller gets back the exact literal, not a widened string. **Q:** When should you prefer an enum over a literal union? **A:** Literal unions compile away to nothing and are simpler to write. Enums generate a runtime object with reverse-mapping. For most cases in modern TypeScript, a literal union is enough. **Q:** How do discriminated unions use literal types? **A:** The literal field acts as a discriminant. TypeScript narrows the type automatically in each branch. ```typescript type ApiResponse<T> = | { status: "success"; data: T } | { status: "error"; error: string }; // Inside a handler: if (response.status === "success") { console.log(response.data); // TypeScript knows data exists here } ``` ## Examples ### Basic: direction handler ```typescript type Direction = "up" | "down" | "left" | "right"; function move(dir: Direction, distance: number) { console.log(`Move ${distance}px ${dir}`); } move("up", 10); // OK - logs "Move 10px up" move("left", 5); // OK move("diagonal", 3); // Error: '"diagonal"' is not assignable to type 'Direction' ``` TypeScript catches the invalid value before the code ships. ### Intermediate: button component ```typescript type ButtonVariant = "primary" | "secondary" | "danger"; interface ButtonProps { variant: ButtonVariant; label: string; } function Button({ variant, label }: ButtonProps) { return `<button class="btn btn-${variant}">${label}</button>`; } Button({ variant: "primary", label: "Save" }); // OK - renders btn-primary Button({ variant: "warning", label: "Save" }); // Error: '"warning"' is not assignable ``` The component only accepts variants that have matching CSS classes. No runtime surprises.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.