The satisfies operator — checking a shape without widening or narrowing it
Both an explicit type annotation and satisfies check that a value matches a shape — but an annotation replaces the value's inferred type with the annotation, while satisfies validates against the shape and then lets TypeScript keep inferring from the value's own actual, more specific type.
3 min read
The problem: an explicit type annotation throws away specific, useful information
type Config = Record<string, number | string>;
const settings: Config = {
width: 100,
label: "sidebar",
};
settings.width.toFixed(2); // TYPE ERROR — TypeScript only knows settings.width is `number | string`,
// NOT specifically `number`, even though the actual value clearly IS oneAnnotating settings as Config correctly checks that every property matches number | string — but it also means every property's type, from TypeScript's perspective, genuinely is number | string from that point on, not the more specific type each value actually has. settings.width is really always a number, but the annotation has already widened it to the union, losing that specificity — calling .toFixed() (a number-only method) now fails to type-check, even though the actual runtime value would handle it fine.
The fix: satisfies checks the shape, but doesn't replace the inferred type
const settings = {
width: 100,
label: "sidebar",
} satisfies Config;
settings.width.toFixed(2); // WORKS — TypeScript still knows settings.width is specifically `number`,
// because satisfies validated against Config WITHOUT widening the typesatisfies Config performs the exact same validation — every property must match number | string — but it does not change what TypeScript infers settings's type to be afterward; that inference still comes from the object literal itself, where width: 100 is inferred as the specific type number, not the broader number | string. The check happens, catches the same real mistakes an annotation would, but the more specific, more useful inferred type survives afterward.
Where satisfies genuinely earns its keep: catching a real typo, without losing autocomplete
type RouteConfig = Record<string, { path: string; method: "GET" | "POST" }>;
const routes = {
users: { path: "/users", method: "GET" },
createUser: { path: "/users", methd: "POST" }, // TYPO caught — "methd" isn't a valid key
} satisfies RouteConfig;
routes.createUser.path; // still autocompletes correctly, since routes' inferred type is its OWN literal shapeWithout satisfies, a typo like methd in an object literal with no type annotation at all would simply become part of the object's inferred shape — TypeScript wouldn't catch it, since nothing said what the object was supposed to look like. satisfies RouteConfig validates the object against the expected shape (catching methd immediately) while still letting routes's own type be inferred from the literal itself — which is specifically what keeps routes.createUser.path and similar property accesses fully precise and autocompleting correctly afterward, rather than falling back to RouteConfig's broader shape.
satisfies vs. a plain type annotation: the practical rule of thumb
// Use an ANNOTATION when you genuinely want the value treated as the broader type going forward
const handler: (event: Event) => void = (e) => { console.log(e); };
// Use SATISFIES when you want validation now, but the value's OWN specific
// inferred type to keep working normally afterward
const theme = { primary: "#2563eb", spacing: 8 } satisfies Record<string, string | number>;The practical distinction: reach for a plain annotation when the variable is genuinely meant to be used as the broader, annotated type from that point on (a function assigned to a specific callback signature). Reach for satisfies specifically when the goal is validating a literal against an expected shape while preserving that literal's own more specific inferred type for everything downstream — configuration objects, route tables, theme definitions, and similar "this needs to conform to a shape, but I still want precise autocomplete on it afterward" situations are exactly where satisfies earns its place over a plain annotation.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What's the key difference between annotating a variable with a type and using `satisfies` with that same type?
2. Why does `satisfies` catch a typo'd object key while still preserving autocomplete on the object afterward?
3. When is a plain type annotation the better choice over satisfies?