🔷 TypeScript Q72 / 101

What is satisfies operator?

AI-Powered Answer ✓ Answered

The `satisfies` operator, introduced in TypeScript 4.9, provides a powerful way to ensure that a type conforms to another type without actually *widening* its literal types. It's a useful tool when you want the benefits of type checking while retaining the most specific inferred types for your values.

What is `satisfies`?

The satisfies operator is a type operator that allows you to check if an expression's type is assignable to a given type. Crucially, it performs this check *without* changing the inferred type of the expression itself. This means you can get compile-time type safety while still benefiting from TypeScript's powerful type inference to preserve literal types.

Before satisfies, developers often faced a dilemma: either annotate an object with a type to get safety, which could widen its properties to less specific types (e.g., 'red' becoming string), or forgo the annotation to keep specific types but lose immediate type validation for the whole object.

The Problem Before `satisfies`

Consider a scenario where you want to define a style object that adheres to a StyleProperty interface, but also want TypeScript to remember the exact string literals for its property values.

typescript
type StyleProperty = {
    color: string;
    fontSize: string;
}

// Problem: This annotation widens 'color' and 'fontSize' to string, losing literal types
const myStyles_widened: StyleProperty = {
    color: 'red', // 'red' is widened to string
    fontSize: '16px' // '16px' is widened to string
};

// myStyles_widened.color has type 'string', not 'red'
const colorVal_widened = myStyles_widened.color; // Type: string

In the example above, by explicitly annotating myStyles_widened with StyleProperty, the literal types 'red' and '16px' are widened to string. This prevents TypeScript from offering specific type-based autocomplete or checks for those exact literal values later.

The Solution with `satisfies`

The satisfies operator allows us to assert that an object conforms to StyleProperty *without* coercing its type. TypeScript still infers the most specific literal types possible for its properties.

typescript
type StyleProperty = {
    color: string;
    fontSize: string;
}

const myStyles_narrowed = {
    color: 'red',
    fontSize: '16px'
} satisfies StyleProperty;

// myStyles_narrowed.color now has type 'red' (literal type)
const colorVal_narrowed = myStyles_narrowed.color; // Type: 'red'

// If we try to assign an invalid type, satisfies will catch it at compile-time:
// const invalidStyles = {
//     color: 123, // Error: Type 'number' is not assignable to type 'string'.
//     fontSize: '16px'
// } satisfies StyleProperty;

Here, myStyles_narrowed is checked against StyleProperty. If it doesn't conform, a compile-time error occurs. However, because satisfies doesn't *change* the type, myStyles_narrowed.color retains its literal type 'red', allowing for more precise type inference and tooling support.

Key Benefits

  • Type Safety without Type Widening: Ensures an object meets a certain type contract without losing the specificity of its literal types.
  • Improved IntelliSense and Autocompletion: With literal types preserved, editors can offer more accurate suggestions and catch more specific errors.
  • Better Refactoring: Changes to the expected type will correctly flag non-conforming objects, while the objects themselves retain their original inferred types.
  • Flexible Configuration: Ideal for defining configuration objects where values might be literal strings or numbers, but the overall structure needs to adhere to an interface.

When to use `satisfies`?

  • Configuration Objects: When you define a configuration object that should conform to a specific type, but you want to preserve the exact string or number literals for properties like keys, paths, or enum-like values.
  • API Definitions with Literal Types: When defining API payloads or parameters where certain fields are expected to be specific literal strings (e.g., 'success', 'error' for status fields).
  • Working with CSS-in-JS or Styled Components: Where property values might be specific strings (e.g., 'flex', 'block') but you still want to ensure they fit a general style object interface.
  • Ensuring Union Member Conformance: When you want to ensure an object conforms to *one* of the members of a union type, without losing information that might make it uniquely identifiable as that specific member.

In essence, satisfies bridges the gap between strict type annotation and powerful type inference, allowing developers to get the best of both worlds: compile-time type validation for structure, and highly specific inferred types for values.