Literal types
A literal type denotes exactly one value, such as the string "ready" or the number 404, and is a subtype of its base primitive. Use literal unions to model a value drawn from a fixed set, narrow to a specific one with a comparison, and freeze a whole structure with as const.
A literal type denotes exactly one value. Where string is every string, the literal type "ready" is only the string "ready", and nothing else. Each literal is a subtype of its base primitive.
type Status = "ready" | "done" | "error" // a union of string literals
type Code = 200 | 404 | 500 // number-literal singletons
If you have used TypeScript, these are the same literal types. They are how you model a value drawn from a fixed set of options: a status that is one of a few known strings, a response code that is one of a few known numbers, a flag that is exactly true or exactly false.
Assignability is one-directional
A literal is assignable to its base primitive and to any literal union that contains it. The base is not assignable back to a literal, because a plain string could hold any string, not just the one the literal names.
let s: string = "ready" // ok: the literal "ready" is a string
let r: "ready" = someString // error: a string is not necessarily "ready"
const t: Status = "pending" // error: "pending" is not a member of the union
This runs in the direction you would expect from primitive assignability: the more specific type flows into the more general one, never the reverse. The check happens at compile time.
Narrowing to a literal
Literals do their strongest work in narrowing. An if or switch whose test compares the value against a literal refines the value to that literal inside the matching branch:
function handle(s: Status) {
if (s === "ready") {
// here s is exactly "ready"
}
switch (s) {
case "done": return finish() // here s is "done"
case "error": return report() // here s is "error"
}
}
For this to work the subject must be a number, string, or boolean, and every case label a matching literal; anything else is a compile-time error. See type narrowing for the full set of checks. When a switch over a literal union handles every case, the leftover is never, which is how the type system confirms the coverage is exhaustive.
as const
By default an expression widens to its base type: const dir = "north" gives dir the type string, not "north". Writing as const stops that widening and does two things at once.
First, it narrows every contained literal to its singleton type instead of widening to the base. Second, it marks the whole structure deeply read-only, so nested objects and arrays are read-only too, and arrays become read-only tuples.
const dir = "north" as const // type "north", not string
const cfg = { mode: "dark", level: 3 } as const // { readonly mode: "dark"; readonly level: 3 }
const pt = [10, 20] as const // readonly [10, 20]
Unlike the general as cast, as const only ever makes a type more specific, so in principle it never needs a runtime check.
One honest caveat: as const is not accepted inside a runnable function body today. Written in an executable position, const dir = "north" as const is rejected — the assertion operator reports that the target is not proven by the source expression. The form above describes the intended type-level behavior; treat the three examples as type-level illustrations rather than code you can run now. Relatedly, even where the type is understood, the deep read-only marking is a type-level property: the runtime does not yet enforce the immutability, so a mutation the checker would reject is not blocked at runtime.
Why it works this way
Literal types let you pin a value to an exact member of a fixed set and have the type system check that only real members are used. The one-directional assignability is what keeps that guarantee honest: a general string cannot slip into a slot that expects a specific literal, because it might not be the right one. Narrowing then lets you recover the exact literal at runtime through an ordinary comparison, so code inside each branch knows precisely which value it is handling. as const extends the same precision to whole structures without ever loosening anything.
Limitations
- The base does not narrow to a literal on its own. A
stringis not assignable to"ready"without a runtime check that proves it. Narrow with a comparison rather than expecting an implicit refinement. as constis not usable in a runnable body yet. The construct is documented at the type level, but in an executable position the assertion is rejected today. Its deep read-only marking is likewise a compile-time property with no runtime freezing.