Literal Types and const Assertions
Constrain a value to an exact set, and stop TypeScript widening the types you wanted narrow.
What you'll learn
- Use a literal union to restrict a value to an exact set
- Explain why an object property widens, and fix it with `as const`
- Derive a union type from an existing object with `keyof` and `typeof`
Prerequisites
Explanation
A literal type is a type with exactly one possible value: "draft" is a type, and the only value it accepts is the string "draft". On its own that is useless. In a union it is one of TypeScript's sharpest tools:
type Alignment = "left" | "center" | "right";
"centre" is now a compile error rather than a silent no-op discovered by a user.
The widening problem
Lesson 2 introduced widening. Here is where it bites:
const config = { align: "center" };
// config.align is string, not "center"
The const applies to config, not to its properties — config.align can be reassigned, so TypeScript widens it to string. Passing config.align to something expecting Alignment is then rejected, which feels wrong until you see why.
as const
A const assertion freezes the whole structure into its narrowest form:
const config = { align: "center" } as const;
// config.align is "center", and readonly
Every property becomes readonly and every literal keeps its literal type. This is the standard fix, and it composes: as const on an array gives you a readonly tuple of literals rather than string[].
Deriving a union from data
Two operators let a type follow a value instead of being maintained alongside it.
typeof xin type position gives the type of the valuex.keyof Tgives a union ofT's keys.
Together:
const ROLES = { admin: 3, editor: 2, viewer: 1 } as const;
type Role = keyof typeof ROLES; // "admin" | "editor" | "viewer"
Read it inside-out: typeof ROLES is the object's type, keyof extracts its keys. Add a role to the object and the type updates itself. There is no second list to forget.
The as const matters here: without it, the values widen to number and you lose the exact levels — though keyof would still work for the keys.
as const is not a cast
as const narrows what a value is understood to be. It is not as SomeType, which asserts a type the compiler cannot verify and can be genuinely unsafe. Lesson 11 covers that distinction.
Example
Widening, the const-assertion fix, and a union derived from an object so the two can never disagree.
type Alignment = "left" | "center" | "right";
const widened = { align: "center" };
const narrowed = { align: "center" } as const;
function applyAlignment(a: Alignment): string {
return "text-align: " + a;
}
// applyAlignment(widened.align); // rejected: string is not Alignment
console.log(applyAlignment(narrowed.align));
const ROLES = { admin: 3, editor: 2, viewer: 1 } as const;
type Role = keyof typeof ROLES;
function levelFor(role: Role): number {
return ROLES[role];
}
console.log(levelFor("admin"), levelFor("viewer"));Try it yourself
Remove `as const` and Run. The error shows exactly what widening cost you.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Define `type Size = "small" | "medium" | "large"`. Write `sizeInPixels(size: Size): number` returning 12, 16, and 24 respectively.
Checks: sizeInPixels is defined · small and medium map correctly · large maps to 24
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Create `const ICONS = { save: "💾", trash: "🗑" } as const`, derive `type IconName = keyof typeof ICONS`, then write `iconFor(name: IconName): string` returning the matching icon.
Checks: ICONS maps save correctly · iconFor is defined · iconFor returns the matching icon
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Expecting `const obj = { a: "x" }` to give `a` a literal type. `const` protects the binding, not the properties — that needs `as const`.
- Maintaining a union and an object separately, then letting them drift. `keyof typeof` derives one from the other.
- Confusing `as const` with `as SomeType`. The first narrows what is already there; the second asserts something the compiler cannot check.
Knowledge check
Takeaway
Literal unions make invalid values unrepresentable; `as const` stops TypeScript widening away the precision you wanted.
Summary
Literal types restrict a value to an exact set. Object properties widen by default, which `as const` prevents, and `keyof typeof` derives a union from an existing object so the two can never fall out of sync.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.