{"id":"l-at-2-1","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Write generic functions whose type parameters link inputs to outputs, constrain them with extends, and recognise generics that are really unchecked casts.","html":"<p>Here is a helper from Cartwheel’s order history page. It is typed, it compiles, and it quietly throws information away:</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\"> DetailedOrder</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\">totalCents</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\">customer</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\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">totalCents</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:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">totalCents</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:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.totalCents);</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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DetailedOrder</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\"> sorted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sorted[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].customer;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Property 'customer' does not exist on type '{ totalCents: number; }'.</span></span></code></pre></div>\n<p>The objects in <code dir=\"ltr\">sorted</code> are still full orders at runtime. But the function’s signature can only describe its output in terms of its input annotation, and that annotation only mentions <code dir=\"ltr\">totalCents</code>. The fix is not a wider annotation. It is a <strong>relationship</strong>: “whatever kind of object you give me, you get the same kind back”.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">A type parameter is a relationship</h2>\n<p>That relationship is what a generic says:</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\"> sortByTotal</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\">totalCents</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\">orders</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\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">orders].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.totalCents);</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\"> sorted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// DetailedOrder[]</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sorted[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">].customer;                 </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// string</span></span></code></pre></div>\n<p>Read the signature aloud: “for some type <code dir=\"ltr\">T</code> that has a numeric <code dir=\"ltr\">totalCents</code>, take an array of <code dir=\"ltr\">T</code> and return an array of <code dir=\"ltr\">T</code>”. The caller does not write <code dir=\"ltr\">&lt;DetailedOrder&gt;</code>; TypeScript infers <code dir=\"ltr\">T</code> from the argument (the next lesson is about exactly how).</p>\n<p>Two parts are doing the work. <code dir=\"ltr\">T</code> appears in the parameter and in the return type, which links them. And <code dir=\"ltr\">extends { totalCents: number }</code> is a <strong>constraint</strong>: it limits which types <code dir=\"ltr\">T</code> can be, which is what makes <code dir=\"ltr\">a.totalCents</code> legal inside the function. Without it, <code dir=\"ltr\">T</code> could be anything, and TypeScript would reject the property access.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">The appears-twice rule</h2>\n<p>The most useful test for a generic, and the one I apply in every code review: <strong>each type parameter should appear at least twice</strong>. Once to capture a type, once to use it somewhere else.</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\"> lastItem</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\">items</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:#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:#6E7781;--shiki-dark:#8B949E\">// input → output</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> items[items.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> -</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> indexById</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\">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\">items</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\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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\">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:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(items.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">item</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [item.id, item]));</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\"> merge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">A</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">B</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\"> A</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\"> B</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> A</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> B</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {              </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// two inputs → output</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">a, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">b };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>A parameter that appears once links nothing. <code dir=\"ltr\">function log&lt;T&gt;(value: T): void</code> is just <code dir=\"ltr\">function log(value: unknown): void</code> with extra syntax, and <code dir=\"ltr\">function count&lt;T&gt;(items: T[]): number</code> is <code dir=\"ltr\">items: unknown[]</code>. Remove those type parameters; the signatures get easier to read and nothing is lost.</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>Generics as casts</span></p>\n<p><code dir=\"ltr\">function parseJson&lt;T&gt;(text: string): T { return JSON.parse(text); }</code> looks type-safe, and every call site reads nicely: <code dir=\"ltr\">parseJson&lt;Order&gt;(body)</code>. But <code dir=\"ltr\">T</code> appears only in the return type, so the caller can pick any type and nothing checks it. It is <code dir=\"ltr\">JSON.parse(body) as Order</code> in disguise, and it hides the cast where reviewers will not see it. Return <code dir=\"ltr\">unknown</code> and validate (see <a href=\"/lesson/advanced-typescript/l-at-4-3\">Parsing Untrusted Data at the Boundary</a>); when you truly must cast, write <code dir=\"ltr\">as</code> at the call site where it is visible.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">The caller chooses T</h2>\n<p>This error confuses almost everyone the first time:</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\"> Shippable</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">weightGrams</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> withDefaultWeight</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:#953800;--shiki-dark:#FFA657\"> Shippable</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\"> { weightGrams: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">500</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  // Error: Type '{ weightGrams: number; }' is not assignable to type 'T'.</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  //   '{ weightGrams: number; }' is assignable to the constraint of type 'T', but 'T'</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">  //   could be instantiated with a different subtype of constraint 'Shippable'.</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>The constraint says <code dir=\"ltr\">T</code> is <em>at least</em> <code dir=\"ltr\">Shippable</code>. The caller might ask for <code dir=\"ltr\">withDefaultWeight&lt;{ weightGrams: number; sku: string }&gt;()</code>, and your literal has no <code dir=\"ltr\">sku</code>. Inside a generic function, <code dir=\"ltr\">T</code> is a type you do not get to choose; you can only produce a <code dir=\"ltr\">T</code> from values that already are one, such as the arguments. If your function builds the value itself, its return type is the constraint (<code dir=\"ltr\">Shippable</code>), and no generic is needed.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Constraints that use other parameters</h2>\n<p>A constraint can refer to another type parameter. The classic case picks a property by name:</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\"> pluck</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\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</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\">items</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\">key</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</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:#953800;--shiki-dark:#FFA657\">K</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\"> items.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">item</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> item[key]);</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\"> customers</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> pluck</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'customer'</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:#8250DF;--shiki-dark:#D2A8FF\">pluck</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'email'</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 '\"email\"' is not assignable to parameter of type 'keyof DetailedOrder'.</span></span></code></pre></div>\n<p><code dir=\"ltr\">keyof T</code> is the union of <code dir=\"ltr\">T</code>’s property names, and <code dir=\"ltr\">T[K]</code> is the type of the property <code dir=\"ltr\">K</code>. You will take both apart in section 3; for now, notice the pattern. <code dir=\"ltr\">K</code> appears in the parameter and in the return type, and its constraint ties it to <code dir=\"ltr\">T</code>. Each parameter appears at least twice, and each one earns its place.</p>\n<h2 id=\"sec-5\" tabindex=\"-1\">Generic or plain union?</h2>\n<p>Not every function that accepts several types needs a type parameter. Ask one question: <strong>does the output type depend on which input type the caller passed?</strong></p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"5\"><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\">// Output is always a number, whatever came in: a union is enough.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> toCents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">amount</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\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</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:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">Number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(amount) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</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:#6E7781;--shiki-dark:#8B949E\">// Output depends on the input: a generic says so.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> firstOrThrow</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\">items</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\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (items.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Expected at least one item'</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\"> items[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</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><code dir=\"ltr\">toCents</code> returns <code dir=\"ltr\">number</code> no matter what, so making it <code dir=\"ltr\">toCents&lt;T extends number | string&gt;(amount: T): number</code> would add a parameter that appears once and links nothing. <code dir=\"ltr\">firstOrThrow</code> is the opposite: a list of orders must give back an order, and a list of products a product. A union signature (<code dir=\"ltr\">items: unknown[]): unknown</code>) would force every caller to cast.</p>\n<p>When you are unsure, start without the generic. If callers start writing <code dir=\"ltr\">as</code> on the result, that is the signal the output depends on the input, and a type parameter will remove those casts.</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>Generic types follow the same rule</span></p>\n<p>Types take parameters too: <code dir=\"ltr\">type Page&lt;T&gt; = { items: T[]; nextCursor: string | null }</code>. Cartwheel’s client returns <code dir=\"ltr\">Page&lt;DetailedOrder&gt;</code> and <code dir=\"ltr\">Page&lt;Product&gt;</code> from the same pagination code. The rule still holds: a type parameter that is not used in the body is a sign the type is doing less than it claims.</p>\n</aside>\n<p>In the exercise you will turn four lossy helpers into generic ones that keep the caller’s types. Next lesson looks at how TypeScript decides what <code dir=\"ltr\">T</code> is when the caller does not say.</p>\n","toc":[{"id":"sec-1","text":"A type parameter is a relationship","level":2},{"id":"sec-2","text":"The appears-twice rule","level":2},{"id":"sec-3","text":"The caller chooses T","level":2},{"id":"sec-4","text":"Constraints that use other parameters","level":2},{"id":"sec-5","text":"Generic or plain union?","level":2}],"takeaways":["A type parameter earns its place when it appears at least twice, linking an input to an output or one input to another.","<code dir=\"ltr\">T extends { totalCents: number }</code> lets the function use <code dir=\"ltr\">totalCents</code> while still returning the caller’s full, more specific type.","A generic that appears only in the return type, like <code dir=\"ltr\">parse&lt;T&gt;(text): T</code>, is an unchecked cast with nicer syntax.","The caller chooses <code dir=\"ltr\">T</code>, not the function, which is why you cannot return a literal object where <code dir=\"ltr\">T</code> is expected."],"further":[{"title":"Generics","url":"https://www.typescriptlang.org/docs/handbook/2/generics.html"},{"title":"More on Functions (generic functions and constraints)","url":"https://www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions"}],"quiz":[{"q":"<p>What is the type of <code dir=\"ltr\">sorted</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\"> DetailedOrder</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\">totalCents</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\">customer</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\">declare</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> orders</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> DetailedOrder</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\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">xs</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">totalCents</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\">return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">xs].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.totalCents </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.totalCents); }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sorted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> sortByTotal</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orders);</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">DetailedOrder[]</code>, because the array holds <code dir=\"ltr\">DetailedOrder</code> values","why":"The values are still full orders at runtime, but the return type is computed from the parameter type, which only knows <code dir=\"ltr\">totalCents</code>."},{"text":"<code dir=\"ltr\">{ totalCents: number }[]</code>, so <code dir=\"ltr\">sorted[0].customer</code> is an error","why":"Correct. Without a type parameter, the function has no way to say “I return the same kind of thing you gave me”."},{"text":"<code dir=\"ltr\">any[]</code>, because <code dir=\"ltr\">sort</code> returns <code dir=\"ltr\">any</code>","why":"<code dir=\"ltr\">sort</code> returns the array’s own element type. The loss of information comes from the parameter annotation."}],"answer":1},{"q":"<p>Which of these signatures uses its type parameter in a way that adds real type safety?</p>","options":[{"text":"<code dir=\"ltr\">function log&lt;T&gt;(value: T): void</code>","why":"<code dir=\"ltr\">T</code> appears once, so it links nothing. <code dir=\"ltr\">value: unknown</code> says the same thing more honestly."},{"text":"<code dir=\"ltr\">function parse&lt;T&gt;(json: string): T</code>","why":"<code dir=\"ltr\">T</code> only appears in the return type, so the caller picks any type and nothing checks it. It is a cast in disguise."},{"text":"<code dir=\"ltr\">function lastItem&lt;T&gt;(items: T[]): T | undefined</code>","why":"Correct. <code dir=\"ltr\">T</code> appears in the input and the output, linking them, so the caller gets back exactly the element type they passed in."},{"text":"<code dir=\"ltr\">function count&lt;T&gt;(items: T[]): number</code>","why":"<code dir=\"ltr\">T</code> appears only once in a position that matters; <code dir=\"ltr\">items: unknown[]</code> works identically."}],"answer":2},{"q":"<p>Why is this an error?</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\"> emptyCart</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\">items</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:#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\"> { items: [] };</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\">[]</code> is inferred as <code dir=\"ltr\">never[]</code>.","why":"<code dir=\"ltr\">never[]</code> is assignable to <code dir=\"ltr\">string[]</code>. The problem is the relationship between the literal and <code dir=\"ltr\">T</code>."},{"text":"Because generic functions cannot return object literals.","why":"They can, when the literal’s type is what the return type says. Here the return type is the caller’s <code dir=\"ltr\">T</code>, which you do not know."},{"text":"Because the caller chooses <code dir=\"ltr\">T</code>, which might be <code dir=\"ltr\">{ items: string[]; owner: string }</code>, and your literal has no <code dir=\"ltr\">owner</code>.","why":"Correct. The error says <code dir=\"ltr\">T</code> could be instantiated with a different subtype of the constraint. Return the constraint type instead."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"function sortByTotal(orders: { totalCents: number }[]): { totalCents: number }[] {\n  return [...orders].sort((a, b) => a.totalCents - b.totalCents);\n}\n\nfunction indexById(items: { id: number }[]): Map<number, { id: number }> {\n  return new Map(items.map((item) => [item.id, item]));\n}\n\nfunction pluck(items: any[], key: string): any[] {\n  return items.map((item) => item[key]);\n}\n\nfunction lastItem(items: unknown[]): unknown {\n  return items[items.length - 1];\n}\n\n// ---- Type tests (do not edit) ----\ntype DetailedOrder = { id: number; totalCents: number; customer: string };\nconst orders: DetailedOrder[] = [\n  { id: 1003, totalCents: 4500, customer: 'Mona' },\n  { id: 1001, totalCents: 18900, customer: 'Karim' },\n  { id: 1002, totalCents: 1200, customer: 'Lea' },\n];\n\nconst sorted = sortByTotal(orders);\nconst byId = indexById(orders);\nconst customers = pluck(orders, 'customer');\nconst last = lastItem(orders);\n\ntype T1 = Expect<Equal<typeof sorted, DetailedOrder[]>>;\ntype T2 = Expect<Equal<typeof byId, Map<number, DetailedOrder>>>;\ntype T3 = Expect<Equal<typeof customers, string[]>>;\ntype T4 = Expect<Equal<typeof last, DetailedOrder | undefined>>;\n\n// @ts-expect-error: products have no totalCents\nsortByTotal([{ sku: 'TENT-2P' }]);\n\n// @ts-expect-error: 'email' is not a property of DetailedOrder\npluck(orders, 'email');\n\nconsole.log(sorted.map((o) => o.customer), byId.get(1001)?.customer, customers, last?.id);\n"},"solution":{"ts":"function sortByTotal<T extends { totalCents: number }>(orders: T[]): T[] {\n  return [...orders].sort((a, b) => a.totalCents - b.totalCents);\n}\n\nfunction indexById<T extends { id: number }>(items: T[]): Map<number, T> {\n  return new Map(items.map((item) => [item.id, item]));\n}\n\nfunction pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {\n  return items.map((item) => item[key]);\n}\n\nfunction lastItem<T>(items: T[]): T | undefined {\n  return items[items.length - 1];\n}\n\n// ---- Type tests (do not edit) ----\ntype DetailedOrder = { id: number; totalCents: number; customer: string };\nconst orders: DetailedOrder[] = [\n  { id: 1003, totalCents: 4500, customer: 'Mona' },\n  { id: 1001, totalCents: 18900, customer: 'Karim' },\n  { id: 1002, totalCents: 1200, customer: 'Lea' },\n];\n\nconst sorted = sortByTotal(orders);\nconst byId = indexById(orders);\nconst customers = pluck(orders, 'customer');\nconst last = lastItem(orders);\n\ntype T1 = Expect<Equal<typeof sorted, DetailedOrder[]>>;\ntype T2 = Expect<Equal<typeof byId, Map<number, DetailedOrder>>>;\ntype T3 = Expect<Equal<typeof customers, string[]>>;\ntype T4 = Expect<Equal<typeof last, DetailedOrder | undefined>>;\n\n// @ts-expect-error: products have no totalCents\nsortByTotal([{ sku: 'TENT-2P' }]);\n\n// @ts-expect-error: 'email' is not a property of DetailedOrder\npluck(orders, 'email');\n\nconsole.log(sorted.map((o) => o.customer), byId.get(1001)?.customer, customers, last?.id);\n"},"checks":[{"id":"types","typecheck":true},{"id":"sorts-copy","test":"const xs = [{ totalCents: 3 }, { totalCents: 1 }, { totalCents: 2 }]; const out = sortByTotal(xs); return out.map((o) => o.totalCents).join() === '1,2,3' && xs[0].totalCents === 3;"},{"id":"indexes","test":"const m = indexById([{ id: 7, n: 'a' }, { id: 9, n: 'b' }]); return m.get(9).n === 'b' && m.size === 2;"},{"id":"plucks","test":"return pluck([{ a: 1 }, { a: 2 }], 'a').join() === '1,2' && lastItem([1, 2, 3]) === 3 && lastItem([]) === undefined;"}],"title":"Make the helpers keep their types","prompt":"<p>These four helpers from Cartwheel’s order history page work at runtime but lose type information. Make each one generic so the type tests pass:</p>\n<ul>\n<li><code dir=\"ltr\">sortByTotal</code> returns a sorted <strong>copy</strong> typed as the caller’s element type, and still rejects objects without <code dir=\"ltr\">totalCents</code>.</li>\n<li><code dir=\"ltr\">indexById</code> returns a <code dir=\"ltr\">Map</code> from id to the caller’s element type.</li>\n<li><code dir=\"ltr\">pluck(items, key)</code> only accepts real property names and returns an array of that property’s type.</li>\n<li><code dir=\"ltr\">lastItem</code> returns the last element, typed as the element type or <code dir=\"ltr\">undefined</code>.</li>\n</ul>\n<p>Keep the runtime behaviour the same, and do not use <code dir=\"ltr\">any</code> or <code dir=\"ltr\">as</code>.</p>\n","hints":["Each helper needs a type parameter that appears in both the parameter and the return type.","Constrain with <code dir=\"ltr\">extends</code>: <code dir=\"ltr\">&lt;T extends { totalCents: number }&gt;</code> lets you read <code dir=\"ltr\">totalCents</code> while returning <code dir=\"ltr\">T[]</code>.","For <code dir=\"ltr\">pluck</code>, use two type parameters: <code dir=\"ltr\">&lt;T, K extends keyof T&gt;(items: T[], key: K): T[K][]</code>."],"explanation":"<p>Each type parameter now appears at least twice, linking the input to the output, so the caller gets back exactly the element type they passed in. The constraints (<code dir=\"ltr\">extends { totalCents: number }</code>, <code dir=\"ltr\">extends { id: number }</code>) are what make the property reads legal inside the functions, and they are also what rejects <code dir=\"ltr\">[{ sku: 'TENT-2P' }]</code>. In <code dir=\"ltr\">pluck</code>, <code dir=\"ltr\">K extends keyof T</code> ties the key to the items, and <code dir=\"ltr\">T[K]</code> looks up that property’s type, so <code dir=\"ltr\">'customer'</code> gives <code dir=\"ltr\">string[]</code> and <code dir=\"ltr\">'email'</code> is refused.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including all type tests","sorts-copy":"<code dir=\"ltr\">sortByTotal</code> sorts ascending without mutating its input","indexes":"<code dir=\"ltr\">indexById</code> maps each id to its item","plucks":"<code dir=\"ltr\">pluck</code> and <code dir=\"ltr\">lastItem</code> still work at runtime"}},"words":749,"readMinutes":4}