{"slug": "comparing-satisfies-type-annotations-and-assertions-through-compiler-output", "title": "Comparing satisfies, type annotations, and assertions through compiler output", "summary": "A developer compared TypeScript's `satisfies` operator against type annotations and `as` assertions using emitted declarations and compiler diagnostics under TypeScript 7.0.2. The experiment showed that `satisfies` preserves per-property inference (so `checked.ready` stays `string` and `toUpperCase()` compiles), while annotations and assertions widen values to the target type and, in the assertion case, let a missing required key pass type checking and evaluate to `undefined` at runtime. The writeup also found that `satisfies` rejects excess properties only on inline object literals, not on pre-assigned variables.", "body_md": "Use `satisfies` when a configuration value should meet a type while its individual properties remain convenient to use. Two common shortcuts are misleading, though: that it can never affect inference, and that it always rejects extra keys.\n\nThe comparison uses the same object under a type annotation, `satisfies`, and `as`, with emitted declarations and diagnostics as evidence. For this display configuration, the differences were:\n\n| Form | Type when reading `ready` | Missing `waiting` | \n|---|---|---|\n| `: Notices` | `string | number[]` | \n| `satisfies Notices` | `string` | Error | \n| `as Notices` | `string | number[]` | \n\nThe experiment ran on September 11, 2026, with macOS arm64, Node.js 24.15.0, and TypeScript 7.0.2. The compiler used `strict: true`, `target: ES2022`, `module: ESNext`, and `moduleResolution: Bundler`, with type checking and declaration emit. The display values are synthetic test data.\n\n``` js\ntype Notices = Record<\"ready\" | \"waiting\", string | number[]>;\nexport const annotated: Notices = { ready: \"Ready\", waiting: [1, 2] };\nexport const checked = { ready: \"Ready\", waiting: [1, 2] } satisfies Notices;\nexport const asserted = { ready: \"Ready\", waiting: [1, 2] } as Notices;\nconsole.log(checked.ready.toUpperCase());\n```\n\n`checked.ready.toUpperCase()` passed and printed `READY`. Calling the same method on `annotated.ready` produced TS2339 because the property could also be an array.\n\nThe emitted declarations explain that difference:\n\n```\ntype Notices = Record<\"ready\" | \"waiting\", string | number[]>;\nexport declare const annotated: Notices;\nexport declare const checked: {\n    ready: string;\n    waiting: number[];\n};\nexport declare const asserted: Notices;\nexport {};\n```\n\n`checked` retains the types of individual properties. The other two variables have the annotated `Notices` type. This makes `satisfies` useful when a configuration needs a shared compatibility check without requiring extra narrowing wherever a particular value is used. The [TypeScript 4.9 documentation](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html) describes this use of compatibility checks while retaining a useful expression type.\n\n``` js\ntype Notices = Record<\"ready\" | \"waiting\", string | number[]>;\nconst value = { ready: \"Ready\" } as Notices;\nconsole.log(value.waiting);\n```\n\nThis code passed type checking and printed `undefined`. Changing the assertion to `satisfies Notices` produced TS2741 for the missing `waiting` property. An assertion does not create that property.\n\nThis also does not mean every possible assertion is accepted. TypeScript can reject an assertion based on the relationship between the types. The finding here is narrower: it accepts this particular object despite the missing required key. As the [official explanation of type assertions](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) states, assertions do not validate or convert runtime values.\n\n``` js\nexport const plain = { enabled: true, count: 1 };\nexport const checked = { enabled: true, count: 1 } satisfies { enabled: boolean; count: number };\nexport const frozenType = { enabled: true, count: 1 } as const;\n```\n\nIn the emitted declarations, `plain.enabled` was `boolean`, while `checked.enabled` was `true`. Both `count` properties were `number`. Context from the target type means literal inference is not necessarily identical.\n\nWith `as const`, `frozenType.enabled` became a readonly `true`, and `frozenType.count` became a readonly `1`. This does not freeze the object at runtime either. If a configuration will be modified later, decide whether that property should remain the literal `true` or accept both boolean values, including `false`.\n\nWriting `{ ready: \"Ready\", waiting: [1], typo: 1 } satisfies Notices` directly produced TS2353 for the extra `typo` property. Assigning that object to a variable first changed the outcome:\n\n``` js\ntype Notices = Record<\"ready\" | \"waiting\", string | number[]>;\nconst input = { ready: \"Ready\", waiting: [1], typo: 1 };\nconst value = input satisfies Notices;\nconsole.log(value.typo);\n```\n\nThis passed and printed `1`. A value can satisfy the required structure while retaining additional properties. Restricting the keys of external data requires a runtime check separate from these type constructs.\n\nFor the local configuration in this experiment, use `satisfies` to catch mistakes while retaining operations specific to each value. Use an annotation to specify the type the variable should expose. Reserve `as` for places where other evidence supports the asserted type.\n\nThe T02 cases in the [reproduction code](https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/experiments/article-stock-2026-09/language-batch08/compiler-cases.json) and the [execution record](https://github.com/takahiro-saeki/articles/blob/codex/article-stock-2026-09/production/2026-09/batch-08/compiler-results.json) include both accepted declarations and deliberately invalid inputs. Do not assume diagnostic numbers or inference remain identical across compiler versions; compare declaration output when upgrading.", "url": "https://wpnews.pro/news/comparing-satisfies-type-annotations-and-assertions-through-compiler-output", "canonical_source": "https://dev.to/hirodeath/comparing-satisfies-type-annotations-and-assertions-through-compiler-output-4f50", "published_at": "2026-10-22 02:07:14+00:00", "updated_at": "2026-10-22 02:10:38.749292+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["TypeScript", "Node.js", "macOS"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/comparing-satisfies-type-annotations-and-assertions-through-compiler-output", "markdown": "https://wpnews.pro/news/comparing-satisfies-type-annotations-and-assertions-through-compiler-output.md", "text": "https://wpnews.pro/news/comparing-satisfies-type-annotations-and-assertions-through-compiler-output.txt", "jsonld": "https://wpnews.pro/news/comparing-satisfies-type-annotations-and-assertions-through-compiler-output.jsonld"}}