as const, satisfies and literal inference: precise types without lying
Why TypeScript widens "md" to string, how as const keeps literal types, how satisfies checks a value without losing its type, and why as is an assertion, not a conversion.
Comes up in: Coding
Learn#
Hari’s design system has size: "sm" | "md" | "lg". He writes const props = { size: "md" } and passes it on. Error: string is not assignable to "sm" | "md" | "lg". TypeScript widened "md" to string, because object properties can change later.
A const assertion, as const, keeps the exact values: literal types, readonly properties, and fixed-length tuples. ["sm", "md", "lg"] as const has the type readonly ["sm", "md", "lg"].
Three ways to connect a value to a type behave differently:
type Routes = Record<string, { path: string }>;
const annotated: Routes = { home: { path: "/" }, cart: { path: "/cart" } };
const checked = { home: { path: "/" }, cart: { path: "/cart" } } satisfies Routes;
const asserted = JSON.parse('{"home": {}}') as Routes;
annotated.chekout.path; // no error: the annotation forgot which keys exist
checked.chekout.path; // error: satisfies kept the real keys
asserted.home.path.length; // no error, but it crashes at runtime
const typo = { home: { pth: "/" } } satisfies Routes; // error: still checkedexample.ts(8,9): error TS2339: Property 'chekout' does not exist on type '{ home: { path: string; }; cart: { path: string; }; }'.
example.ts(10,24): error TS2561: Object literal may only specify known properties, but 'pth' does not exist in type '{ path: string; }'. Did you mean to write 'path'?State
- annotated: Record<string, { path }>
Console
empty
With an annotation, the variable's type IS Routes. TypeScript forgets that only home and cart exist.
Read the steps as text
- With an annotation, the variable's type IS Routes. TypeScript forgets that only home and cart exist.
- With satisfies, TypeScript checks the value against Routes but keeps the type it inferred.
- With as, TypeScript believes you. Nothing is checked or converted at runtime.
- So checked.chekout is an error, annotated.chekout is not, and asserted.home.path crashes at runtime.
Predict#
Predict first
const n = '42' as unknown as number; console.log(n + 1) — what prints?
as changes only what TypeScript believes. At runtime n is still the string “42”, so + 1 joins strings. People expect a conversion because the syntax looks like a cast in other languages.
Interview angle#
Say it like this — about 30 seconds
“A type annotation sets the variable’s type, and the value must match it, but you lose the more precise inferred type. satisfies checks the value against a type and keeps the inferred type, so known keys and literals stay. as is a type assertion: it tells the compiler to trust me, checks almost nothing, and changes nothing at runtime. as const keeps literal values and makes everything readonly, which is how I derive union types from constant arrays.”
Follow-up What is the difference between satisfies, a type annotation and as?
Follow-up What does as const do?
Follow-up When is as acceptable?
Practice#
Guided
Make ROLES precise with as const, derive Role, and check routes with satisfies so its keys are kept.
Done when
Roleis"admin" | "editor" | "viewer", derived fromROLES.isRolereturns true only for those three strings.routeFortakeskeyof typeof routes, so a misspelled route name is a compile error.routesusessatisfies, not an annotation oras.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What does as const do to an array of strings?
Makes it a readonly tuple of the exact string literals.
satisfies vs annotation: what does satisfies keep?
The value's own inferred type, after checking it.
What does as do at runtime?
Nothing. It changes only what the compiler believes.
How do you get a union type from a const array?
(typeof arr)[number], with the array declared as const.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
const x = "md" has type "md"; let x = "md" is widened to string, because it can change. Object properties and array items widen too, since they are mutable.
Check yourself: Why is let x = 'md' typed string but const x = 'md' typed 'md'?
A let can be reassigned, so TypeScript widens it; a const cannot.
How it works underneathSenior rounds
as const makes arrays readonly tuples, so .push is a compile error and (typeof arr)[number] gives the union of items (TS.05.01).Check yourself: What is the type of ['a', 'b'] as const?
readonly ['a', 'b']
Edge cases and trapsWhere people slip
as unknown as T skips even that, and JSON.parse(text) as T is always allowed, because any matches everything.Check yourself: Why does '42' as number fail but '42' as unknown as number compile?
as rejects types with no overlap; going through unknown removes that last check.
In productionStaff-level answers
Use satisfies for config, routes, theme tokens and lookup tables: the shape is checked, and autocomplete still knows every key. Combine both: { … } as const satisfies Config gives literal types and a check Source: TypeScript 4.9: The satisfies operator.
Check yourself: Where is satisfies most useful?
Config objects and lookup tables you want checked but still precisely typed.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- What is the difference between
satisfies, a type annotation andas? - What does
as constdo?