Blog
3 min read

TypeScript satisfies Explained: Check a Type Without Losing Precision

The satisfies operator checks that a value matches a type while keeping the value's own, more specific type. How it differs from a type annotation and from as, the patterns where it shines (config objects, route maps, as const), and the mistakes to avoid.

TypeScript's satisfies operator answers a common frustration: you want TypeScript to check that an object has the right shape, but you don't want it to forget the exact values you wrote.

const config = { ... } satisfies Config

The problem with a type annotation

type Colors = Record<string, string | [number, number, number]>

const palette: Colors = {
  red: [255, 0, 0],
  green: '#00ff00',
}

palette.green.toUpperCase()
// ❌ Error: Property 'toUpperCase' does not exist on type 'string | [number, number, number]'

The annotation : Colors makes palette be a Colors. TypeScript now only knows each value is "a string or a tuple", even though you can see green is a string. It also forgets which keys exist — palette.blue type-checks fine and is undefined at runtime.

The fix: satisfies

const palette = {
  red: [255, 0, 0],
  green: '#00ff00',
} satisfies Colors

palette.green.toUpperCase()   // ✅ green is known to be a string
palette.red[0]                // ✅ red is known to be a tuple
palette.blue                  // ✅ Error: 'blue' doesn't exist

satisfies checks the value against Colors — a typo like gren: 123 is still an error — but the variable keeps its own precise type.

satisfies vs annotation vs as

Checks the value? Resulting type
const x: T = value Yes T (widened)
const x = value satisfies T Yes The value's own type (precise)
const x = value as T No — you're telling TypeScript to trust you T

as is an assertion: it silences errors instead of catching them. AI-generated code reaches for as a lot; satisfies is usually what was meant. (TypeScript for AI-Generated Code)

Patterns where it shines

Config objects — checked, with literal values kept:

type Env = 'development' | 'production'
type AppConfig = { env: Env; port: number; features: Record<string, boolean> }

export const config = {
  env: 'production',
  port: 8080,
  features: { newCheckout: true, betaSearch: false },
} satisfies AppConfig

config.features.newCheckout   // ✅ known key

Lookup tables that must cover every case:

type Status = 'draft' | 'published' | 'archived'

const statusLabel = {
  draft: 'Draft',
  published: 'Live',
  archived: 'Archived',
} satisfies Record<Status, string>
// Add a new Status and forget a label → compile error

With as const for fully literal, readonly data:

const routes = {
  home: '/',
  pricing: '/pricing',
  post: '/blog/:slug',
} as const satisfies Record<string, `/${string}`>
// routes.pricing has type '/pricing', and every value must start with '/'

Mistakes to avoid

  • Using it for function parameters or return types. Those already have annotations; satisfies is for values.
  • Expecting it to change the value. It's compile-time only — no runtime effect.
  • Thinking it validates external data. Data from an API or form is unchecked at runtime; use a validator like Zod. (Validate Input With Zod)

The summary

  • value satisfies Type checks the shape but keeps the precise inferred type.
  • Prefer it to as (which skips checking) and to annotations when you need the specifics.
  • Great for configs, lookup tables and as const data.

EasySpawn gives Claude Code a persistent workspace where tsc runs after every change, so type errors are caught and fixed before code reaches you. See how it works or join the waitlist.

Related: type vs interface · TypeScript Generics Explained · What Is TypeScript? · Type Is Not Assignable

Keep reading