TypeScript satisfies Operator: Type Checks Without Widening
AI generated
type
TypeScript
The satisfies Operator: Type Checking Without Widening
Validate object literals precisely without losing their literal types

Introduced in TypeScript 4.9, the satisfies operator closes a gap between explicit type annotations and as type assertions. It checks whether an expression matches a type while preserving the expression's own, narrower type.

9 min read TypeScript 4.9+ Compiler feature

1. The Problem: Annotation or Assertion

Before TypeScript 4.9, checking an object literal against a type meant choosing between two tools, and both came with drawbacks. An explicit type annotation such as const config: Config = { ... } reliably validates the structure, but it widens every property to the declared type. A field typed as string stays string, even if the actual value can only ever be "json" or "xml".

The alternative was a type assertion using as. It preserves the narrower literal types, but it completely skips structural validation by the compiler. Typos in property names or missing required fields only surface at runtime, if at all.

The satisfies operator combines both properties: it validates the expression against a type and reports errors for missing or incorrect fields, while still preserving the expression's own inferred type instead of widening it to the checked type.

2. Syntax Basics

The syntax is deliberately simple: an expression is followed by the satisfies keyword and a type. The compiler checks compatibility, but the resulting type of the variable remains the original, narrower type of the expression.

In the example below, a configuration object is checked against an interface. Despite the check, the type of route.method stays the literal "GET" rather than the wider string type.

This order, expression first and type second, deliberately sets satisfies apart from a type annotation, where the type comes before the expression. The compiler reads the operator as a check applied after the fact, not as a declaration fixed up front, which is exactly what affects how inference behaves.


interface RouteConfig {
  method: string;
  path: string;
  cache: boolean;
}

const route = {
  method: "GET",
  path: "/products",
  cache: true,
} satisfies RouteConfig;

// route.method has the type "GET", not string
function handle(method: "GET" | "POST") {
  // ...
}

handle(route.method); // works because "GET" stays a literal

3. Combining satisfies With as const

satisfies becomes especially useful together with as const. While as const fixes every property to its narrowest, immutable literal type, satisfies adds structural validation against an expected interface on top.

Without satisfies, a plain as const declaration leaves you on your own to make sure the shape actually matches the expected schema. With satisfies you get both at once: validation and maximum type precision.


type Theme = {
  primary: string;
  secondary: string;
  radius: number;
};

const theme = {
  primary: "#0f172a",
  secondary: "#e11d48",
  radius: 8,
} as const satisfies Theme;

// theme.radius has the type 8, not number
// theme.primary has the type "#0f172a", not string

4. Using satisfies With Record and Union Keys

A common use case is validating objects with a fixed set of keys, such as a map of feature flags or routes. With a Record type as the target, satisfies ensures every expected key is present without generalizing the concrete value types.

This pays off especially when you keep working with the individual values afterward, for instance in a switch statement that narrows on concrete literals.


type Feature = "checkout" | "wishlist" | "reviews";

const featureFlags = {
  checkout: true,
  wishlist: false,
  reviews: true,
} satisfies Record<Feature, boolean>;

// A missing or extra key is reported by the compiler immediately.

5. Configuration Objects and Function Return Values

In applications with many configuration objects, such as route definitions, API client options, or theme settings, satisfies pays off in a real way. Instead of giving every object its own type annotation that discards literal information, full precision is preserved while typos still get caught.

For functions that return configuration objects, satisfies can also be applied directly to the return expression, so callers benefit from the narrower types too.


interface ApiOptions {
  baseUrl: string;
  timeout: number;
  retries: number;
}

function buildOptions() {
  return {
    baseUrl: "https://mironsoft.de/api",
    timeout: 5000,
    retries: 3,
  } satisfies ApiOptions;
}

const options = buildOptions();
// options.timeout has the type 5000

6. Nested Structures and Arrays

satisfies isn't limited to flat objects, it also works with nested structures and arrays of objects. The compiler recursively checks every level of the structure against the given type while preserving the concrete literal types at each level.

That enables, for example, arrays of navigation entries where each entry still keeps its exact path string as a type, which adds extra type safety when combined with routing libraries.


interface NavItem {
  label: string;
  href: string;
  external?: boolean;
}

const navigation = [
  { label: "Magento", href: "/magento" },
  { label: "Hyva", href: "/hyva" },
  { label: "Blog", href: "https://mironsoft.de/blog", external: true },
] satisfies NavItem[];

7. Comparison to as and Explicit Annotations

