Menu

TypeScript Literal Types and as const, with Examples

A literal type is a type with exactly one value, like "GET" or 404. Learn string, number and boolean literal types, unions of literals, why let widens and const does not, what as const does, and const type parameters.

This page includes runnable editors - edit, run, and see output instantly.

A literal type is a type with exactly one value: "up" is a type whose only member is the string "up", and 404 is a type whose only member is the number 404. On their own they are not very useful. Joined into a union, they give you a variable that accepts a fixed set of values and nothing else.

Without the @ts-expect-error comment, the last call is a compile-time error (TS2345). With it, the program compiles and the call still runs and prints moving north by 1: literal types exist only for the compiler, and at runtime the value is an ordinary string.

String, Number and Boolean Literals

Any string, number, bigint or boolean value can be written as a type. The compiler then accepts only that exact value.

let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;

type Port = 80 | 443 | 8080;
const port: Port = 443;

boolean itself is just the union true | false, which is why narrowing a boolean with if (flag) leaves false in the else branch.

Literal typeAllowsWider type
"GET"only the string "GET"string
404only the number 404number
10nonly the bigint 10bigint
trueonly trueboolean

Unions of Literals

The common use is a union that lists every allowed value. Inside the function the compiler narrows the union as you check it, so each branch knows exactly which value it has.

A union of string literals is the usual TypeScript alternative to an enum. It costs nothing at runtime, the values are plain strings you can log and send over JSON, and a typo is a compile error. More on the trade-offs in enums.

Widening: let vs const

When TypeScript infers a type from a literal, it looks at whether the value can change. A const variable can never be reassigned, so it keeps the literal type. A let variable can, so its type is widened to the general type.

Hover over each name in the editor to see the inferred type. If you want a let that only holds certain values, annotate it: let mode: "light" | "dark" = "light".

Why Object Properties Widen

Properties of an object literal are mutable, so they widen too, even when the object is stored in a const. This is the most common way to meet literal types by accident:

The compiler reports:

index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

req is inferred as { url: string; method: string } because code later could run req.method = "DELETE". There are three fixes:

A fourth option is satisfies, which checks the object against a type while keeping its literal property types.

as const

as const is a const assertion. Put it after an expression and the compiler infers the narrowest type it can:

  • string, number and boolean values keep their literal types
  • object properties become readonly
  • array literals become readonly tuples with a fixed length

The assertion is compile-time only. The emitted JavaScript is the same object literal with as const removed, so nothing stops other code from mutating it at runtime. If you need a runtime guarantee, call Object.freeze as well.

A Union Type from an as const Array

A frequent pattern is to keep the allowed values in one array, which you can loop over at runtime, and derive the union type from it. (typeof arr)[number] means "the type of any element of arr".

Without as const, ROLES would be string[] and Role would be plain string. The cast to readonly string[] in isRole is needed because includes on a tuple of literals only accepts those literals, and the point of the function is to test a string that might not be one. The same object pattern works for key/value maps: const Status = { Active: "active", Banned: "banned" } as const, then type Status = (typeof Status)[keyof typeof Status].

const Type Parameters

A generic function normally widens the literals you pass it. Since TypeScript 5.0 you can mark a type parameter const, which makes the compiler infer the argument as if it had as const on it, without asking the caller to write it.

This is mostly a tool for library authors: route definitions, builders and schema helpers use it so callers get precise types from plain literals.

The Meanings of const

The keyword const shows up in four different places in TypeScript code:

SyntaxKindWhat it does
const x = 1JavaScript declarationthe binding cannot be reassigned; a literal value keeps its literal type
expr as constTypeScript assertionnarrowest type: literals, readonly properties, readonly tuples
function f<const T>()TypeScript type parameterinfers arguments as if they had as const
const enum E {}TypeScript enuman enum whose members are inlined at compile time

None of them freezes an object at runtime. const obj = { a: 1 } still allows obj.a = 2; only reassigning obj itself is an error.

Frequently Asked Questions

What is a literal type in TypeScript?

A type that allows exactly one value. "GET" is a type whose only value is the string "GET", 404 is a type whose only value is the number 404, and true is a type whose only value is true. They are most useful combined into unions, like type Method = "GET" | "POST".

What does as const do in TypeScript?

as const is a const assertion. It tells the compiler to infer the narrowest type for an expression: string and number values keep their literal types, object properties become readonly, and array literals become readonly tuples. It changes only the type; the value at runtime is the same plain object or array, and it is not frozen.

Why does TypeScript infer string instead of my literal?

Because the value is mutable. let x = "a" and the property in { method: "GET" } can be reassigned later, so TypeScript widens them to string. A const variable keeps the literal type "a". To keep literals inside an object, annotate it with the literal type, use as const, or use satisfies.

What is the difference between const and as const?

const is a JavaScript declaration: the variable cannot be reassigned, but the object it points to can still be changed. as const is a TypeScript type assertion: it makes every property of the value readonly and literal in the type system. Neither one freezes the object at runtime; use Object.freeze for that.

How do I get a union type from an array of strings?

Declare the array with as const, then index its type with number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; gives "admin" | "user". Without as const the array is string[] and the result is just string.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED