Skip to content
exercises
TypeScript

satisfies in practice

Three real cases where it earned its keep.

BasicSelf

The satisfies operator (TypeScript 4.9+) validates that a value matches a type without widening the type. Three real cases where it’s better than an annotation.

The Problem with Annotations

const palette: Record<string, string> = {
primary: "#007bff",
secondary: "#6c757d",
};
palette.primary.toUpperCase(); // Error: string might not have toUpperCase

Wait, string definitely has toUpperCase. The problem: annotating as Record<string, string> widens the type so TypeScript forgets the specific keys. You lose autocomplete on palette.primary.

Case 1: Preserve Literal Types

const palette = {
primary: "#007bff",
secondary: "#6c757d",
} satisfies Record<string, string>;
palette.primary.toUpperCase(); // Works — TypeScript knows it's a string
palette.nonexistent; // Error — TypeScript knows the exact keys

satisfies validates the shape but keeps the inferred type.

Case 2: Config Objects

type Config = {
port: number;
host: string;
debug?: boolean;
};
const config = {
port: 3000,
host: "localhost",
extraField: true, // Error — not in Config
} satisfies Config;
config.port; // TypeScript infers number, not just Config['port']

Case 3: Tagged Union Members

type Action = { type: "INCREMENT"; amount: number } | { type: "RESET" };
const increment = {
type: "INCREMENT",
amount: 5,
} satisfies Action;
increment.amount; // number — not lost to the union type

satisfies vs. as const vs. Annotation

Approach Validates shape Preserves literal types
Annotation Yes No — widens
as const No Yes — too narrow
satisfies Yes Yes

Why It Matters

satisfies fills a gap between “I want type safety” and “I want precise autocomplete.” It’s small but it removes the choice between validation and precision.