The key difference from as lies in error checking. An as assertion essentially tells the compiler to trust you without performing a real structural check. If a required property is missing, as reports no error, while satisfies does.

The difference from an explicit type annotation lies in the resulting type. An annotation such as const x: Config = {...} widens the properties to the types declared in the interface. satisfies, on the other hand, still infers the type from the expression itself and only uses the given interface for validation.

In practice this means: choosing as trades away safety for control, while choosing a type annotation trades away precision for simplicity. satisfies asks for neither trade-off, which is exactly why it should be the default choice in most situations.

8. Best Practices for Real Projects

satisfies is a great fit for configuration objects, lookup tables, theme definitions, and anything where the exact literal values are used later on. For plain variables whose literal types are never used again, a normal annotation often remains sufficient.

In library code consumed by many callers, satisfies helps keep public constants as precisely typed as possible without redundantly redeclaring the internal structure.

9. Common Pitfalls

A frequent misunderstanding is that satisfies changes the declared type of the variable. That is not the case: the resulting type remains the inferred type of the expression, not the checked type. Anyone who explicitly wants the checked type as the variable's type still needs an additional annotation.

A second point concerns the minimum version: satisfies requires TypeScript 4.9 or newer. In older projects still running an earlier version, the syntax causes a parser error. Editor autocompletion also benefits from the feature: while typing inside the object literal, the editor already suggests the target type's fields, even though the resulting type stays narrower.

A third pitfall involves build pipelines using older transpilers that understand the rest of the TypeScript code but aren't yet built against the current language version. Upgrading the toolchain is worth checking before introducing satisfies.

Aspect Type Annotation as Assertion satisfies
Structural check Yes No Yes
Literal types preserved No Yes Yes
Missing required property Error No error Error
Resulting variable type Declared type Asserted type Inferred type of the expression
Minimum version All versions All versions TypeScript 4.9+

Mironsoft

TypeScript migration, type safety, and team onboarding

A JavaScript codebase without type safety, but no time for a full migration?

We migrate existing JavaScript projects to TypeScript step by step, set up strict compiler settings cleanly, and bring teams to the same type-safety level with code reviews and style guides.

Migration Roadmap

Plan and execute a gradual JS-to-TS migration without big-bang risk.

Strict Mode Rollout

Set up tsconfig.json, ESLint rules, and CI checks for lasting type safety.

Team Onboarding

Bring developers up to speed on TypeScript best practices with workshops and reviews.

10. Summary

Satisfies Operator

Available since

TypeScript 4.9

Runtime overhead

None, pure compiler feature

Combines well with

as const, Record, arrays

Typical use

Configuration objects, lookup tables

11. FAQ: Satisfies Operator

1What exactly does the satisfies operator do in TypeScript?
It checks whether an expression structurally matches a given type while preserving the narrower type inferred from the expression itself, instead of widening it to the checked type. That gives you validation and precision at the same time.
2Since which TypeScript version is satisfies available?
satisfies was introduced in TypeScript 4.9 in November 2022 and is available in every newer version since. Projects on an older compiler version need to upgrade first to use the syntax.
3How is satisfies different from a normal type annotation?
A type annotation widens the object's properties to the types declared in the type. satisfies only checks compatibility, so the resulting type remains the original, narrower type of the expression, which matters most for literal values.
4How is satisfies different from an as assertion?
An as assertion performs no real structural check and can leave missing or incorrect properties unnoticed. satisfies reports such errors already at compile time, making it the safer choice for new object literals.
5Can satisfies be combined with as const?
Yes, that combination is a common pattern. as const fixes every value to its narrowest literal type, and satisfies additionally checks the structure against an interface, so no required property can be forgotten.
6Does satisfies change the declared type of a variable?
No. The variable's type remains the type inferred from the expression. satisfies is used purely for validation, not for declaring a type, so an extra annotation is still needed if the checked type itself is wanted.
7Does satisfies also work with arrays and nested objects?
Yes, the compiler recursively checks every level of the structure against the given type while preserving the concrete literal types at each level, even for deeply nested configuration objects.
8When should satisfies be used instead of a normal annotation?
Whenever the exact literal values are used later on, for instance in switch statements or function parameters that expect concrete literals. For simple, one-off variables a normal annotation is often enough.
9Is there any runtime difference caused by satisfies?
No, satisfies is a purely compile-time feature with no effect at all on the generated JavaScript code or runtime behavior. The output is identical to a version without satisfies.
10Does satisfies improve editor autocompletion?
Yes, since the expression is still checked against the target type, the editor already suggests the target type's expected fields while you type the object literal, which noticeably speeds up development.