[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-typescript-won-what-it-bought-us":32,"code:ts:true:knsv1u":155},[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":36,"body":64,"bodyMd":146,"seo":147,"translationGroupId":149,"thread":150,"assessments":152,"translations":153,"quiz":36},"019fe65f-a9f9-7079-bfbf-ca4dc5685bdd","typescript-won-what-it-bought-us","TypeScript Won. Here's What That Actually Bought Us.",null,"The 'should we use TypeScript?' debate is over. The interesting question is what all those types are quietly enabling now.","\u002Fmedia\u002Fcovers\u002Ftypescript-won-what-it-bought-us.png",3,"2026-06-28T07:31:54.189Z",6,{"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},"javascript","Javascript",{"slug":58,"name":59,"color":36},"webdev","Webdev",{"slug":61,"name":62,"color":36},"ai","Ai",{"assessments":19},{"blocks":65,"version":145},[66,70,73,78,81,87,90,93,96,99,102,105,108,111,114,122,125,128,131,134,137],{"id":67,"html":68,"type":69},"b1","\u003Cp>Nobody seriously argues about adopting TypeScript anymore. New frontend projects default to it; the holdouts are legacy codebases and the occasional throwaway script. The debate is over, TypeScript won.\u003C\u002Fp>","paragraph",{"id":71,"html":72,"type":69},"b2","\u003Cp>But &quot;won&quot; is the boring part. The interesting part is \u003Cem>what types turned out to be good for\u003C\u002Fem> — which is more, and different, than the original pitch of &quot;catch typos before runtime.&quot;\u003C\u002Fp>",{"id":74,"html":75,"text":76,"type":77,"level":31},"b3","Types are the cheapest documentation you&#39;ll never have to update","Types are the cheapest documentation you'll never have to update","heading",{"id":79,"html":80,"type":69},"b4","\u003Cp>A function signature is documentation that can&#39;t go stale, because the compiler fails the build the moment it lies.\u003C\u002Fp>",{"id":82,"code":83,"type":84,"language":85,"highlight":86},"b5","function scheduleReminder(\n  userId: string,\n  at: Date,\n  channel: \"email\" | \"push\" | \"sms\",\n): Promise\u003CReminderId>;","code","ts",[],{"id":88,"html":89,"type":69},"b6","\u003Cp>You already know almost everything about calling this without reading a single comment: what it needs, what it returns, that \u003Ccode>channel\u003C\u002Fcode> is one of exactly three strings. A comment claiming the same things could rot the next time someone adds a \u003Ccode>&quot;slack&quot;\u003C\u002Fcode> channel and forgets to update it. The type \u003Cem>can&#39;t\u003C\u002Fem> — adding \u003Ccode>&quot;slack&quot;\u003C\u002Fcode> to the union forces every call site to be reconsidered.\u003C\u002Fp>",{"id":91,"html":92,"text":92,"type":77,"level":31},"b7","Types make refactoring a mechanical activity instead of an act of courage",{"id":94,"html":95,"type":69},"b8","\u003Cp>In a large untyped codebase, renaming a widely-used field is genuinely scary — you&#39;re grepping strings and praying. In a typed one, you change the type and the compiler hands you the complete to-do list of everything that broke. Refactoring stops being &quot;risky&quot; and becomes &quot;tedious but safe,&quot; which is exactly the trade you want. Whole categories of &quot;we can&#39;t touch that, it&#39;s too entangled&quot; simply dissolve.\u003C\u002Fp>",{"id":97,"html":98,"text":98,"type":77,"level":31},"b9","The payoff nobody planned: types are the contract AI codegen needs",{"id":100,"html":101,"type":69},"b10","\u003Cp>Here&#39;s the part that wasn&#39;t in the original sales pitch. The more machine-readable your boundaries are, the more reliably an AI assistant can operate inside them.\u003C\u002Fp>",{"id":103,"html":104,"type":69},"b11","\u003Cp>Ask a model to &quot;add a field to this object&quot; in untyped JavaScript and it&#39;s guessing at shape from usage. Ask it in TypeScript and the type \u003Cem>is\u003C\u002Fem> the spec — it knows precisely what&#39;s allowed, and its mistakes surface as compile errors instead of 2am production incidents. Types turn &quot;generate plausible code&quot; into &quot;generate code that provably fits.&quot;\u003C\u002Fp>",{"id":106,"html":107,"type":69},"b12","\u003Cp>This flips an old objection on its head. People used to say types slowed them down. In an AI-assisted workflow, types \u003Cem>speed you up\u003C\u002Fem>, because they&#39;re the guardrail that lets you accept generated code with confidence instead of auditing every line by hand.\u003C\u002Fp>",{"id":109,"html":110,"text":110,"type":77,"level":31},"b13","A few habits that compound",{"id":112,"html":113,"type":69},"b14","\u003Cp>If types are this leverage-rich, it&#39;s worth writing them with intent rather than appeasing the compiler:\u003C\u002Fp>",{"id":115,"type":116,"items":117,"ordered":18},"b15","list",[118,119,120,121],"\u003Cstrong>Prefer unions over booleans + optionals.\u003C\u002Fstrong> \u003Ccode>status: &quot;loading&quot; | &quot;error&quot; | &quot;ready&quot;\u003C\u002Fcode> beats three independent boolean flags that can contradict each other.","\u003Cstrong>Name your domain types.\u003C\u002Fstrong> \u003Ccode>type Cents = number\u003C\u002Fcode> documents intent at every use site and lets you tighten it later.","\u003Cstrong>Avoid \u003Ccode>any\u003C\u002Fcode>; reach for \u003Ccode>unknown\u003C\u002Fcode> and narrow.\u003C\u002Fstrong> \u003Ccode>any\u003C\u002Fcode> is a hole in exactly the safety net you&#39;re paying for.","\u003Cstrong>Let inference work.\u003C\u002Fstrong> You don&#39;t need to annotate everything — annotate the \u003Cem>boundaries\u003C\u002Fem> (function signatures, exported APIs) and let the rest flow.",{"id":123,"html":124,"text":124,"type":77,"level":31},"b16","The takeaway",{"id":126,"html":127,"type":69},"b17","\u003Cp>TypeScript&#39;s real win wasn&#39;t catching typos. It was turning your codebase into something with \u003Cem>explicit, enforced contracts\u003C\u002Fem> — and contracts turn out to be exactly what fearless refactoring, reliable tooling, and trustworthy AI assistance all quietly depend on.\u003C\u002Fp>",{"id":129,"html":130,"type":69},"b18","\u003Cp>We adopted types to prevent a class of bugs. We&#39;re keeping them because they&#39;re the substrate everything else now builds on. Worth investing in writing them well.\u003C\u002Fp>",{"id":132,"type":133},"b19","divider",{"id":135,"html":136,"type":69},"b20","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":138,"type":116,"items":139,"ordered":18},"b21",[140,141,142,143,144],"⭐ \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>",1,"Nobody seriously argues about adopting TypeScript anymore. New frontend projects default to it; the holdouts are legacy codebases and the occasional throwaway script. The debate is over, TypeScript won.\n\nBut \"won\" is the boring part. The interesting part is *what types turned out to be good for* — which is more, and different, than the original pitch of \"catch typos before runtime.\"\n\n## Types are the cheapest documentation you'll never have to update\n\nA function signature is documentation that can't go stale, because the compiler fails the build the moment it lies.\n\n```ts\nfunction scheduleReminder(\n  userId: string,\n  at: Date,\n  channel: \"email\" | \"push\" | \"sms\",\n): Promise\u003CReminderId>;\n```\n\nYou already know almost everything about calling this without reading a single comment: what it needs, what it returns, that `channel` is one of exactly three strings. A comment claiming the same things could rot the next time someone adds a `\"slack\"` channel and forgets to update it. The type *can't* — adding `\"slack\"` to the union forces every call site to be reconsidered.\n\n## Types make refactoring a mechanical activity instead of an act of courage\n\nIn a large untyped codebase, renaming a widely-used field is genuinely scary — you're grepping strings and praying. In a typed one, you change the type and the compiler hands you the complete to-do list of everything that broke. Refactoring stops being \"risky\" and becomes \"tedious but safe,\" which is exactly the trade you want. Whole categories of \"we can't touch that, it's too entangled\" simply dissolve.\n\n## The payoff nobody planned: types are the contract AI codegen needs\n\nHere's the part that wasn't in the original sales pitch. The more machine-readable your boundaries are, the more reliably an AI assistant can operate inside them.\n\nAsk a model to \"add a field to this object\" in untyped JavaScript and it's guessing at shape from usage. Ask it in TypeScript and the type *is* the spec — it knows precisely what's allowed, and its mistakes surface as compile errors instead of 2am production incidents. Types turn \"generate plausible code\" into \"generate code that provably fits.\"\n\nThis flips an old objection on its head. People used to say types slowed them down. In an AI-assisted workflow, types *speed you up*, because they're the guardrail that lets you accept generated code with confidence instead of auditing every line by hand.\n\n## A few habits that compound\n\nIf types are this leverage-rich, it's worth writing them with intent rather than appeasing the compiler:\n\n- **Prefer unions over booleans + optionals.** `status: \"loading\" | \"error\" | \"ready\"` beats three independent boolean flags that can contradict each other.\n- **Name your domain types.** `type Cents = number` documents intent at every use site and lets you tighten it later.\n- **Avoid `any`; reach for `unknown` and narrow.** `any` is a hole in exactly the safety net you're paying for.\n- **Let inference work.** You don't need to annotate everything — annotate the *boundaries* (function signatures, exported APIs) and let the rest flow.\n\n## The takeaway\n\nTypeScript's real win wasn't catching typos. It was turning your codebase into something with *explicit, enforced contracts* — and contracts turn out to be exactly what fearless refactoring, reliable tooling, and trustworthy AI assistance all quietly depend on.\n\nWe adopted types to prevent a class of bugs. We're keeping them because they're the substrate everything else now builds on. Worth investing in writing them well.\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":35,"canonical":148,"description":37},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftypescript-won-what-it-bought-us","019fe65f-a9f9-7079-bfbf-cf0ca00e2b95",{"id":151,"locked":18},"019fe65f-b0e9-732c-a752-2ad96903a875",[],[154],{"locale":13,"slug":34},"\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>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">scheduleReminder\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n  userId\u003Cspan class=\"shj-type\">: string\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  at\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Date\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  channel\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"email\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"push\"\u003C\u002Fspan> \u003Cspan class=\"shj-oper\">|\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"sms\"\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&lt;\u003C\u002Fspan>\u003Cspan class=\"shj-class\">ReminderId\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">&gt;\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>"]