Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Cheat Sheets
  3. TypeScript
  4. TypeScript satisfies Operator Cheat Sheet
TypeScriptCheat Sheet

TypeScript satisfies Operator Cheat Sheet

Validate types without widening them -- satisfies vs annotations, satisfies vs as, config patterns, and when not to use it.

On this page
  1. 1The Problem: Type Widening
  2. 2satisfies vs Type Annotation
  3. 3satisfies vs as
  4. 4Preserving Literal Types
  5. 5Config Object Pattern
  6. 6satisfies with Record
  7. 7When NOT to Use satisfies
The Problem: Type Wideningsatisfies vs Type Annotationsatisfies vs asPreserving Literal TypesConfig Object Patternsatisfies with RecordWhen NOT to Use satisfies

The Problem: Type Widening

Type annotations widen values to the annotated type, losing specific literal information. The `satisfies` operator checks the type without widening.

Annotation widens the type
// With type annotation: TypeScript widens to Record<string, string>
const routes: Record<string, string> = {
  home: '/',
  about: '/about',
  blog: '/blog',
};

routes.home;    // type: string (not '/')
routes.oops;    // type: string -- no error, even though 'oops' doesn't exist!
satisfies preserves the literal type
// With satisfies: TypeScript validates AND preserves the exact type
const routes = {
  home: '/',
  about: '/about',
  blog: '/blog',
} satisfies Record<string, string>;

routes.home;    // type: '/' (literal preserved!)
// routes.oops; // Error: Property 'oops' does not exist
//              // TypeScript knows the exact keys
Side-by-side comparison
type ColorMap = Record<string, [number, number, number]>;

// Annotation: loses key information
const a: ColorMap = { red: [255, 0, 0], green: [0, 128, 0] };
a.red;      // type: [number, number, number]
a.purple;   // type: [number, number, number] -- no error

// satisfies: validates shape, keeps exact keys and values
const b = { red: [255, 0, 0], green: [0, 128, 0] } satisfies ColorMap;
b.red;      // type: [number, number, number]
// b.purple; // Error: Property 'purple' does not exist

This is the core tradeoff: type annotations give you the annotated type, satisfies gives you the inferred type after validation.

satisfies vs Type Annotation

A type annotation says "treat this as type T." The satisfies operator says "verify this matches T, but keep the inferred type."

Annotation: the value IS the type
type Theme = {
  primary: string;
  secondary: string;
};

// Annotation widens values to string
const theme: Theme = {
  primary: '#3b82f6',
  secondary: '#10b981',
};

theme.primary;  // type: string (not '#3b82f6')
satisfies: the value MATCHES the type
type Theme = {
  primary: string;
  secondary: string;
};

// satisfies validates but preserves literals
const theme = {
  primary: '#3b82f6',
  secondary: '#10b981',
} satisfies Theme;

theme.primary;  // type: '#3b82f6' (literal preserved)
Catching extra properties
type Config = { host: string; port: number };

// Annotation: catches extra properties
// const a: Config = { host: 'localhost', port: 3000, debug: true };
// Error: 'debug' does not exist in type 'Config'

// satisfies: also catches extra properties
// const b = { host: 'localhost', port: 3000, debug: true } satisfies Config;
// Error: 'debug' does not exist in type 'Config'

// Both catch excess properties. The difference is what type you get back.

Use annotation when you want consumers to see the abstract type. Use satisfies when you want consumers to see the specific value.

satisfies vs as

Type assertions (`as`) override the compiler. `satisfies` works with the compiler. One is a safety net, the other is a bypass.

as: trusting the developer
type Status = 'active' | 'inactive';

// as lets you lie to the compiler
const status = 'invalid' as Status;   // No error!
// status is type Status, but the value is 'invalid' at runtime

// as bypasses type checking -- use only when you know more than TS
satisfies: trusting the compiler
type Status = 'active' | 'inactive';

// satisfies catches the mistake
// const status = 'invalid' satisfies Status;
// Error: '"invalid"' does not satisfy 'Status'

const status = 'active' satisfies Status;  // OK
// status is type 'active' (narrower than Status)
Real-world: parsing JSON
type User = { name: string; age: number };

const raw = JSON.parse('{"name":"Alice","age":30}');

// as: compiles, but raw could be anything at runtime
const userA = raw as User;

// satisfies doesn't help here -- raw is typed as 'any'
// You need runtime validation (Zod, Valibot, etc.) for JSON parsing

// The right tool for JSON: runtime validation, not type-level operators

Neither `as` nor `satisfies` validates data at runtime. For user input, API responses, or parsed JSON, use a runtime validation library.

Preserving Literal Types

The biggest win for `satisfies`: keeping string, number, and boolean literals exact while still validating the overall shape.

Preserving string literals in a config
type Level = 'debug' | 'info' | 'warn' | 'error';
type LogConfig = { level: Level; prefix: string };

// Annotation widens level to Level
const logA: LogConfig = { level: 'warn', prefix: '[APP]' };
logA.level;  // type: Level (could be any of the 4)

// satisfies preserves 'warn' as a literal
const logB = { level: 'warn', prefix: '[APP]' } satisfies LogConfig;
logB.level;  // type: 'warn' (exact)
Preserving tuple types
type RGB = [number, number, number];
type Palette = Record<string, RGB>;

const palette = {
  ocean: [0, 119, 190],
  sunset: [255, 99, 71],
} satisfies Palette;

