Use satisfies to check authored values while retaining useful inference; use an annotation when the declared contract is the intended view. An assertion is a claim to the compiler, and none of these validates external data at runtime.
Choose the compiler relationship you intend
Do you want the variable exposed as the declared contract, or the initializer checked against that contract? That distinction determines whether a specific entry remains conveniently usable. Do not choose as just because a check produces an error: first find whether a required value is missing or an intended type is too broad.
| Syntax | Purpose | Variable's view |
|---|---|---|
| const x: T = value | Checked declaration | Declared T |
| value satisfies T | Compatibility check | Inferred expression |
| value as T | Type assertion | Asserted T |
Check a route map and observe the inferred entries
Compile this standalone file with strict checking. The two @ts-expect-error comments mark deliberate errors: the annotation exposes a union, and the satisfies initializer lacks health. If either line stops being an error, the directive itself fails. checked.home supports a string method, while checked.health exposes path.
The assertion accepts the incomplete object, and logging asserted.health prints undefined. It has not created the missing route. The next caller may fail despite a reassuring declared type. That is the difference between demonstrating compatibility and asserting knowledge the compiler cannot verify.
type RouteName = 'home' | 'health';
type RouteValue = string | { path: string };
type Routes = Record<RouteName, RouteValue>;
const annotated: Routes = {
home: '/', health: { path: '/health' }
};
const checked = {
home: '/', health: { path: '/health' }
} satisfies Routes;
const asserted = { home: '/' } as Routes;
checked.home.toUpperCase();
checked.health.path.toUpperCase();
// @ts-expect-error: the annotated property is a union
annotated.home.toUpperCase();
// @ts-expect-error: health is required
const missing = { home: '/' } satisfies Routes;
// Compiles, but asserted.health is undefined at runtime.
console.log(asserted.health);Do not promise that satisfies preserves every literal
In the worked example, checked.home is inferred as string, not necessarily the literal '/'. satisfies retains a useful expression type, but contextual typing and ordinary inference still matter. Test the specific property type you depend on instead of repeating a blanket claim about unchanged inference.
For a fresh object literal, checking a Record with the required route names also catches misspelled extra keys. This does not turn TypeScript's structural type system into universal exact-object validation. Moving a value through another variable can change which excess-property checks apply.
Add as const only when literal and readonly inference fit
This separate example retains the exact home path and makes the properties readonly at the type level while checking required keys. The assignment error is intentional. Compile it separately from the first example, since both declare RouteName. Unlike an assertion to Routes, as const requests a specific inference treatment of the literal.
Choose it when those constraints help consumers, not as a ritual for every configuration. It is not runtime freezing: JavaScript objects still need a separate runtime policy if mutation must be prevented.
type RouteName = 'home' | 'health';
const routes = {
home: '/', health: '/health'
} as const satisfies Record<RouteName, string>;
const exactHome: '/' = routes.home;
// @ts-expect-error: as const makes this property readonly
routes.home = '/new';Validate external input before using it
An HTTP response or parsed JSON may disagree with its claimed shape. Pass it as unknown to a real check. This function verifies only the path field required by its consumer; it returns /health for the shown input and throws for null or a numeric path. A richer contract needs corresponding runtime checks.
Annotating a parsed value, asserting it as a route, or checking a value already typed any with satisfies does not inspect the payload. Keep the compile-time authoring check and runtime trust boundary explicit.
function readPath(value: unknown): string {
if (
typeof value !== 'object' || value === null ||
!('path' in value) || typeof value.path !== 'string'
) {
throw new Error('Expected an object with a string path');
}
return value.path;
}
console.log(readPath({ path: '/health' }));A ten-minute review exercise
Add an admin route to RouteName and predict which initializers fail. Restore all required keys, then add a misspelled extra key to a fresh satisfies literal. Finally compare the types of home in the first example and the as const example. Explain why none of these proves a server returned a valid path.
Review the linked TypeScript interview questions, then describe each choice using its purpose and one rejected alternative. A useful answer identifies the contract and the runtime boundary, not merely the syntax.
Quick answers
Frequently asked questions
Is satisfies safer than as?
For checking an authored value against a contract, satisfies performs a compatibility check. as changes the compiler's view and can hide missing data, as the route example shows. Neither checks runtime payloads.
Does satisfies replace type annotations?
No. An annotation is useful when you deliberately want the declared contract to be the variable's view. satisfies is useful when checking the initializer while keeping more specific entry types.
Does satisfies make an object immutable?
No. as const can add readonly and literal inference for an authored literal, but even that does not freeze the object at runtime.
Source notes
References and review policy
Information checked on October 4, 2026. Section links identify sources for factual claims and technical explanations. Interpretations, practice scenarios and preparation recommendations are RecallDeck’s editorial work.
From reading to recall
Practice the full interview loop.
RecallDeck schedules the concepts you miss and keeps coding, design, and behavioral fundamentals available when the interviewer changes direction.