ESC

Type to search the knowledge base.

satisfies Operator

Check a value against a type while keeping the narrow inferred type — config objects, route maps, and as const without losing literals.

intermediate3 min read
  • typescript
  • satisfies-operator

Before satisfies, you chose between two pains:

type Colors = Record<string, string>;

// Annotation: checks shape, widens values to string
const palette: Colors = { primary: '#0f0', danger: '#f00' };
// palette.primary is string, not '#0f0'

// as const: keeps literals, no check against Colors
const palette2 = { primary: '#0f0', danger: '#f00' } as const;

satisfies validates against a type and preserves the inferred (often narrower) type of the expression.

const palette = {
  primary: '#0f0',
  danger: '#f00',
} as const satisfies Record<string, string>;

// palette.primary is '#0f0'
// typos / wrong value types still error

Docs: satisfies operator (TS 4.9+).

Config objects

type FeatureConfig = {
  enabled: boolean;
  rollout: number; // 0–100
};

const features = {
  darkMode: { enabled: true, rollout: 100 },
  newCheckout: { enabled: false, rollout: 10 },
} as const satisfies Record<string, FeatureConfig>;

type FeatureName = keyof typeof features; // 'darkMode' | 'newCheckout'

Add a feature missing rollout → compile error. Keys stay literal.

Route maps

type Route = { path: `/${string}`; auth: boolean };

const routes = {
  home: { path: '/', auth: false },
  settings: { path: '/settings', auth: true },
} as const satisfies Record<string, Route>;

Note: '/' may need a slightly looser path type depending on template constraints — adjust the Route type to match reality.

Event name maps

const handlers = {
  onClick: (e: MouseEvent) => console.log(e.clientX),
  onKeyDown: (e: KeyboardEvent) => console.log(e.key),
} satisfies {
  [K: string]: (event: Event) => void;
};

Each handler keeps its specific event parameter type under many TS versions when written carefully — validate in your version; the win is catching wrong handler shapes.

vs type annotation

Approach Validates Keeps literals / narrow keys
: Type Yes Often no (widens)
as Type No (forces) N/A — assertion
as const No Yes
satisfies Type Yes Yes
as const satisfies Type Yes Yes (strongest combo)

vs assertion

const bad = { primary: 123 } as Record<string, string>; // lies
const good = { primary: 123 } satisfies Record<string, string>; // error

Never use as when satisfies expresses “check me.”

Arrays of unions

type Role = 'admin' | 'user' | 'guest';

const roles = ['admin', 'user'] as const satisfies readonly Role[];
// cannot include 'superadmin'

When not to use it

  • Variable you want widened to the annotation type for mutability (let x: string = 'a').
  • Values that truly are the wide type.
  • Older TS (< 4.9) — upgrade or use intermediate checks.

Footguns

  1. Forgetting as const when you need readonly literal keys.
  2. satisfies on a widened expression — order matters; apply to the object literal.
  3. Expecting deep immutability — still a type-level tool.

Interview out-loud answer

“satisfies ensures a value matches a type without widening the inferred type. I use it for config and route maps with as const satisfies … so keys stay literal and missing fields fail compile. It’s a check, not a cast.”

Further reading

Related guides