{"id":"l-at-1-1","courseId":"advanced-typescript","lang":"en","kind":"intro","summary":"Tell a type that prevents a bug from a type that only decorates code, and read the type tests every exercise in this course uses.","html":"<p>Here is a bug that shipped in a checkout I worked on. A refund function took an <code dir=\"ltr\">amount</code> and the payment provider expected cents. One caller passed dollars. Customers got refunded 1% of what they were owed, and nobody noticed for two weeks, because every line of that code was “typed”: <code dir=\"ltr\">amount: number</code>.</p>\n<p>The types were there. They just did not earn their keep. <code dir=\"ltr\">number</code> described the code and prevented nothing. This course is about the other kind of type, the kind that makes the bug impossible to write.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Two ways to type the same thing</h2>\n<p>Cartwheel, the store you will work with throughout this course, has five order statuses: <code dir=\"ltr\">processing</code>, <code dir=\"ltr\">shipped</code>, <code dir=\"ltr\">delivered</code>, <code dir=\"ltr\">cancelled</code> and <code dir=\"ltr\">returned</code>. Here is the decorative version:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> canCancel</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order.status </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'procesing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// typo: always false</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>That compiles. <code dir=\"ltr\">status</code> is <code dir=\"ltr\">string</code>, so comparing it with any string is legal. Now the version that earns its keep:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'processing'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'returned'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">status</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> canCancel</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order.status </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'procesing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // Error: This comparison appears to be unintentional because the types</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // 'OrderStatus' and '\"procesing\"' have no overlap.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Same runtime code, one changed line of types, and a whole class of bug is gone. You will see this move again and again: replace a wide type (<code dir=\"ltr\">string</code>, <code dir=\"ltr\">number</code>, <code dir=\"ltr\">object</code>) with a narrow one that says what is actually allowed.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>The rule this course follows</span></p>\n<p>A type is worth writing when you can name the bug it prevents. If you cannot, it is decoration, and decoration has a cost: someone has to read it. The best TypeScript codebases are not the ones with the cleverest types; they are the ones where every type pulls its weight.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">The running example</h2>\n<p>Lesson by lesson you will build the type layer of Cartwheel’s checkout API client: orders and their statuses, money that refuses to mix currencies, coupons, a fetch client whose responses are typed from a route map, events the checkout emits, and parsers that turn untrusted JSON into trusted types. The runtime code stays small on purpose. The interesting part is what the compiler refuses to accept.</p>\n<p>The course runs in five steps. First you use types as a design tool: structural typing, narrowing, discriminated unions and <code dir=\"ltr\">satisfies</code>. Then generics, with a focus on inference. Then type-level programming, with honest advice on when to stop. Then patterns that real codebases need: branded types, <code dir=\"ltr\">Result</code> types, parsing at the boundary, typed events. Finally, shipping: compiler flags, declaration files, decorators and migrating JavaScript.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">How the exercises test types</h2>\n<p>Most exercises are type-level. The playground runs TypeScript 5.9 in strict mode and gives you two helpers:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Equal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'shipped'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'delivered'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'cancelled'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'returned'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>>;</span></span></code></pre></div>\n<p><code dir=\"ltr\">Equal&lt;A, B&gt;</code> is <code dir=\"ltr\">true</code> only when the two types are identical, and <code dir=\"ltr\">Expect</code> only accepts <code dir=\"ltr\">true</code>. So that line is a unit test: it compiles when you got the type right and fails when you did not.</p>\n<p>The second tool is built into TypeScript. A <code dir=\"ltr\">// @ts-expect-error</code> comment says “the next line must be an error”:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// @ts-expect-error: 'canceled' is not a status</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">canCancel</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ id: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1001</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'canceled'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });</span></span></code></pre></div>\n<p>If the line below it compiles, TypeScript reports an unused directive. That turns “this bad call is rejected” into a test, which matters as much as “this good call is accepted”. A type that accepts everything passes every positive test.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Reaching for @ts-ignore</span></p>\n<p><code dir=\"ltr\">// @ts-ignore</code> hides an error and stays silent when the error goes away, so it rots. In tests and in production code, prefer <code dir=\"ltr\">@ts-expect-error</code> with a short reason after a colon. When someone fixes the underlying problem, the directive tells them it can be deleted.</p>\n</aside>\n<p>In the exercise below, the statuses are typed as <code dir=\"ltr\">string</code>. Narrow the type and watch the compiler find the missing label for you. The next lesson looks at what “assignable” actually means in TypeScript, because it is not what most people coming from Java or C# expect.</p>\n","toc":[{"id":"sec-1","text":"Two ways to type the same thing","level":2},{"id":"sec-2","text":"The running example","level":2},{"id":"sec-3","text":"How the exercises test types","level":2}],"takeaways":["A type earns its keep when it makes a real bug impossible to write, not when it restates what the code already says.","A <code dir=\"ltr\">string</code> where only five values are valid is a missed chance; a union of literals lets the compiler check every use.","<code dir=\"ltr\">Expect&lt;Equal&lt;A, B&gt;&gt;</code> fails to compile unless the two types are identical, so it works as a unit test for types.","A <code dir=\"ltr\">// @ts-expect-error</code> line asserts that the next line is rejected, and it becomes an error itself if that line compiles."],"further":[{"title":"TypeScript for JavaScript Programmers","url":"https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html"},{"title":"Everyday Types","url":"https://www.typescriptlang.org/docs/handbook/2/everyday-types.html"}],"quiz":[{"q":"<p>Which of these types catches the most real bugs for an order’s <code dir=\"ltr\">status</code> field that can only be <code dir=\"ltr\">processing</code>, <code dir=\"ltr\">shipped</code>, <code dir=\"ltr\">delivered</code>, <code dir=\"ltr\">cancelled</code> or <code dir=\"ltr\">returned</code>?</p>","options":[{"text":"<code dir=\"ltr\">string</code>, documented with a comment listing the five values","why":"A comment is not checked. <code dir=\"ltr\">'canceled'</code> with one L compiles fine and fails silently at runtime."},{"text":"<code dir=\"ltr\">'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'</code>","why":"Correct. Every assignment and comparison is checked against the five values, and the compiler can tell when a switch misses one."},{"text":"<code dir=\"ltr\">any</code>, because the value comes from the database anyway","why":"<code dir=\"ltr\">any</code> switches checking off for every use of the value, so a typo anywhere downstream goes unnoticed."},{"text":"<code dir=\"ltr\">String</code>, the boxed object type, because it is stricter than <code dir=\"ltr\">string</code>","why":"<code dir=\"ltr\">String</code> is the wrapper object type and is no stricter; it accepts any string and is almost never what you want."}],"answer":1},{"q":"<p>What does this type test do when <code dir=\"ltr\">Total</code> is <code dir=\"ltr\">number | undefined</code>?</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Expect</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Equal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Total</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>>;</span></span></code></pre></div>\n","options":[{"text":"It compiles, because <code dir=\"ltr\">number</code> is part of the union.","why":"<code dir=\"ltr\">Equal</code> checks that the two types are identical, not that one overlaps the other. A union with <code dir=\"ltr\">undefined</code> is a different type."},{"text":"It compiles, but logs a warning at runtime.","why":"Types are erased before the code runs, so a type test can only fail at compile time; there is nothing left to log."},{"text":"It fails to compile, because <code dir=\"ltr\">Equal</code> resolves to <code dir=\"ltr\">false</code> and <code dir=\"ltr\">Expect</code> only accepts <code dir=\"ltr\">true</code>.","why":"Correct. <code dir=\"ltr\">Expect&lt;T extends true&gt;</code> rejects <code dir=\"ltr\">false</code>, so the mismatch shows up as a type error on that line."}],"answer":2},{"q":"<p>You add <code dir=\"ltr\">// @ts-expect-error</code> above <code dir=\"ltr\">pay(order, 'EUR')</code>, but <code dir=\"ltr\">pay</code> accepts any string as the currency. What happens?</p>","options":[{"text":"Nothing; the directive is ignored when there is no error to suppress.","why":"That is how <code dir=\"ltr\">// @ts-ignore</code> behaves. <code dir=\"ltr\">@ts-expect-error</code> is stricter and complains when it has nothing to suppress."},{"text":"TypeScript reports “Unused ‘@ts-expect-error’ directive”, so the test fails.","why":"Correct. An unused <code dir=\"ltr\">@ts-expect-error</code> is itself an error, which is exactly why it works as a test that a bad call is rejected."},{"text":"The call is removed from the compiled JavaScript.","why":"Comments never change emitted code. The call still runs; only type checking is affected."}],"answer":1}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"// Cartwheel's five order statuses come from the orders table.\n// Right now the type accepts any string at all.\ntype OrderStatus = string;\n\nconst statusLabels: Record<OrderStatus, string> = {\n  processing: 'Processing',\n  shipped: 'Shipped',\n  delivered: 'Delivered',\n  cancelled: 'Cancelled',\n};\n\nfunction statusLabel(status: OrderStatus): string {\n  return statusLabels[status];\n}\n\nfunction canCancel(status: OrderStatus): boolean {\n  return status === 'processing';\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<OrderStatus, 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'>>;\n\n// @ts-expect-error: 'canceled' (one L) is a typo, not a status\ncanCancel('canceled');\n\nconsole.log(statusLabel('shipped'), canCancel('processing'));\n"},"solution":{"ts":"// Cartwheel's five order statuses come from the orders table.\ntype OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\n\nconst statusLabels: Record<OrderStatus, string> = {\n  processing: 'Processing',\n  shipped: 'Shipped',\n  delivered: 'Delivered',\n  cancelled: 'Cancelled',\n  returned: 'Returned',\n};\n\nfunction statusLabel(status: OrderStatus): string {\n  return statusLabels[status];\n}\n\nfunction canCancel(status: OrderStatus): boolean {\n  return status === 'processing';\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<OrderStatus, 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned'>>;\n\n// @ts-expect-error: 'canceled' (one L) is a typo, not a status\ncanCancel('canceled');\n\nconsole.log(statusLabel('shipped'), canCancel('processing'));\n"},"checks":[{"id":"types","typecheck":true},{"id":"returned-label","test":"return statusLabel('returned') === 'Returned';"},{"id":"can-cancel","test":"return canCancel('processing') === true && canCancel('shipped') === false;"}],"title":"Narrow the order status","prompt":"<p>Cartwheel orders have exactly five statuses: <code dir=\"ltr\">processing</code>, <code dir=\"ltr\">shipped</code>, <code dir=\"ltr\">delivered</code>, <code dir=\"ltr\">cancelled</code> and <code dir=\"ltr\">returned</code>. Replace <code dir=\"ltr\">string</code> in <code dir=\"ltr\">OrderStatus</code> with a union of those five literals so the type tests pass. Once the type is narrow, the compiler points at a gap in <code dir=\"ltr\">statusLabels</code>: fill it so <code dir=\"ltr\">statusLabel('returned')</code> returns <code dir=\"ltr\">'Returned'</code>.</p>\n","hints":["A union of string literals looks like <code dir=\"ltr\">'a' | 'b' | 'c'</code>.","After you change <code dir=\"ltr\">OrderStatus</code>, read the new error on <code dir=\"ltr\">statusLabels</code>: <code dir=\"ltr\">Record&lt;OrderStatus, string&gt;</code> now requires one key per status.","Add <code dir=\"ltr\">returned: 'Returned'</code> to the object."],"explanation":"<p>With <code dir=\"ltr\">OrderStatus</code> as <code dir=\"ltr\">string</code>, <code dir=\"ltr\">Record&lt;OrderStatus, string&gt;</code> means “any string keys”, so a missing status is invisible and the typo <code dir=\"ltr\">'canceled'</code> is a valid argument. As a union of five literals, the record must have exactly those five keys, and <code dir=\"ltr\">canCancel('canceled')</code> is rejected, which is what the <code dir=\"ltr\">@ts-expect-error</code> line asserts. Nothing changed at runtime except the added label: the types alone found both problems.</p>\n","checkLabels":{"types":"The file type-checks with zero errors (including the type tests)","returned-label":"<code dir=\"ltr\">statusLabel('returned')</code> returns <code dir=\"ltr\">'Returned'</code>","can-cancel":"<code dir=\"ltr\">canCancel</code> still allows only <code dir=\"ltr\">processing</code> orders to be cancelled"}},"words":596,"readMinutes":3}