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.

5 min read

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.

typescripttypescript
const theme = "dark";
// Type: "dark"
 
const maxRetries = 3;
// Type: 3

With 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.

typescripttypescript
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.

typescripttypescript
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:

typescripttypescript
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.

typescripttypescript
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

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.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between string and a string literal type?

string accepts any string value. A string literal type like "active" only accepts the exact string "active". Literal types are narrower and let you restrict values to a specific set.

When should I use literal types?

Use literal types when a value should only be one of a specific set, like function options ("small" | "medium" | "large"), HTTP methods ("GET" | "POST"), or known string constants.

Do literal types exist at runtime?

No. Like all TypeScript types, literal types are erased during compilation. They are purely compile-time checks.

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.