{"id":"l-at-2-2","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Predict what TypeScript infers for a type parameter, keep literal types with constraints and const type parameters, and order callback arguments so inference flows the right way.","html":"<p>In the last lesson you wrote <code dir=\"ltr\">sortByTotal(orders)</code> and TypeScript worked out that <code dir=\"ltr\">T</code> was <code dir=\"ltr\">DetailedOrder</code>. Most of the time inference does what you want, which makes the times it does not feel random. They are not. TypeScript follows a small set of rules, and once you know them you can design signatures that infer exactly the type you intend, with no <code dir=\"ltr\">&lt;…&gt;</code> at the call site.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Inference sites and candidates</h2>\n<p>Every place a type parameter appears inside a parameter’s type is an <strong>inference site</strong>. At a call, TypeScript matches each argument against its parameter type and collects a <strong>candidate</strong> for the type parameter from each site. Then it picks one type from the candidates.</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\"> same</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">a</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\">b</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</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\"> [a, b];</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\">same</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// T = string, so string[]</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">same</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'a'</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 'string' is not assignable to parameter of type 'number'.</span></span></code></pre></div>\n<p>With two string literals, the candidates <code dir=\"ltr\">'processing'</code> and <code dir=\"ltr\">'shipped'</code> are combined and then widened to <code dir=\"ltr\">string</code>, because <code dir=\"ltr\">T</code> is only returned inside an array (the next section explains when literals survive). With <code dir=\"ltr\">1</code> and <code dir=\"ltr\">'a'</code>, TypeScript does not invent <code dir=\"ltr\">number | string</code>; it picks the first candidate, <code dir=\"ltr\">number</code>, and then the second argument fails against it. That is deliberate. Silently widening to a union would hide the bugs generics are meant to catch. If you really want mixed values, say so: <code dir=\"ltr\">same&lt;number | string&gt;(1, 'a')</code>.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-2-2-t1\"><defs><marker id=\"arrow-l-at-2-2-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-at-2-2-t1\">Inference pipeline. Two arguments at the call site each produce a candidate for T through their inference sites. The candidates are combined and widened by rules into one chosen T, which is then substituted into the return type.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"30\" width=\"170\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"95\" y=\"57\" text-anchor=\"middle\">a: 'processing'</text>\n  <rect class=\"d-box\" x=\"10\" y=\"130\" width=\"170\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"95\" y=\"157\" text-anchor=\"middle\">b: 'shipped'</text>\n  <path class=\"d-arrow\" d=\"M180 52 L250 90\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <path class=\"d-arrow\" d=\"M180 152 L250 116\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <rect class=\"d-box-warn\" x=\"254\" y=\"66\" width=\"180\" height=\"72\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"344\" y=\"94\" text-anchor=\"middle\">candidates</text>\n  <text class=\"d-label-muted\" x=\"344\" y=\"118\" text-anchor=\"middle\">combine, widen?</text>\n  <path class=\"d-arrow\" d=\"M434 102 L478 102\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"482\" y=\"66\" width=\"208\" height=\"72\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"586\" y=\"94\" text-anchor=\"middle\">T chosen</text>\n  <text class=\"d-code\" x=\"586\" y=\"118\" text-anchor=\"middle\">string</text>\n  <path class=\"d-arrow\" d=\"M586 138 L586 172\" marker-end=\"url(#arrow-l-at-2-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"482\" y=\"176\" width=\"208\" height=\"44\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"586\" y=\"203\" text-anchor=\"middle\">return: T[]</text>\n</svg><figcaption>From arguments to a chosen type to the return type</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">When literals widen</h2>\n<p>The second rule explains most “why did I get <code dir=\"ltr\">string</code>?” moments. A literal candidate like <code dir=\"ltr\">'processing'</code> is kept or widened depending on the signature:</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\"> identity</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">value</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:#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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> value; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">value</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { value }; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> boxStatus</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\"> 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\">value</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { value }; }</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\"> a</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> identity</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'processing'</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);       </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { value: string }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> c</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> boxStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { value: 'processing' }</span></span></code></pre></div>\n<p>The literal survives when <code dir=\"ltr\">T</code> is returned directly at the top level (<code dir=\"ltr\">identity</code>), or when <code dir=\"ltr\">T</code>’s constraint includes a primitive type such as <code dir=\"ltr\">string</code> (<code dir=\"ltr\">boxStatus</code>). Otherwise it widens, on the theory that a value tucked into an object will probably be reassigned later.</p>\n<p>Objects follow the same logic, and here a constraint has a second benefit: it gives the argument a <strong>contextual type</strong>.</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\"> 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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> draft</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">order</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:#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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> draftOrder</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\"> extends</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 style=\"color:#953800;--shiki-dark:#FFA657\">order</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:#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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order; }</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">draft</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> });      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { status: string }</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">draftOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ status: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { status: 'processing' }</span></span></code></pre></div>\n<p>Because the constraint says <code dir=\"ltr\">status</code> is an <code dir=\"ltr\">OrderStatus</code>, TypeScript checks the literal against that union while inferring, and the literal type sticks. A good constraint both documents the function and improves what it infers.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">const type parameters</h2>\n<p>Sometimes you want the caller’s argument exactly as written: tuple order, literal values, nested objects. Before TypeScript 5.0 that meant asking every caller to write <code dir=\"ltr\">as const</code>. Now the function can ask for it:</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> defineFlow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> readonly</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> OrderStatus</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">steps</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</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\"> steps;</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\"> happyPath</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> defineFlow</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'delivered'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// readonly ['processing', 'shipped', 'delivered']</span></span></code></pre></div>\n<p>The <code dir=\"ltr\">const</code> modifier tells TypeScript to infer the argument as if it had <code dir=\"ltr\">as const</code>: literals stay literal, arrays become readonly tuples, object properties become readonly. Pair it with a <code dir=\"ltr\">readonly</code> array constraint, since <code dir=\"ltr\">as const</code> produces readonly arrays. It only affects literals written directly in the call; a variable you pass in keeps whatever type it already had.</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>When to reach for const</span></p>\n<p>Use <code dir=\"ltr\">const</code> type parameters for definition-style APIs: route tables, feature flags, state machines, form schemas, anything where the caller writes a literal once and you derive types from it. You will use one in <a href=\"/lesson/advanced-typescript/l-at-2-4\">Designing a Typed API Client</a>. For ordinary data functions like <code dir=\"ltr\">sortByTotal</code>, it adds nothing.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Callbacks are inferred last</h2>\n<p>Arguments that are functions with unannotated parameters, such as <code dir=\"ltr\">(d) =&gt; d.totalCents</code>, are <strong>context-sensitive</strong>: their parameter types come from the signature, which may still depend on <code dir=\"ltr\">T</code>. TypeScript infers from everything else first, then types these callbacks. Inside a single object literal, it works through context-sensitive properties from left to right:</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\"> createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">config</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">load</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</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\"> T</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">render</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:#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:#8250DF;--shiki-dark:#D2A8FF\">createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  load</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ orderId, totalCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }),</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d.totalCents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// d: { orderId: number; totalCents: number }</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\">createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d.totalCents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'd' is of type 'unknown'.</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  load</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ orderId, totalCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</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>When <code dir=\"ltr\">render</code> comes first, <code dir=\"ltr\">T</code> has no candidate yet. You can fix it at the call site by swapping the properties or annotating <code dir=\"ltr\">orderId: number</code>, which makes <code dir=\"ltr\">load</code> no longer context-sensitive. Better, fix it in the API: put the producer before the consumer in your parameter lists and documented examples, so the natural way to call it is the way that infers.</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>Expecting partial inference</span></p>\n<p><code dir=\"ltr\">convert&lt;string&gt;('12', Number)</code> on a function with two type parameters is an error, “Expected 2 type arguments, but got 1”. TypeScript does not infer the rest once you specify any. Either let it infer everything, give the later parameters defaults (they then take the default type, not an inferred one), or split the function in two (<code dir=\"ltr\">convert('12').to(Number)</code>), which is the shape many typed libraries use for exactly this reason. The next lesson covers defaults and the other inference controls.</p>\n</aside>\n<p>In the exercise you will make three Cartwheel helpers keep the literals their callers pass. Next, you will learn to stop inference from happening where it should not.</p>\n","toc":[{"id":"sec-1","text":"Inference sites and candidates","level":2},{"id":"sec-2","text":"When literals widen","level":2},{"id":"sec-3","text":"const type parameters","level":2},{"id":"sec-4","text":"Callbacks are inferred last","level":2}],"takeaways":["Every place a type parameter appears in a parameter type is an inference site; TypeScript collects a candidate from each argument and picks one type.","Inferred literals widen unless the type parameter’s constraint includes a primitive type or the parameter is declared <code dir=\"ltr\">const</code>.","A <code dir=\"ltr\">const</code> type parameter infers arguments as if they were written with <code dir=\"ltr\">as const</code>, keeping literals and readonly tuples without asking the caller.","Callbacks with unannotated parameters are inferred after other arguments, and inside one object literal TypeScript works left to right.","Explicit type arguments are all or nothing; specify one and TypeScript stops inferring the rest."],"further":[{"title":"Type Inference","url":"https://www.typescriptlang.org/docs/handbook/type-inference.html"},{"title":"const type parameters (TypeScript 5.0 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html"}],"quiz":[{"q":"<p>What is the type of <code dir=\"ltr\">b</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">value</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:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">value</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:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { value }; }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> box</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'processing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">{ value: 'processing' }</code>","why":"That is what you would get with <code dir=\"ltr\">T extends string</code> or a <code dir=\"ltr\">const</code> type parameter. An unconstrained <code dir=\"ltr\">T</code> nested in the return type widens."},{"text":"<code dir=\"ltr\">{ value: string }</code>","why":"Correct. The literal candidate widens because <code dir=\"ltr\">T</code> has no primitive constraint and is not returned at the top level."},{"text":"<code dir=\"ltr\">{ value: unknown }</code>","why":"<code dir=\"ltr\">unknown</code> is what TypeScript falls back to when it finds no candidate at all. Here it found one: the string argument."}],"answer":1},{"q":"<p>Which declaration makes <code dir=\"ltr\">defineFlow(['processing', 'shipped'])</code> return the type <code dir=\"ltr\">readonly ['processing', 'shipped']</code> without the caller writing <code dir=\"ltr\">as const</code>?</p>","options":[{"text":"<code dir=\"ltr\">function defineFlow&lt;T&gt;(steps: T[]): T[]</code>","why":"This infers <code dir=\"ltr\">T</code> as <code dir=\"ltr\">string</code> and returns <code dir=\"ltr\">string[]</code>; the order and the literals are both lost."},{"text":"<code dir=\"ltr\">function defineFlow&lt;T extends string&gt;(steps: T[]): T[]</code>","why":"The constraint keeps the literals, but you get the array type <code dir=\"ltr\">('processing' | 'shipped')[]</code>, not a readonly tuple."},{"text":"<code dir=\"ltr\">function defineFlow&lt;const T extends readonly string[]&gt;(steps: T): T</code>","why":"Correct. The <code dir=\"ltr\">const</code> modifier infers the argument as if it had <code dir=\"ltr\">as const</code>, so you get a readonly tuple of literals."},{"text":"<code dir=\"ltr\">function defineFlow(steps: readonly string[]): readonly string[]</code>","why":"Without a type parameter there is nothing to capture the argument’s precise type."}],"answer":2},{"q":"<p>Why is <code dir=\"ltr\">d</code> typed as <code dir=\"ltr\">unknown</code> here?</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:#8250DF;--shiki-dark:#D2A8FF\">createStep</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  render</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">d</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> d.totalCents.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toFixed</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(),</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  load</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ orderId, totalCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">4500</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","options":[{"text":"Because <code dir=\"ltr\">render</code> comes before <code dir=\"ltr\">load</code>, and TypeScript infers context-sensitive functions in an object literal from left to right.","why":"Correct. When <code dir=\"ltr\">render</code> is checked, <code dir=\"ltr\">T</code> has no candidate yet. Put <code dir=\"ltr\">load</code> first, or annotate <code dir=\"ltr\">orderId</code> so <code dir=\"ltr\">load</code> is not context-sensitive."},{"text":"Because <code dir=\"ltr\">T</code> cannot be inferred from a return type.","why":"It can; <code dir=\"ltr\">load</code>’s return type is the inference site for <code dir=\"ltr\">T</code>. The problem is when that site is visited."},{"text":"Because arrow functions in object literals disable inference.","why":"Inference works fine in object literals; swapping the two properties makes this compile."}],"answer":0},{"q":"<p><code dir=\"ltr\">function convert&lt;From, To&gt;(value: From, to: (v: From) =&gt; To): To</code> is called as <code dir=\"ltr\">convert&lt;string&gt;('12', Number)</code>. What happens?</p>","options":[{"text":"<code dir=\"ltr\">To</code> is inferred as <code dir=\"ltr\">number</code> from <code dir=\"ltr\">Number</code>.","why":"TypeScript does not do partial inference. Once you pass any explicit type argument, it expects all of the required ones."},{"text":"It is an error, “Expected 2 type arguments, but got 1”.","why":"Correct. Explicit type arguments are all or nothing, unless the remaining parameters have defaults."},{"text":"<code dir=\"ltr\">To</code> becomes <code dir=\"ltr\">unknown</code>, and the call compiles.","why":"Missing type arguments only fall back silently when they have defaults. Without one, the call is rejected."}],"answer":1}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Flag = { rollout: number; countries: readonly string[] };\n\nfunction defineFlow(steps: OrderStatus[]) {\n  return steps;\n}\n\nfunction oneOf(values: readonly string[]) {\n  return (input: string): boolean => values.includes(input);\n}\n\nfunction defineFlags(flags: Record<string, Flag>) {\n  return flags;\n}\n\n// ---- Type tests (do not edit) ----\nconst happyPath = defineFlow(['processing', 'shipped', 'delivered']);\ntype T1 = Expect<Equal<typeof happyPath, readonly ['processing', 'shipped', 'delivered']>>;\n\n// @ts-expect-error: 'refunded' is not an order status\ndefineFlow(['processing', 'refunded']);\n\nconst isChannel = oneOf(['web', 'mobile_app', 'marketplace']);\nconst raw: string = 'mobile_app';\nif (isChannel(raw)) {\n  type T2 = Expect<Equal<typeof raw, 'web' | 'mobile_app' | 'marketplace'>>;\n}\n\nconst flags = defineFlags({\n  expressShipping: { rollout: 0.5, countries: ['EG', 'AE'] },\n  giftWrap: { rollout: 1, countries: [] },\n});\ntype T3 = Expect<Equal<typeof flags.expressShipping.countries, readonly ['EG', 'AE']>>;\n\n// @ts-expect-error: every flag needs a rollout\ndefineFlags({ applePay: { countries: ['US'] } });\n\nconsole.log(happyPath, isChannel('web'), isChannel('fax'), flags.expressShipping.countries);\n"},"solution":{"ts":"type OrderStatus = 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'returned';\ntype Flag = { rollout: number; countries: readonly string[] };\n\nfunction defineFlow<const T extends readonly OrderStatus[]>(steps: T): T {\n  return steps;\n}\n\nfunction oneOf<const T extends readonly string[]>(values: T) {\n  return (input: string): input is T[number] => (values as readonly string[]).includes(input);\n}\n\nfunction defineFlags<const T extends Record<string, Flag>>(flags: T): T {\n  return flags;\n}\n\n// ---- Type tests (do not edit) ----\nconst happyPath = defineFlow(['processing', 'shipped', 'delivered']);\ntype T1 = Expect<Equal<typeof happyPath, readonly ['processing', 'shipped', 'delivered']>>;\n\n// @ts-expect-error: 'refunded' is not an order status\ndefineFlow(['processing', 'refunded']);\n\nconst isChannel = oneOf(['web', 'mobile_app', 'marketplace']);\nconst raw: string = 'mobile_app';\nif (isChannel(raw)) {\n  type T2 = Expect<Equal<typeof raw, 'web' | 'mobile_app' | 'marketplace'>>;\n}\n\nconst flags = defineFlags({\n  expressShipping: { rollout: 0.5, countries: ['EG', 'AE'] },\n  giftWrap: { rollout: 1, countries: [] },\n});\ntype T3 = Expect<Equal<typeof flags.expressShipping.countries, readonly ['EG', 'AE']>>;\n\n// @ts-expect-error: every flag needs a rollout\ndefineFlags({ applePay: { countries: ['US'] } });\n\nconsole.log(happyPath, isChannel('web'), isChannel('fax'), flags.expressShipping.countries);\n"},"checks":[{"id":"types","typecheck":true},{"id":"one-of","test":"const g = oneOf(['a', 'b']); return g('a') === true && g('c') === false;"},{"id":"identity","test":"const f = { x: { rollout: 1, countries: [] } }; return defineFlags(f) === f && defineFlow(['shipped']).join() === 'shipped';"}],"title":"Keep the caller's literals","prompt":"<p>Three definition helpers in Cartwheel’s checkout lose the precise types their callers write. Make the type tests pass without changing the calls (no <code dir=\"ltr\">as const</code> at the call sites):</p>\n<ul>\n<li><code dir=\"ltr\">defineFlow(steps)</code> returns the steps as a readonly tuple of literal statuses, and still rejects unknown statuses.</li>\n<li><code dir=\"ltr\">oneOf(values)</code> returns a type guard: after <code dir=\"ltr\">if (isChannel(raw))</code>, <code dir=\"ltr\">raw</code> is narrowed to the literal values passed in.</li>\n<li><code dir=\"ltr\">defineFlags(flags)</code> keeps each flag’s exact shape (including the <code dir=\"ltr\">countries</code> tuple) while still checking every flag against <code dir=\"ltr\">Flag</code>.</li>\n</ul>\n<p>All three return their argument unchanged at runtime (<code dir=\"ltr\">oneOf</code> returns a checking function).</p>\n","hints":["A <code dir=\"ltr\">const</code> type parameter infers the argument as if the caller had written <code dir=\"ltr\">as const</code>.","Give each function a type parameter whose constraint is the old parameter type, for example <code dir=\"ltr\">&lt;const T extends readonly OrderStatus[]&gt;(steps: T): T</code>.","For <code dir=\"ltr\">oneOf</code>, the returned function’s return type is a type predicate: <code dir=\"ltr\">(input: string): input is T[number] =&gt; …</code>. Inside, widen the tuple for <code dir=\"ltr\">includes</code>: <code dir=\"ltr\">(values as readonly string[]).includes(input)</code>."],"explanation":"<p>Without a type parameter, each function’s return type is just its parameter annotation, so the literals the caller wrote are gone. A <code dir=\"ltr\">const</code> type parameter captures the argument with <code dir=\"ltr\">as const</code> semantics: tuples stay ordered and readonly, strings stay literal. The constraint (<code dir=\"ltr\">extends readonly OrderStatus[]</code>, <code dir=\"ltr\">extends Record&lt;string, Flag&gt;</code>) still validates the argument, so <code dir=\"ltr\">'refunded'</code> and a flag without <code dir=\"ltr\">rollout</code> are rejected. In <code dir=\"ltr\">oneOf</code>, <code dir=\"ltr\">T[number]</code> is the union of the tuple’s elements, and the type predicate hands that union to the caller’s <code dir=\"ltr\">if</code>.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including all type tests","one-of":"<code dir=\"ltr\">oneOf</code> returns a function that accepts only the listed values","identity":"<code dir=\"ltr\">defineFlow</code> and <code dir=\"ltr\">defineFlags</code> return their argument unchanged"}},"words":722,"readMinutes":3}