Difficulty: Beginner
Literal types in TypeScript allow you to specify the exact value a variable can hold, not just its general type. Instead of saying a variable is a `string`, you can say it's specifically `"hello"` or `"world"` and nothing else. This concept applies to strings, numbers, and booleans. Literal types are the foundation for creating precise, expressive type definitions that restrict values to a known set of options.
The most common use of literal types is combining them with union types to create a set of allowed values. For example, `type Direction = "north" | "south" | "east" | "west"` creates a type that only accepts those four specific strings. This is similar to enums but uses plain string values, making them fully compatible with JavaScript without any runtime overhead. Most TypeScript codebases prefer string literal unions over enums for this reason.
Literal types arise naturally from `const` declarations. When you write `const direction = "north"`, TypeScript infers the type as the literal `"north"` rather than `string`, because a const can never be reassigned. With `let direction = "north"`, TypeScript widens the type to `string` since the variable could later hold a different string. Understanding this widening behavior is key to working with literal types effectively.
Number literal types work the same way. You can define `type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6` to restrict a value to valid dice outcomes. Boolean literal types are less common on their own but appear in conditional types and discriminated unions. The power of literal types really shines when combined with type narrowing - TypeScript can track which specific literal value a variable holds through conditional branches, enabling very precise type checking.
type Status = "pending" | "active" | "archived";
type Theme = "light" | "dark" | "system";
function setStatus(status: Status): string {
return `Status set to: ${status}`;
}
function getBackground(theme: Theme): string {
switch (theme) {
case "light": return "#ffffff";
case "dark": return "#1a1a1a";
case "system": return "#f0f0f0";
}
}
console.log(setStatus("active"));
console.log(getBackground("dark"));
// setStatus("deleted"); // Error: Argument of type '"deleted"' is not assignable
String literal unions restrict a parameter to specific allowed values. TypeScript ensures exhaustive checking in switch statements - if you add a new status, the compiler warns about unhandled cases.
type HttpSuccessCode = 200 | 201 | 204;
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function describeStatus(code: HttpSuccessCode): string {
switch (code) {
case 200: return "OK";
case 201: return "Created";
case 204: return "No Content";
}
}
function rollDice(): DiceRoll {
return (Math.floor(Math.random() * 6) + 1) as DiceRoll;
}
console.log(describeStatus(200));
console.log(describeStatus(201));
console.log(typeof rollDice());
Number literal types restrict values to specific numbers. The switch statement is exhaustive - TypeScript ensures every literal is handled.
const exactColor = "red"; // Type: "red" (literal)
let flexibleColor = "red"; // Type: string (widened)
// Using literal type in a function
type Color = "red" | "green" | "blue";
function paint(color: Color): string {
return `Painting with ${color}`;
}
console.log(paint(exactColor)); // Works: "red" is assignable to Color
// paint(flexibleColor); // Error: string is not assignable to Color
const flexibleAsLiteral = flexibleColor as Color;
console.log(paint("blue"));
console.log(typeof exactColor);
const declarations produce literal types while let declarations widen to the general type. A const "red" is type '"red"', but let "red" is type 'string'. This affects assignability to literal type parameters.
String literal types, Number literal types, Boolean literal types, Union of literals