as const — Precision on Demand
Intermediate · 18 min read · ▶ live playground · ✦ checkpoint
as const tells TypeScript to keep a literal expression at its most precise type. Use it when ordinary inference is too wide: object properties should stay exact, arrays should become readonly tuples, or a real data list should become the source of a literal union.
Freeze the type, not the object
Plain const freezes the binding: you cannot reassign the variable. It does not make object properties literal or readonly:
const request = {
method: "GET",
path: "/users",
retryDelays: [100, 250],
} as const;
const badge = ["admin", 7] as const;
function routeLabel(method: "GET", path: string): string {
return method + " " + path;
}
function badgeLabel(pair: readonly [string, number]): string {
return pair[0] + ":" + pair[1];
}
console.log(routeLabel(request.method, request.path));
console.log(badgeLabel(badge));
console.log(Object.isFrozen(request));
console.log(Object.isFrozen(request.retryDelays));→ GET /users
→ admin:7
→ false
→ false
request.method is the literal type "GET", not plain string, so it can satisfy routeLabel's first parameter. The object properties are readonly at the type layer. badge is not (string | number)[]; it is a readonly tuple with two exact seats: "admin" at position 0, 7 at position 1.
The last two lines are the part people skip: both Object.isFrozen calls print false. as const does not call Object.freeze, does not lock the JavaScript object, and does not validate data from outside your program. It only changes what the checker remembers about the literal you wrote.
Here is the assignment error in its smallest form:
const request = {
method: "GET",
path: "/users",
} as const;
request.method = "POST";→ playground.ts:6:9 - error TS2540: Cannot assign to 'method' because it is a read-only property.
That readonly is useful because it protects the precision. If method could become "POST", the checker could not honestly keep calling it "GET".
Derive a union from data
The most common as const pattern is a list of allowed values that exists once at runtime and also becomes a type:
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number];
function labelRole(role: Role): string {
return role.toUpperCase();
}
console.log(ROLES.join(", "));
console.log(labelRole("editor"));→ admin, editor, viewer
→ EDITOR
Read the type line slowly:
typeof ROLESmeans "the type of the value namedROLES" because this is a type position.[number]means "the type you get when you index that tuple with a number."- The result is
"admin" | "editor" | "viewer".
This is just enough typeof and indexed access for the pattern. Section 10 will teach the larger type-manipulation toolbox; here the goal is simple: one source of truth.
And here is the emitted JavaScript:
const ROLES = ["admin", "editor", "viewer"];
function labelRole(role) {
return role.toUpperCase();
}
console.log(ROLES.join(", "));
console.log(labelRole("editor"));The type alias is gone. The as const assertion is gone. There is no enum object, no reverse mapping, no helper function. The array is still the array you wrote because you use it at runtime, but the type machinery costs nothing after compilation.
Use the data and the type together
This playground puts the runtime list and the derived type to work. Run it, then try the break-it line:
The loop gets real values from ROLES; the parameter type gets its allowed set from the same value. Add "owner" to the source literal and the type updates. Remove "editor" and calls using "editor" stop compiling. This is the maintenance win: the checker follows the data you already need.
There is one boundary to keep clear. as const is for trusted literals you authored. If a role arrives from a form, storage, or a server, it is not safe just because you have a Role type. That value must still be checked before you treat it like a Role; the runtime-boundary pattern waits for Section 13.
Use as const when the value is the source of truth. Use an annotation when you want to check a value against a shape while allowing normal widening. Next lesson handles the missing middle: satisfies, which checks a value without throwing away its useful precision.
Checkpoint
Answer all three to mark this lesson complete