• _hello
  • _about-me
  • _projects
  • _blog
  • _support
_contact-me
find me in:
@faeztgh
cd ../blog~/blog/typescript-satisfies-operator
  • TypeScript
  • JavaScript

The TypeScript satisfies Operator, and When to Use It

The satisfies operator lets you validate a value against a type without widening it — keeping the narrow, literal types you actually want. Here's how it differs from a type annotation.

FFaez Tgh·Jun 20, 20264 min read·774 words
// share: post linkedin
Colorful TypeScript source code on a computer monitor

Here's a small annoyance every TypeScript developer eventually hits. You have an object, you want to guarantee it conforms to some shape, so you annotate it with that type. The check works — but now TypeScript has widened your value to the annotated type, and you've lost the precise information about what you actually wrote. The narrow literal types are gone, replaced by the broad type you were only using to validate.

The satisfies operator, introduced in TypeScript 4.9, resolves exactly this. It says: "check that this value is assignable to type T, but keep inferring its actual, narrower type." Validation without widening. Once you understand the distinction, you'll reach for it constantly.

The problem, concretely#

Say you're defining a color palette. Each value can be an RGB tuple or a hex string:

ts
type RGB = [red: number, green: number, blue: number];
type Color = RGB | string;

const palette: Record<string, Color> = {
  primary: [67, 217, 173],
  accent: '#4D5BCE',
};

This compiles. But watch what happens when you use it:

ts
palette.primary[0]; // ❌ Error — Color might be a string, which has no index
palette.accent.toUpperCase(); // ❌ Error — Color might be an RGB tuple

Because you annotated with Record<string, Color>, every value is now just Color — the union — as far as TypeScript is concerned. It has forgotten that primary is specifically a tuple and accent is specifically a string. The annotation validated the shape but flattened the detail.

The old workarounds, and why they're worse#

Before satisfies, you had two unsatisfying options.

Drop the annotation entirely and you keep the narrow types — but you lose the check that each value is a valid Color. Typo a value and nothing complains until much later.

Reach for as assertions and you're lying to the compiler, not asking it. as const freezes everything to readonly literals, which is sometimes right but often too aggressive, and it still doesn't validate against Color. Assertions in general suppress errors rather than catch them — the opposite of what you want here.

The fix: satisfies#

Replace the annotation with satisfies after the value:

ts
const palette = {
  primary: [67, 217, 173],
  accent: '#4D5BCE',
} satisfies Record<string, Color>;

palette.primary[0]; // ✅ number — tuple type preserved
palette.accent.toUpperCase(); // ✅ string method available

Both work now. TypeScript still checked that every value is assignable to Color — misspell a key's value as something that isn't a valid color and you get an error at the definition — but it inferred the specific type of each property instead of widening to the union. You get the guarantee and the precision.

A second everyday example#

The pattern shines any time you want a constrained object whose keys and value-types you then use precisely. Configuration and route maps are perfect:

ts
type RouteConfig = { path: string; auth?: boolean };

const routes = {
  home: { path: '/' },
  dashboard: { path: '/dashboard', auth: true },
} satisfies Record<string, RouteConfig>;

// Keys are known literals, not just `string`:
type RouteName = keyof typeof routes; // 'home' | 'dashboard'

With a plain Record<string, RouteConfig> annotation, keyof typeof routes would collapse to string and you'd lose the autocomplete and exhaustiveness that make the config worth having. satisfies keeps the literal keys.

When to use it — and when not#

Reach for satisfies when both of these are true: you want to validate a value against a type, and you want to keep using its narrower inferred type afterward. That covers most literal objects, config maps, lookup tables, and constant arrays.

It's not a replacement for annotations everywhere:

  • Function parameters still want annotations — you're describing the contract, not inferring from a value.
  • When you genuinely want the wider type — for instance a mutable variable you'll reassign to other members of the union — a normal annotation is correct.
  • as const still has its place. Combine them when you want deep readonly literals and validation: ... as const satisfies T.

The rule of thumb: annotation describes what a thing is allowed to be; satisfies checks what you wrote without forgetting what you wrote.

The takeaway#

satisfies closes a real gap that used to force a choice between safety and precision. It's a small operator with an outsized effect on how pleasant strongly-typed config and constant data feel to work with — validate against the shape, keep the exact types, move on.

FAQ#

What does the satisfies operator do in TypeScript?#

It checks that a value is assignable to a given type without changing (widening) the value's inferred type. You get the type-safety of validating against a type while keeping the precise, narrow types that TypeScript infers from the literal you wrote.

How is satisfies different from a type annotation?#

A type annotation (const x: T = ...) forces the value to be type T, widening away any narrower detail. satisfies (const x = ... satisfies T) verifies assignability to T but leaves the inferred narrow type intact, so you can still access tuple indices, string methods, or literal keys.

When was satisfies added, and can I use it everywhere?#

It was introduced in TypeScript 4.9. Use it for literal objects, config maps, and constant data where you want both validation and precise types. Keep normal annotations for function parameters and for mutable variables where you actually want the wider type.

Can I combine satisfies with as const?#

Yes. ... as const satisfies T gives you deeply readonly literal types and validates the value against T — a common pattern for constant configuration you want both frozen and type-checked.

References:

  • TypeScript 4.9 release notes — the satisfies operator
  • TypeScript Handbook

#on this page

  • The problem, concretely
  • The old workarounds, and why they're worse
  • The fix: satisfies
  • A second everyday example
  • When to use it — and when not
  • The takeaway
  • FAQ
  • What does the satisfies operator do in TypeScript?
  • How is satisfies different from a type annotation?
  • When was satisfies added, and can I use it everywhere?
  • Can I combine satisfies with as const?
Streaming AI Chat in Next.js with the AI SDK olderModel Context Protocol (MCP), Explained for Web Developersnewer

# related-posts

3 more
  • 01
    Discriminated Unions: TypeScript's Best Modelling ToolJul 4, 2026·4 min
  • 02
    Debounce vs Throttle: Taming Noisy Events in JavaScriptJun 5, 2026·5 min
  • 03
    TypeScript DecoratorsFeb 6, 2024·2 min