// Each value is inferred as a readonly tuple, not number[]
palette.ocean;   // type: [number, number, number]
palette.sunset;  // type: [number, number, number]
Preserving discriminated union members
type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number };

// Annotation: TypeScript only knows it's some Shape
const shapeA: Shape = { kind: 'circle', radius: 5 };
// shapeA.radius;  // Error: 'radius' doesn't exist on Shape

// satisfies: TypeScript knows it's specifically a circle
const shapeB = { kind: 'circle', radius: 5 } satisfies Shape;
shapeB.radius;  // OK: type number (TS knows it's the circle variant)

Config Object Pattern

The most common use of `satisfies` in practice: validating configuration objects while keeping autocomplete on exact keys and values.

Route configuration
type RouteConfig = {
  path: string;
  auth: boolean;
  roles?: string[];
};

const routes = {
  dashboard: { path: '/dashboard', auth: true, roles: ['admin', 'user'] },
  login:     { path: '/login', auth: false },
  settings:  { path: '/settings', auth: true, roles: ['admin'] },
} satisfies Record<string, RouteConfig>;

// Autocomplete works for route keys
routes.dashboard.path;  // type: '/dashboard' (literal)
routes.dashboard.roles; // type: string[] (known to exist)

// Typos caught at compile time:
// routes.dashbord;  // Error: did you mean 'dashboard'?
Feature flags
type FeatureFlag = {
  enabled: boolean;
  rolloutPercent?: number;
  description: string;
};

const flags = {
  darkMode:      { enabled: true, description: 'Dark theme toggle' },
  newCheckout:   { enabled: false, rolloutPercent: 25, description: 'Redesigned checkout flow' },
  betaDashboard: { enabled: true, rolloutPercent: 100, description: 'New analytics dashboard' },
} satisfies Record<string, FeatureFlag>;

// Type-safe access with autocomplete on flag names
if (flags.darkMode.enabled) {
  // TypeScript knows darkMode.enabled is true (literal)
}
i18n translations
type Translations = Record<string, string>;

const en = {
  greeting: 'Hello',
  farewell: 'Goodbye',
  error_not_found: 'Page not found',
} satisfies Translations;

const fr = {
  greeting: 'Bonjour',
  farewell: 'Au revoir',
  error_not_found: 'Page non trouvee',
} satisfies Translations;

// Both validated as Translations, but you still get
// autocomplete on exact keys like en.greeting

satisfies with Record

Record types are where `satisfies` shines brightest. Annotations erase your keys; `satisfies` keeps them.

Record loses keys with annotation
// Annotation: any string key is valid
const colors: Record<string, string> = {
  red: '#ff0000',
  green: '#00ff00',
  blue: '#0000ff',
};

colors.red;       // OK: type string
colors.banana;    // OK: type string (no error -- bad!)
Record keeps keys with satisfies
// satisfies: only declared keys are valid
const colors = {
  red: '#ff0000',
  green: '#00ff00',
  blue: '#0000ff',
} satisfies Record<string, string>;

colors.red;       // OK: type '#ff0000'
// colors.banana; // Error: 'banana' does not exist
Constrained keys with satisfies
type Theme = 'light' | 'dark' | 'system';

// Validate all required keys exist
const themeLabels = {
  light: 'Light Mode',
  dark: 'Dark Mode',
  system: 'System Default',
} satisfies Record<Theme, string>;

// If you forget one:
// const incomplete = {
//   light: 'Light Mode',
//   dark: 'Dark Mode',
// } satisfies Record<Theme, string>;
// Error: Property 'system' is missing

With a finite key union like `Theme`, `satisfies Record<Theme, string>` ensures every key is present. This is a compile-time exhaustiveness check for object keys.

When NOT to Use satisfies

satisfies is not always the right choice. Here are situations where a type annotation or `as const` works better.

When consumers should see the abstract type
type Config = { host: string; port: number };

// If a function returns Config, annotation is better:
function getConfig(): Config {
  return { host: 'localhost', port: 3000 };
}
// Callers see Config, not { host: 'localhost'; port: 3000 }
// This keeps the public API stable when implementation changes

// satisfies on a return value doesn't affect the return type:
function getConfig2() {
  return { host: 'localhost', port: 3000 } satisfies Config;
}
// Return type is { host: string; port: number } -- same shape,
// but now callers depend on the implementation details
When as const is enough
// If you just want literals without validation:
const directions = ['north', 'south', 'east', 'west'] as const;
// type: readonly ['north', 'south', 'east', 'west']

// satisfies adds nothing here unless you also need shape validation:
const directions2 = ['north', 'south', 'east', 'west'] as const
  satisfies readonly string[];
// Same literals, but validated as string array
When the type is already narrow enough
// Don't add satisfies just because you can:
const name: string = 'Alice';           // fine -- 'Alice' literal not useful here
const count: number = 42;               // fine -- 42 literal not useful here

// satisfies shines when the annotation would erase USEFUL specificity.
// If you don't need the literal type downstream, an annotation is simpler.

Reach for `satisfies` when you need both validation and literal preservation. If you only need one of those, a type annotation or `as const` is simpler and communicates intent more clearly.

Learn TypeScript in Depth
satisfies Operator →
See Also
Template Literal Types →Interface vs Type →

Start Practicing TypeScript

Free daily exercises with spaced repetition. No credit card required.

← Back to TypeScript Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.