TypeScript Literal Types Explained
A literal type is a type that represents exactly one value. Learn how string, number, and boolean literal types work, why they matter with union types, and when to use const assertions.
A literal type is a type that represents exactly one value. Instead of the general string type, which matches any string, a string literal type only matches the exact string. The same idea works for numbers and booleans.
Literal types let TypeScript check that the right specific value is used, not just the right general category.
Literal types matter because they are the foundation for precise function signatures, discriminated unions, and configuration objects where values come from a known set. They catch typos at compile time instead of relying on runtime validation.
const theme = "dark";
// Type: "dark"
const maxRetries = 3;
// Type: 3With const, TypeScript infers the narrowest possible type: the literal itself. It knows the value will never change, so the exact value is a safe assumption. With let, TypeScript widens to the general type because reassignment is possible.
For more on this difference, see Let and const in TypeScript.
Literal Types with Unions
A literal type alone limits a variable to one value, which is rarely useful on its own. The power comes from combining them into unions. A union of string literals creates a type that accepts only a specific set of values.
function setSize(size: "small" | "medium" | "large") {
console.log(`Size set to ${size}`);
}
setSize("small"); // OK
setSize("huge");
// Error: '"huge"' is not assignable to '"small" | "medium" | "large"'The function only accepts three specific strings. Any other value is a compile error. This replaces runtime validation with a compile-time check.
Typos and incorrect values are caught before the code runs, with no need for if statements or switch cases to validate input.
String literal unions model anything with a fixed set of names: button variants, HTTP methods, log levels, or status values. Numeric literal unions work the same way and are useful for return values with known meanings, like a comparison function that returns negative one, zero, or one. Boolean literals are the simplest case: true and false, with the boolean type itself being their union.
Using As Const for Objects and Arrays
For objects and arrays, const alone does not give literal types. TypeScript widens each property to its general type because properties can be reassigned even on a const object. The as const assertion forces those exact values and readonly on every level.
const config = {
theme: "dark",
version: 2,
} as const;
// Type: { readonly theme: "dark"; readonly version: 2 }Without as const, the theme property would be string and version would be number. The assertion preserves the exact values and makes every property readonly. This is the standard pattern for configuration objects that need both runtime access and compile-time precision.
Arrays become readonly tuples with as const. Each element keeps its literal type, and you cannot push or reassign. You can then derive a union type from the array, keeping a single source of truth:
const sizes = ["small", "medium", "large"] as const;
type Size = (typeof sizes)[number];
// Type: "small" | "medium" | "large"The type expression extracts the union of all element types from the readonly tuple. You maintain the array for runtime use and the type for compile-time checks from one place.
Literal Inference and Common Surprises
TypeScript does not infer literal types for object properties by default, even on const objects. This is intentional: the compiler assumes properties might change, so it widens them. This leads to a common surprise when passing an object to a function that expects literal types.
const req = {
url: "https://example.com",
method: "GET",
};
function handle(url: string, method: "GET" | "POST") {}
handle(req.url, req.method);
// Error: 'string' not assignable to '"GET" | "POST"'The method property is inferred as string, not "GET". TypeScript does not know it will stay "GET" forever, because code between the object creation and the function call could reassign it. The fix is as const on the object, which locks every property to its literal type.
When to Use Literal Types
Literal types shine when a value must come from a known set. Use them for function parameters that only accept specific options, for return values with documented meanings, and for configuration objects that should not drift.
They are compile-time only and disappear completely in the JavaScript output. There is zero runtime cost.
For the reverse process, when TypeScript widens a value and you want more precision, see Type widening in TypeScript. For making values immutable alongside literal types, see Readonly values in TypeScript.
Rune AI
Key Insights
- A literal type represents exactly one value, like "GET" or 200.
- const variables get literal types automatically because their value is fixed.
- Combine literal types into unions to restrict parameters to a specific set of values.
- as const marks an entire object or array with literal and readonly types.
- Literal types are compile-time only and are erased before the code runs.
Frequently Asked Questions
What is the difference between string and a string literal type?
When should I use literal types?
Do literal types exist at runtime?
Conclusion
Literal types let TypeScript track exact values instead of just general categories. A const variable gets a literal type automatically because its value cannot change. When you combine literal types with unions, you get precise function signatures that reject invalid inputs at compile time. Use as const when you need literal types on object properties or arrays, and use literal union types whenever a value must come from a known set.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.