[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-typescript-satisfies-operator":32,"code:ts:true:1cgqlts":237,"code:ts:true:824gob":238,"code:ts:true:1mg5hum":239,"code:ts:true:obehe5":240,"code:ts:true:rrvqqt":241,"code:ts:true:11k01v2":242,"code:ts:true:1hemr77":243,"code:ts:true:1m06is3":244,"code:ts:true:hq988":245},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"title":35,"subtitle":36,"excerpt":37,"coverUrl":38,"locale":13,"readingMinutes":39,"publishedAt":40,"viewCount":41,"likeCount":19,"commentCount":19,"author":42,"vertical":47,"topic":48,"tags":51,"_count":63,"playground":65,"body":67,"bodyMd":219,"seo":220,"translationGroupId":224,"thread":225,"assessments":227,"translations":233,"quiz":235},"019fe660-0d25-72d6-8fd8-3cf3c3592e54","typescript-satisfies-operator","Your TypeScript config objects are losing type information. `satisfies` fixes it.",null,"Type annotations widen your object types and erase the precise values TypeScript inferred. The `satisfies` operator validates against a type without changing what TypeScript keeps — you get the check without the widening.","\u002Fmedia\u002Fcovers\u002Ftypescript-satisfies-operator.png",5,"2026-07-11T08:03:26.200Z",16,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},"typescript","TypeScript",[52,54,57,60],{"slug":49,"name":53,"color":36},"Typescript",{"slug":55,"name":56,"color":36},"webdev","Webdev",{"slug":58,"name":59,"color":36},"javascript","Javascript",{"slug":61,"name":62,"color":36},"frontend","Frontend",{"assessments":64},1,{"slug":34,"title":66},"satisfies — validate without widening",{"blocks":68,"version":64},[69,73,79,82,86,89,92,96,99,104,107,111,114,117,120,123,126,129,132,135,139,143,146,149,153,156,160,163,167,170,173,177,180,183,186,189,192,195,198,201,204,207,210],{"id":70,"html":71,"type":72},"b1","\u003Cp>There is a \u003Ccode>config.ts\u003C\u002Fcode> in most TypeScript projects that looks something like this:\u003C\u002Fp>","paragraph",{"id":74,"code":75,"type":76,"language":77,"highlight":78},"b2","type Status = 'idle' | 'loading' | 'error' | 'success';\n\nconst STATUS_LABELS: Record\u003CStatus, string> = {\n  idle: 'Waiting',\n  loading: 'Loading…',\n  error: 'Something went wrong',\n  success: 'Done',\n};","code","ts",[],{"id":80,"html":81,"type":72},"b3","\u003Cp>The annotation validates the object. Miss a key, TypeScript tells you. Use a non-string value, TypeScript tells you. That&#39;s the whole point of the annotation. Now look at what you get when you use it:\u003C\u002Fp>",{"id":83,"code":84,"type":76,"language":77,"highlight":85},"b4","STATUS_LABELS.idle; \u002F\u002F type: string",[],{"id":87,"html":88,"type":72},"b5","\u003Cp>TypeScript knows the value is \u003Cem>a\u003C\u002Fem> string. It doesn&#39;t know it&#39;s \u003Ccode>&#39;Waiting&#39;\u003C\u002Fcode>. The moment you wrote \u003Ccode>: Record&lt;Status, string&gt;\u003C\u002Fcode>, TypeScript swapped the inferred type for the declared type and threw away the actual values. You validated the shape, but you gave up precision to do it.\u003C\u002Fp>",{"id":90,"html":91,"type":72},"b6","\u003Cp>For \u003Ccode>STATUS_LABELS\u003C\u002Fcode>, that trade is usually fine. But pick a more demanding example — a component icon registry:\u003C\u002Fp>",{"id":93,"code":94,"type":76,"language":77,"highlight":95},"b7","const ICONS: Record\u003Cstring, React.ComponentType\u003Cany>> = {\n  trash: TrashIcon,\n  edit:  EditIcon,\n  copy:  CopyIcon,\n};\n\nICONS.trash; \u002F\u002F type: React.ComponentType\u003Cany>",[],{"id":97,"html":98,"type":72},"b8","\u003Cp>You&#39;ve lost the specific component types. \u003Ccode>TrashIcon\u003C\u002Fcode> might have a \u003Ccode>size\u003C\u002Fcode> prop; TypeScript no longer knows. Any access through \u003Ccode>ICONS\u003C\u002Fcode> returns the widened union. The annotation bought validation and cost you everything the concrete types were worth.\u003C\u002Fp>",{"id":100,"html":101,"text":102,"type":103,"level":31},"b9","What \u003Ccode>satisfies\u003C\u002Fcode> does differently","What satisfies does differently","heading",{"id":105,"html":106,"type":72},"b10","\u003Cp>\u003Ccode>satisfies\u003C\u002Fcode> arrived in TypeScript 4.9 (November 2022). The syntax is \u003Ccode>expression satisfies Type\u003C\u002Fcode>. It checks that the left side is compatible with the type on the right — the same check as an annotation — but the \u003Cem>resulting\u003C\u002Fem> inferred type stays as the expression&#39;s own type, not the annotation&#39;s.\u003C\u002Fp>",{"id":108,"code":109,"type":76,"language":77,"highlight":110},"b11","const ICONS = {\n  trash: TrashIcon,\n  edit:  EditIcon,\n  copy:  CopyIcon,\n} satisfies Record\u003Cstring, React.ComponentType\u003Cany>>;\n\nICONS.trash; \u002F\u002F type: typeof TrashIcon — the concrete type, preserved",[],{"id":112,"html":113,"type":72},"b12","\u003Cp>TypeScript validates that each value is a \u003Ccode>React.ComponentType\u003C\u002Fcode>. If you put a string in there by mistake, you get an error. But the inferred type of \u003Ccode>ICONS.trash\u003C\u002Fcode> stays as \u003Ccode>typeof TrashIcon\u003C\u002Fcode>. The check happened; the widening didn&#39;t.\u003C\u002Fp>",{"id":115,"html":116,"type":72},"b13","\u003C!-- playground:start -->",{"id":118,"html":119,"text":119,"type":103,"level":31},"b14","🎮 Try it yourself",{"id":121,"html":122,"type":72},"b15","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-11-typescript-satisfies-operator\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":124,"html":125,"type":72},"b16","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":127,"html":128,"type":72},"b17","\u003C!-- playground:end -->",{"id":130,"html":131,"text":131,"type":103,"level":31},"b18","Where it matters most",{"id":133,"html":134,"type":72},"b19","\u003Cp>\u003Cstrong>Route maps.\u003C\u002Fstrong> A central route object is a common pattern, and getting its keys as a typed union is useful for navigation functions:\u003C\u002Fp>",{"id":136,"code":137,"type":76,"language":77,"highlight":138},"b20","\u002F\u002F With annotation — keys become string\nconst ROUTES: Record\u003Cstring, string> = {\n  home:     '\u002F',\n  about:    '\u002Fabout',\n  settings: '\u002Fsettings',\n};\n\ntype RouteName = keyof typeof ROUTES; \u002F\u002F string — not what you wanted",[],{"id":140,"code":141,"type":76,"language":77,"highlight":142},"b21","\u002F\u002F With satisfies — keys stay literal\nconst ROUTES = {\n  home:     '\u002F',\n  about:    '\u002Fabout',\n  settings: '\u002Fsettings',\n} satisfies Record\u003Cstring, string>;\n\ntype RouteName = keyof typeof ROUTES; \u002F\u002F 'home' | 'about' | 'settings'\n\nfunction navigate(to: RouteName) { \u002F* ... *\u002F }\n\nnavigate('home');    \u002F\u002F fine\nnavigate('contact'); \u002F\u002F Error: Argument of type '\"contact\"' is not assignable to parameter of type 'RouteName'",[],{"id":144,"html":145,"type":72},"b22","\u003Cp>The annotation validated the paths are strings. \u003Ccode>satisfies\u003C\u002Fcode> did the same validation and also kept the named keys as a union — so every call to \u003Ccode>navigate()\u003C\u002Fcode> is checked against the actual routes in your config.\u003C\u002Fp>",{"id":147,"html":148,"type":72},"b23","\u003Cp>\u003Cstrong>Exhaustive pattern validation.\u003C\u002Fstrong> When you have a discriminated union and want to map every case to something:\u003C\u002Fp>",{"id":150,"code":151,"type":76,"language":77,"highlight":152},"b24","type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };\n\nconst AREA_FORMULAS = {\n  circle: (s: Extract\u003CShape, { kind: 'circle' }>) => Math.PI * s.radius ** 2,\n  square: (s: Extract\u003CShape, { kind: 'square' }>) => s.side ** 2,\n} satisfies { [K in Shape['kind']]: (shape: Extract\u003CShape, { kind: K }>) => number };",[],{"id":154,"html":155,"type":72},"b25","\u003Cp>Add a new shape kind to the union and TypeScript immediately tells you \u003Ccode>AREA_FORMULAS\u003C\u002Fcode> is no longer exhaustive — without you having to look it up.\u003C\u002Fp>",{"id":157,"html":158,"text":159,"type":103,"level":31},"b26","How it compares to \u003Ccode>as\u003C\u002Fcode>","How it compares to as",{"id":161,"html":162,"type":72},"b27","\u003Cp>\u003Ccode>as\u003C\u002Fcode> is a type assertion: you are telling TypeScript what the type is, skipping its inference. It&#39;s useful when TypeScript can&#39;t see something you can — reading from the DOM, narrowing after an external type guard — but it provides no validation.\u003C\u002Fp>",{"id":164,"code":165,"type":76,"language":77,"highlight":166},"b28","\u002F\u002F as — you're asserting, not checking\nconst el = document.getElementById('app') as HTMLDivElement;\n\u002F\u002F TypeScript trusts you; it doesn't verify the element is a div",[],{"id":168,"html":169,"type":72},"b29","\u003Cp>\u003Ccode>satisfies\u003C\u002Fcode> is the opposite: TypeScript checks, then steps back. Use \u003Ccode>as\u003C\u002Fcode> when the compiler lacks information and you have it. Use \u003Ccode>satisfies\u003C\u002Fcode> when you have information and want the compiler to verify it without losing it. Use \u003Ccode>: Type\u003C\u002Fcode> annotation when you want to commit to the wider type throughout the file and don&#39;t need the narrow inference.\u003C\u002Fp>",{"id":171,"html":172,"text":172,"type":103,"level":31},"b30","Spot the difference",{"id":174,"code":175,"type":76,"language":77,"highlight":176},"b31","type Path = '\u002F' | '\u002Fabout' | '\u002Fcontact';\n\n\u002F\u002F Annotation: validates, then widens — the exact value and keys are gone\nconst routes: Record\u003Cstring, Path> = { home: '\u002F', about: '\u002Fabout', contact: '\u002Fcontact' };\nroutes.home;                   \u002F\u002F Path  — not '\u002F'\nObject.keys(routes);           \u002F\u002F string[]\ntype RouteKey = keyof typeof routes; \u002F\u002F string  — any key is allowed\n\n\u002F\u002F satisfies: validates, then steps back — the exact value and keys survive\nconst routes = { home: '\u002F', about: '\u002Fabout', contact: '\u002Fcontact' } satisfies Record\u003Cstring, Path>;\nroutes.home;                   \u002F\u002F '\u002F'\ntype RouteKey = keyof typeof routes; \u002F\u002F 'home' | 'about' | 'contact'",[],{"id":178,"html":179,"type":72},"b32","\u003Cp>Two things to notice. First, \u003Ccode>satisfies\u003C\u002Fcode> keeps the exact \u003Cstrong>keys\u003C\u002Fstrong> either way — \u003Ccode>keyof typeof routes\u003C\u002Fcode> is the real union, so key access and \u003Ccode>Object.keys\u003C\u002Fcode> stay precise. Second, the \u003Cstrong>value\u003C\u002Fstrong> literal \u003Ccode>&#39;\u002F&#39;\u003C\u002Fcode> only survives because the target&#39;s value type (\u003Ccode>Path\u003C\u002Fcode>) is itself a literal union; against a wider target like \u003Ccode>Record&lt;string, string&gt;\u003C\u002Fcode> the values would still widen to \u003Ccode>string\u003C\u002Fcode>. Add a value outside \u003Ccode>Path\u003C\u002Fcode> to either declaration and TypeScript catches it. The only difference is what comes out the other side.\u003C\u002Fp>",{"id":181,"html":182,"type":72},"b33","\u003C!-- quiz:start -->",{"id":184,"html":185,"text":185,"type":103,"level":31},"b34","🧠 Test yourself",{"id":187,"html":188,"type":72},"b35","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-11-typescript-satisfies-operator\">Take the 6-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":190,"html":191,"type":72},"b36","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":193,"html":194,"type":72},"b37","\u003C!-- quiz:end -->",{"id":196,"html":197,"text":197,"type":103,"level":31},"b38","The rule",{"id":199,"html":200,"type":72},"b39","\u003Cp>Use \u003Ccode>satisfies\u003C\u002Fcode> for any const you define inline and then access by its specific keys or values — config maps, icon registries, route definitions, theme tokens, anything where the precise shape matters downstream. Use annotation when you want to commit to the wider type and everything accessing it should see only that wider type. Use \u003Ccode>as\u003C\u002Fcode> when TypeScript can&#39;t see the correct type on its own.\u003C\u002Fp>",{"id":202,"html":203,"type":72},"b40","\u003Cp>You&#39;ve been choosing between safety and precision. \u003Ccode>satisfies\u003C\u002Fcode> was added so you don&#39;t have to.\u003C\u002Fp>",{"id":205,"type":206},"b41","divider",{"id":208,"html":209,"type":72},"b42","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":211,"type":212,"items":213,"ordered":18},"b43","list",[214,215,216,217,218],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","There is a `config.ts` in most TypeScript projects that looks something like this:\n\n```ts\ntype Status = 'idle' | 'loading' | 'error' | 'success';\n\nconst STATUS_LABELS: Record\u003CStatus, string> = {\n  idle: 'Waiting',\n  loading: 'Loading…',\n  error: 'Something went wrong',\n  success: 'Done',\n};\n```\n\nThe annotation validates the object. Miss a key, TypeScript tells you. Use a non-string value, TypeScript tells you. That's the whole point of the annotation. Now look at what you get when you use it:\n\n```ts\nSTATUS_LABELS.idle; \u002F\u002F type: string\n```\n\nTypeScript knows the value is *a* string. It doesn't know it's `'Waiting'`. The moment you wrote `: Record\u003CStatus, string>`, TypeScript swapped the inferred type for the declared type and threw away the actual values. You validated the shape, but you gave up precision to do it.\n\nFor `STATUS_LABELS`, that trade is usually fine. But pick a more demanding example — a component icon registry:\n\n```ts\nconst ICONS: Record\u003Cstring, React.ComponentType\u003Cany>> = {\n  trash: TrashIcon,\n  edit:  EditIcon,\n  copy:  CopyIcon,\n};\n\nICONS.trash; \u002F\u002F type: React.ComponentType\u003Cany>\n```\n\nYou've lost the specific component types. `TrashIcon` might have a `size` prop; TypeScript no longer knows. Any access through `ICONS` returns the widened union. The annotation bought validation and cost you everything the concrete types were worth.\n\n## What `satisfies` does differently\n\n`satisfies` arrived in TypeScript 4.9 (November 2022). The syntax is `expression satisfies Type`. It checks that the left side is compatible with the type on the right — the same check as an annotation — but the *resulting* inferred type stays as the expression's own type, not the annotation's.\n\n```ts\nconst ICONS = {\n  trash: TrashIcon,\n  edit:  EditIcon,\n  copy:  CopyIcon,\n} satisfies Record\u003Cstring, React.ComponentType\u003Cany>>;\n\nICONS.trash; \u002F\u002F type: typeof TrashIcon — the concrete type, preserved\n```\n\nTypeScript validates that each value is a `React.ComponentType`. If you put a string in there by mistake, you get an error. But the inferred type of `ICONS.trash` stays as `typeof TrashIcon`. The check happened; the widening didn't.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-11-typescript-satisfies-operator\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Where it matters most\n\n**Route maps.** A central route object is a common pattern, and getting its keys as a typed union is useful for navigation functions:\n\n```ts\n\u002F\u002F With annotation — keys become string\nconst ROUTES: Record\u003Cstring, string> = {\n  home:     '\u002F',\n  about:    '\u002Fabout',\n  settings: '\u002Fsettings',\n};\n\ntype RouteName = keyof typeof ROUTES; \u002F\u002F string — not what you wanted\n```\n\n```ts\n\u002F\u002F With satisfies — keys stay literal\nconst ROUTES = {\n  home:     '\u002F',\n  about:    '\u002Fabout',\n  settings: '\u002Fsettings',\n} satisfies Record\u003Cstring, string>;\n\ntype RouteName = keyof typeof ROUTES; \u002F\u002F 'home' | 'about' | 'settings'\n\nfunction navigate(to: RouteName) { \u002F* ... *\u002F }\n\nnavigate('home');    \u002F\u002F fine\nnavigate('contact'); \u002F\u002F Error: Argument of type '\"contact\"' is not assignable to parameter of type 'RouteName'\n```\n\nThe annotation validated the paths are strings. `satisfies` did the same validation and also kept the named keys as a union — so every call to `navigate()` is checked against the actual routes in your config.\n\n**Exhaustive pattern validation.** When you have a discriminated union and want to map every case to something:\n\n```ts\ntype Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };\n\nconst AREA_FORMULAS = {\n  circle: (s: Extract\u003CShape, { kind: 'circle' }>) => Math.PI * s.radius ** 2,\n  square: (s: Extract\u003CShape, { kind: 'square' }>) => s.side ** 2,\n} satisfies { [K in Shape['kind']]: (shape: Extract\u003CShape, { kind: K }>) => number };\n```\n\nAdd a new shape kind to the union and TypeScript immediately tells you `AREA_FORMULAS` is no longer exhaustive — without you having to look it up.\n\n## How it compares to `as`\n\n`as` is a type assertion: you are telling TypeScript what the type is, skipping its inference. It's useful when TypeScript can't see something you can — reading from the DOM, narrowing after an external type guard — but it provides no validation.\n\n```ts\n\u002F\u002F as — you're asserting, not checking\nconst el = document.getElementById('app') as HTMLDivElement;\n\u002F\u002F TypeScript trusts you; it doesn't verify the element is a div\n```\n\n`satisfies` is the opposite: TypeScript checks, then steps back. Use `as` when the compiler lacks information and you have it. Use `satisfies` when you have information and want the compiler to verify it without losing it. Use `: Type` annotation when you want to commit to the wider type throughout the file and don't need the narrow inference.\n\n## Spot the difference\n\n```ts\ntype Path = '\u002F' | '\u002Fabout' | '\u002Fcontact';\n\n\u002F\u002F Annotation: validates, then widens — the exact value and keys are gone\nconst routes: Record\u003Cstring, Path> = { home: '\u002F', about: '\u002Fabout', contact: '\u002Fcontact' };\nroutes.home;                   \u002F\u002F Path  — not '\u002F'\nObject.keys(routes);           \u002F\u002F string[]\ntype RouteKey = keyof typeof routes; \u002F\u002F string  — any key is allowed\n\n\u002F\u002F satisfies: validates, then steps back — the exact value and keys survive\nconst routes = { home: '\u002F', about: '\u002Fabout', contact: '\u002Fcontact' } satisfies Record\u003Cstring, Path>;\nroutes.home;                   \u002F\u002F '\u002F'\ntype RouteKey = keyof typeof routes; \u002F\u002F 'home' | 'about' | 'contact'\n```\n\nTwo things to notice. First, `satisfies` keeps the exact **keys** either way — `keyof typeof routes` is the real union, so key access and `Object.keys` stay precise. Second, the **value** literal `'\u002F'` only survives because the target's value type (`Path`) is itself a literal union; against a wider target like `Record\u003Cstring, string>` the values would still widen to `string`. Add a value outside `Path` to either declaration and TypeScript catches it. The only difference is what comes out the other side.\n\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 6-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-11-typescript-satisfies-operator)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n## The rule\n\nUse `satisfies` for any const you define inline and then access by its specific keys or values — config maps, icon registries, route definitions, theme tokens, anything where the precise shape matters downstream. Use annotation when you want to commit to the wider type and everything accessing it should see only that wider type. Use `as` when TypeScript can't see the correct type on its own.\n\nYou've been choosing between safety and precision. `satisfies` was added so you don't have to.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":221,"canonical":222,"description":223},"Your TypeScript config objects are losing type information. `satisfies","https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftypescript-satisfies-operator","Type annotations widen your object types and erase the precise values TypeScript inferred. The `satisfies` operator validates against a type without changing what TypeScript keeps ","019fe660-0d25-72d6-8fd8-42b8f2d32fb7",{"id":226,"locked":18},"019fe660-13da-75ee-96f4-265beb80ca3c",[228],{"id":229,"slug":34,"title":230,"_count":231},"019fe776-369d-7220-b877-ea29a4dbbcd3","TypeScript satisfies operator",{"questions":232},6,[234],{"locale":13,"slug":34},{"id":229,"slug":34,"title":230,"_count":236,"questionCount":232},{"questions":232},"\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Status\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'idle'\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'loading'\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'error'\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'success'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-num\">STATUS_LABELS\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Status\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> string\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  idle\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Waiting'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  loading\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Loading…'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  error\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Something went wrong'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  success\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'Done'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-oneline\" data-lang=\"ts\">\u003Cspan class=\"shj-num\">STATUS_LABELS\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>idle; \u003Cspan class=\"shj-cmnt\">\u002F\u002F type: string\u003C\u002Fspan>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-num\">ICONS\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-class\">React\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-class\">ComponentType\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>any\u003Cspan class=\"shj-oper\">&gt;&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  trash\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">TrashIcon\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  edit\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>  \u003Cspan class=\"shj-class\">EditIcon\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  copy\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>  \u003Cspan class=\"shj-class\">CopyIcon\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-num\">ICONS\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>trash; \u003Cspan class=\"shj-cmnt\">\u002F\u002F type: React.ComponentType&lt;any&gt;\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-num\">ICONS\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  trash\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">TrashIcon\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  edit\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>  \u003Cspan class=\"shj-class\">EditIcon\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  copy\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>  \u003Cspan class=\"shj-class\">CopyIcon\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> satisfies \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-class\">React\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-class\">ComponentType\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>any\u003Cspan class=\"shj-oper\">&gt;&gt;\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-num\">ICONS\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>trash; \u003Cspan class=\"shj-cmnt\">\u002F\u002F type: typeof TrashIcon — the concrete type, preserved\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F With annotation — keys become string\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-num\">ROUTES\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> string\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  home\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>     \u003Cspan class=\"shj-str\">'\u002F'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  about\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fabout'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  settings\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fsettings'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RouteName\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> keyof \u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> \u003Cspan class=\"shj-num\">ROUTES\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F string — not what you wanted\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F With satisfies — keys stay literal\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-num\">ROUTES\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  home\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>     \u003Cspan class=\"shj-str\">'\u002F'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  about\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan>    \u003Cspan class=\"shj-str\">'\u002Fabout'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  settings\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fsettings'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> satisfies \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> string\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RouteName\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> keyof \u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> \u003Cspan class=\"shj-num\">ROUTES\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F 'home' | 'about' | 'settings'\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">navigate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>to\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RouteName\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-cmnt\">\u002F* ... *\u002F\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">navigate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'home'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;    \u003Cspan class=\"shj-cmnt\">\u002F\u002F fine\n\u003C\u002Fspan>\u003Cspan class=\"shj-func\">navigate\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'contact'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F Error: Argument of type '\"contact\"' is not assignable to parameter of type 'RouteName'\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Shape\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> kind\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'circle'\u003C\u002Fspan>; radius\u003Cspan class=\"shj-type\">: number\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> kind\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'square'\u003C\u002Fspan>; side\u003Cspan class=\"shj-type\">: number\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> \u003Cspan class=\"shj-num\">AREA_FORMULAS\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  circle\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Extract\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Shape\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> kind\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'circle'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Math\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-num\">PI\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">*\u003C\u002Fspan> s\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>radius \u003Cspan class=\"shj-oper\">**\u003C\u002Fspan> \u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  square\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>s\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Extract\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Shape\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> kind\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'square'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> s\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>side \u003Cspan class=\"shj-oper\">**\u003C\u002Fspan> \u003Cspan class=\"shj-num\">2\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> satisfies \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-num\">K\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">in\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Shape\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">[\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'kind'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">]\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>shape\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Extract\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Shape\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> kind\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-num\">K\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> number \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F as — you're asserting, not checking\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> el \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> document\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getElementById\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'app'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">as\u003C\u002Fspan> \u003Cspan class=\"shj-class\">HTMLDivElement\u003C\u002Fspan>;\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F TypeScript trusts you; it doesn't verify the element is a div\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Path\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002F'\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fabout'\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fcontact'\u003C\u002Fspan>;\n\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F Annotation: validates, then widens — the exact value and keys are gone\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> routes\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Path\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> home\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002F'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> about\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fabout'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> contact\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fcontact'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>;\nroutes\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>home;                   \u003Cspan class=\"shj-cmnt\">\u002F\u002F Path  — not '\u002F'\n\u003C\u002Fspan>\u003Cspan class=\"shj-class\">Object\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">keys\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>routes\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;           \u003Cspan class=\"shj-cmnt\">\u002F\u002F string[]\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RouteKey\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> keyof \u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> routes; \u003Cspan class=\"shj-cmnt\">\u002F\u002F string  — any key is allowed\n\u003C\u002Fspan>\n\u003Cspan class=\"shj-cmnt\">\u002F\u002F satisfies: validates, then steps back — the exact value and keys survive\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> routes \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> home\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002F'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> about\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fabout'\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> contact\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'\u002Fcontact'\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> satisfies \u003Cspan class=\"shj-class\">Record\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>string\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Path\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\nroutes\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>home;                   \u003Cspan class=\"shj-cmnt\">\u002F\u002F '\u002F'\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">type\u003C\u002Fspan> \u003Cspan class=\"shj-class\">RouteKey\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> keyof \u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> routes; \u003Cspan class=\"shj-cmnt\">\u002F\u002F 'home' | 'about' | 'contact'\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>"]