Literals & Widening
A literal is a one-member set
Section titled “A literal is a one-member set”A literal type is a type whose only value is a single specific literal. "GET" is a type; the only value assignable to it is the string "GET". Same for 42, true, and so on.
let method: "GET" = "GET";method = "POST"; // ❌ Type '"POST"' is not assignable to type '"GET"'.On their own, literal types are rarely useful. Their power comes from unions of literals, which model “one of these exact values”:
type Method = "GET" | "POST" | "PUT" | "DELETE";type Dice = 1 | 2 | 3 | 4 | 5 | 6;This is how you replace loose string parameters with precise, self-documenting, autocompleting ones.
Widening: why literals keep disappearing
Section titled “Widening: why literals keep disappearing”TypeScript widens a literal to its general type in places where the value could change. The rule you met in Foundations: const keeps the literal, let widens.
const a = "GET"; // type: "GET"let b = "GET"; // type: string (widened — b is reassignable)The same widening happens to object properties, because object properties are mutable by default:
const config = { method: "GET" };// type: { method: string } — NOT { method: "GET" }
function send(m: "GET" | "POST") {}send(config.method); // ❌ string is not assignable to "GET" | "POST"Even though config is a const, its method property can be reassigned (config.method = "POST" is legal), so the compiler widens it to string. This surprises everyone once.
as const: freeze the narrow type
Section titled “as const: freeze the narrow type”A const assertion (as const) tells the compiler “treat this value as deeply immutable, and keep every literal narrow.”
const config = { method: "GET" } as const;// type: { readonly method: "GET" }
send(config.method); // ✅ now "GET" survivesas const does three things at once:
- Literals stay literal —
"GET"instead ofstring. - Properties become
readonly— reflecting the immutability you promised. - Arrays become
readonlytuples —[1, 2]becomesreadonly [1, 2], notnumber[].
flowchart TB
val["value: { method: 'GET' }"] -->|default| wide["type: { method: string }
(widened, mutable)"]
val -->|as const| narrow["type: { readonly method: 'GET' }
(literal, readonly)"] A very common use: literal config objects
Section titled “A very common use: literal config objects”as const shines for lookup tables and configuration where you want the exact keys and values in the type:
const ROUTES = { home: "/", profile: "/profile", settings: "/settings",} as const;
type Route = typeof ROUTES[keyof typeof ROUTES];// type: "/" | "/profile" | "/settings"Without as const, ROUTES would be { home: string; profile: string; settings: string } and you’d lose every specific path. With it, you can derive precise union types straight from the data — a pattern you’ll see constantly in the type-level module.