{"id":"l-at-2-3","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Block unwanted inference sites with NoInfer, give type parameters sensible defaults, and choose between overloads, unions and separate functions when the return type depends on the input.","html":"<p>Here is a helper from Cartwheel’s checkout. It looks safe, and it has a hole:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">code</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'LOYAL5'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOM10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// compiles</span></span></code></pre></div>\n<p>The intent is clear: <code dir=\"ltr\">code</code> must be one of the available coupons. But <code dir=\"ltr\">C</code> appears in both parameters, and as you saw last lesson, every appearance is an inference site. TypeScript collects <code dir=\"ltr\">'WELCOME10'</code>, <code dir=\"ltr\">'LOYAL5'</code> and <code dir=\"ltr\">'WELCOM10'</code> as candidates, makes <code dir=\"ltr\">C</code> the union of all three, and everything type-checks. The typo was not checked against the list; it was added to it.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">NoInfer: check here, but do not infer from here</h2>\n<p>TypeScript 5.4 added the intrinsic type <code dir=\"ltr\">NoInfer&lt;T&gt;</code>. It evaluates to plain <code dir=\"ltr\">T</code>, but it marks that position as off-limits for inference:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">code</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> NoInfer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>) {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // …</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'LOYAL5'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOM10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type '\"WELCOM10\"' is not assignable to parameter of type '\"WELCOME10\" | \"LOYAL5\"'.</span></span></code></pre></div>\n<p>Now <code dir=\"ltr\">C</code> is inferred only from <code dir=\"ltr\">available</code>, and <code dir=\"ltr\">code</code> is checked against the result. The pattern appears whenever one argument defines a set and another argument must pick from it: an initial state that must be one of the declared states, a default value that must match the options, a sort key that must be one of the listed columns.</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> createMachine</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">S</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">config</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">states</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> S</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">initial</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> NoInfer</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">S</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\"> config;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">createMachine</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ states: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cart'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipping'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'payment'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], initial: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cart'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });    </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">createMachine</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ states: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cart'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipping'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'payment'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], initial: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'checkout'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// error</span></span></code></pre></div>\n<p>Before 5.4, the workaround was a second type parameter constrained by the first, <code dir=\"ltr\">&lt;C extends string, D extends C&gt;(available: C[], code: D)</code>. It works, and you will see it in older libraries, but it adds a parameter that exists only to dodge inference. Use <code dir=\"ltr\">NoInfer</code> in new code. It also documents intent: a reader who sees <code dir=\"ltr\">initial: NoInfer&lt;S&gt;</code> knows at a glance which argument is the source of truth and which one is only being checked, something the two-parameter trick never made obvious.</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>Wrapping the wrong position</span></p>\n<p><code dir=\"ltr\">NoInfer</code> goes on the position that should be <em>checked</em>, not the one that defines the type. Write <code dir=\"ltr\">available: NoInfer&lt;C&gt;[]</code> instead and <code dir=\"ltr\">C</code> is inferred from the single code alone, so the list is checked against the typo. Ask “which argument is the source of truth?” and leave that one unwrapped.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">Defaults for type parameters</h2>\n<p>A type parameter can have a default, used when there is nothing to infer from:</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:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ApiResponse</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">data</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">requestId</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\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> request</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">TRes</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">TBody</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">path</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 style=\"color:#953800;--shiki-dark:#FFA657\">body</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> TBody</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">TRes</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> res</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(path, { method: body </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> undefined</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'GET'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'POST'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, body: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">JSON</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">stringify</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(body) });</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> res.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>Two things make defaults useful. First, <code dir=\"ltr\">ApiResponse</code> on its own now means <code dir=\"ltr\">ApiResponse&lt;unknown&gt;</code>, which is a safe default: callers must narrow <code dir=\"ltr\">data</code> before using it. Second, defaults make <strong>partial explicit type arguments</strong> legal. Without them, <code dir=\"ltr\">request&lt;Order&gt;('/orders/1042')</code> would fail with “Expected 2 type arguments”; with them, <code dir=\"ltr\">TBody</code> falls back to <code dir=\"ltr\">undefined</code>.</p>\n<p>Choose defaults that are safe rather than convenient. <code dir=\"ltr\">unknown</code> is a good default for data that comes from outside; <code dir=\"ltr\">any</code> is not, because it silently switches off checking for everyone who forgets the type argument. (And notice that <code dir=\"ltr\">request&lt;Order&gt;</code> is still a cast in disguise: the response body is not checked. You will fix that properly in the next lesson and in <a href=\"/lesson/advanced-typescript/l-at-4-3\">Parsing Untrusted Data at the Boundary</a>.)</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">When the return type depends on the argument</h2>\n<p>Sometimes the <em>kind</em> of argument decides the kind of result. Overloads describe that directly:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"4\"><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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</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:#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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</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:#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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Array.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">isArray</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(cents) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> cents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(toDollars) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> toDollars</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(cents);</span></span>\n<span class=\"line\"><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\"> toDollars</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">cents</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:#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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `$${</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">cents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> /</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> one</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);          </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> many</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> formatPrice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1299</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string[]</span></span></code></pre></div>\n<p>The first two lines are the <strong>overload signatures</strong> callers see. The third is the <strong>implementation signature</strong>, which must be compatible with all of them and is invisible from outside. TypeScript tries the overloads top to bottom and uses the first that matches, so list the most specific first.</p>\n<p>Overloads have one sharp edge: a caller holding a <code dir=\"ltr\">number | number[]</code> matches <em>neither</em> overload, and gets “No overload matches this call”. You would need a third overload that accepts the union. That edge is a hint about the design. If callers often have the union, the function probably wants a single signature; if they rarely do, two separate functions with clear names (<code dir=\"ltr\">formatPrice</code>, <code dir=\"ltr\">formatPrices</code>) usually beat one overloaded one.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>A default for API design</span></p>\n<p>Prefer, in order: separate, well-named functions; a single signature with a union where the return type does not depend on the input; overloads when you are typing an existing JavaScript API or the convenience really matters. Conditional return types on generic functions come last; they usually need casts inside the implementation, which you will see in section 3.</p>\n</aside>\n<p>In the exercise you will fix three of these inference problems in Cartwheel’s checkout. The next lesson puts everything from this section together in one typed API client.</p>\n","toc":[{"id":"sec-1","text":"NoInfer: check here, but do not infer from here","level":2},{"id":"sec-2","text":"Defaults for type parameters","level":2},{"id":"sec-3","text":"When the return type depends on the argument","level":2}],"takeaways":["Every appearance of a type parameter is an inference site, so a typo in one argument can widen the type instead of being rejected.","<code dir=\"ltr\">NoInfer&lt;T&gt;</code> (TypeScript 5.4+) marks a position as check-only: it is validated against <code dir=\"ltr\">T</code> but never contributes a candidate.","Type parameter defaults apply when there is nothing to infer from, and they make partial explicit type arguments possible.","Overloads let the return type depend on the argument type, but a union argument matches none of them; two well-named functions are often clearer."],"further":[{"title":"NoInfer (TypeScript 5.4 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-4.html"},{"title":"Function overloads","url":"https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads"},{"title":"NoInfer utility type","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html#noinfertype"}],"quiz":[{"q":"<p>Why does this call compile even though <code dir=\"ltr\">'WELCOM10'</code> is a typo?</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">C</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">available</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">code</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> C</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {}</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">applyCoupon</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOME10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'LOYAL5'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'WELCOM10'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n","options":[{"text":"Because string literals always widen to <code dir=\"ltr\">string</code> in generic calls.","why":"The <code dir=\"ltr\">extends string</code> constraint keeps the literals. The issue is where those literals come from."},{"text":"Because <code dir=\"ltr\">code</code> is also an inference site, so <code dir=\"ltr\">C</code> becomes <code dir=\"ltr\">'WELCOME10' | 'LOYAL5' | 'WELCOM10'</code>.","why":"Correct. The typo is collected as a candidate instead of being checked. <code dir=\"ltr\">code: NoInfer&lt;C&gt;</code> fixes it."},{"text":"Because arrays are inferred after plain arguments.","why":"Arrays are not context-sensitive; both arguments contribute candidates in the same pass."}],"answer":1},{"q":"<p><code dir=\"ltr\">function request&lt;TRes = unknown, TBody = undefined&gt;(path: string, body?: TBody): Promise&lt;TRes&gt;</code>. What does <code dir=\"ltr\">request&lt;Order&gt;('/orders/1')</code> do?</p>","options":[{"text":"It is an error, because only one of the two type arguments was given.","why":"That rule applies to type parameters without defaults. Here <code dir=\"ltr\">TBody</code> has a default, so it can be left out."},{"text":"<code dir=\"ltr\">TRes</code> is <code dir=\"ltr\">Order</code> and <code dir=\"ltr\">TBody</code> is inferred from the missing <code dir=\"ltr\">body</code>.","why":"Once you pass explicit type arguments, nothing is inferred; omitted ones take their defaults."},{"text":"<code dir=\"ltr\">TRes</code> is <code dir=\"ltr\">Order</code> and <code dir=\"ltr\">TBody</code> falls back to its default, <code dir=\"ltr\">undefined</code>.","why":"Correct. Defaults are what make partial explicit type arguments legal."}],"answer":2},{"q":"<p>With these overloads, what happens on the last line?</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getOrder</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">ids</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Order</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]>;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">idOrIds</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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:#6E7781;--shiki-dark:#8B949E\">/* … */</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> input</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</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>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">getOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(input);</span></span></code></pre></div>\n","options":[{"text":"It returns <code dir=\"ltr\">Promise&lt;Order | Order[]&gt;</code> from the implementation signature.","why":"The implementation signature is not visible to callers. Only the overload signatures above it can be matched."},{"text":"It is an error, “No overload matches this call”, because no single overload accepts the union.","why":"Correct. Overloads are tried one at a time, and neither accepts <code dir=\"ltr\">number | number[]</code>. You would need a third overload for the union."},{"text":"It picks the first overload, because overloads are tried in order.","why":"Order decides which overload wins when several match. Here none match, because <code dir=\"ltr\">number[]</code> fails the first one."}],"answer":1},{"q":"<p>You are designing a lookup that returns one order for an id and a list for a customer. Which API is easiest for callers and maintainers?</p>","options":[{"text":"Two functions, <code dir=\"ltr\">getOrder(id)</code> and <code dir=\"ltr\">getOrdersForCustomer(customerId)</code>, each with a plain signature.","why":"Correct. Each function says what it does, needs no overloads or conditional types, and inference is trivial."},{"text":"One overloaded <code dir=\"ltr\">get(idOrCustomer)</code> with two overload signatures.","why":"This works but packs two intentions into one name, and callers with union inputs will hit “No overload matches”."},{"text":"One generic <code dir=\"ltr\">get&lt;T&gt;(key)</code> with a conditional return type based on <code dir=\"ltr\">T</code>.","why":"Conditional return types usually need casts inside the implementation, and the call site is less clear than a named function."}],"answer":0}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"function applyCoupon<C extends string>(available: C[], code: C): string {\n  return available.includes(code) ? `Applied ${code}` : `Unknown coupon ${code}`;\n}\n\nfunction createMachine<S extends string>(config: { states: S[]; initial: S }) {\n  return { states: config.states, initial: config.initial };\n}\n\nfunction formatPrice(cents: number | number[]): string | string[] {\n  return Array.isArray(cents) ? cents.map(toDollars) : toDollars(cents);\n}\n\nfunction toDollars(cents: number): string {\n  return `$${(cents / 100).toFixed(2)}`;\n}\n\n// ---- Type tests (do not edit) ----\napplyCoupon(['WELCOME10', 'LOYAL5'], 'LOYAL5');\n// @ts-expect-error: 'WELCOM10' is a typo, not an available coupon\napplyCoupon(['WELCOME10', 'LOYAL5'], 'WELCOM10');\n\nconst machine = createMachine({ states: ['cart', 'shipping', 'payment'], initial: 'cart' });\ntype T1 = Expect<Equal<typeof machine.initial, 'cart' | 'shipping' | 'payment'>>;\n// @ts-expect-error: 'checkout' is not one of the declared states\ncreateMachine({ states: ['cart', 'shipping', 'payment'], initial: 'checkout' });\n\nconst one = formatPrice(4500);\nconst many = formatPrice([4500, 1299]);\ntype T2 = Expect<Equal<typeof one, string>>;\ntype T3 = Expect<Equal<typeof many, string[]>>;\n\nconsole.log(applyCoupon(['WELCOME10'], 'WELCOME10'), machine.initial, one, many);\n"},"solution":{"ts":"function applyCoupon<C extends string>(available: C[], code: NoInfer<C>): string {\n  return available.includes(code) ? `Applied ${code}` : `Unknown coupon ${code}`;\n}\n\nfunction createMachine<S extends string>(config: { states: S[]; initial: NoInfer<S> }) {\n  return { states: config.states, initial: config.initial };\n}\n\nfunction formatPrice(cents: number): string;\nfunction formatPrice(cents: number[]): string[];\nfunction formatPrice(cents: number | number[]): string | string[] {\n  return Array.isArray(cents) ? cents.map(toDollars) : toDollars(cents);\n}\n\nfunction toDollars(cents: number): string {\n  return `$${(cents / 100).toFixed(2)}`;\n}\n\n// ---- Type tests (do not edit) ----\napplyCoupon(['WELCOME10', 'LOYAL5'], 'LOYAL5');\n// @ts-expect-error: 'WELCOM10' is a typo, not an available coupon\napplyCoupon(['WELCOME10', 'LOYAL5'], 'WELCOM10');\n\nconst machine = createMachine({ states: ['cart', 'shipping', 'payment'], initial: 'cart' });\ntype T1 = Expect<Equal<typeof machine.initial, 'cart' | 'shipping' | 'payment'>>;\n// @ts-expect-error: 'checkout' is not one of the declared states\ncreateMachine({ states: ['cart', 'shipping', 'payment'], initial: 'checkout' });\n\nconst one = formatPrice(4500);\nconst many = formatPrice([4500, 1299]);\ntype T2 = Expect<Equal<typeof one, string>>;\ntype T3 = Expect<Equal<typeof many, string[]>>;\n\nconsole.log(applyCoupon(['WELCOME10'], 'WELCOME10'), machine.initial, one, many);\n"},"checks":[{"id":"types","typecheck":true},{"id":"format","test":"return formatPrice(4500) === '$45.00' && formatPrice([1299, 5]).join() === '$12.99,$0.05';"},{"id":"coupon","test":"return applyCoupon(['A', 'B'], 'B') === 'Applied B';"}],"title":"Stop the typos, type the overloads","prompt":"<p>Fix three inference problems in Cartwheel’s checkout. Do not change the calls under the type tests.</p>\n<ol>\n<li><code dir=\"ltr\">applyCoupon(available, code)</code> must reject a <code dir=\"ltr\">code</code> that is not in <code dir=\"ltr\">available</code>, instead of adding it to the union.</li>\n<li><code dir=\"ltr\">createMachine({ states, initial })</code> must reject an <code dir=\"ltr\">initial</code> state that is not in <code dir=\"ltr\">states</code>.</li>\n<li><code dir=\"ltr\">formatPrice</code> must return <code dir=\"ltr\">string</code> for a number and <code dir=\"ltr\">string[]</code> for an array of numbers.</li>\n</ol>\n","hints":["For 1 and 2, the type parameter should be inferred only from the list, not from the single value.","Wrap the check-only position in <code dir=\"ltr\">NoInfer&lt;…&gt;</code>: <code dir=\"ltr\">code: NoInfer&lt;C&gt;</code>.","For 3, add two overload signatures above the existing implementation: <code dir=\"ltr\">function formatPrice(cents: number): string;</code> and <code dir=\"ltr\">function formatPrice(cents: number[]): string[];</code>."],"explanation":"<p><code dir=\"ltr\">code</code> and <code dir=\"ltr\">initial</code> were inference sites, so a typo became one more candidate and the union grew to include it. <code dir=\"ltr\">NoInfer&lt;C&gt;</code> keeps the type the same but removes that position from inference, so <code dir=\"ltr\">C</code> comes only from the list and the single value is checked against it. For <code dir=\"ltr\">formatPrice</code>, the union signature cannot say which input produces which output; overload signatures can, and the original union signature stays as the hidden implementation signature.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including all type tests","format":"<code dir=\"ltr\">formatPrice</code> formats a single price and a list of prices","coupon":"<code dir=\"ltr\">applyCoupon</code> still applies a valid coupon"}},"words":691,"readMinutes":3}