{"id":"l-at-3-2","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Transform every property of a type at once with mapped types, add or remove readonly and optional modifiers, and rename or drop keys with as clauses, re-implementing Partial, Pick and Omit along the way.","html":"<p>Cartwheel’s checkout has an address form. The address type is simple:</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\"> Address</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#953800;--shiki-dark:#FFA657\">line1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">city</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">country</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">postcode</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></code></pre></div>\n<p>The form needs more than the address, though. It needs, for each field, its current value, a validation error and whether the user has touched it. The PATCH endpoint needs a version where every field is optional. The review screen needs a frozen copy. You could write three more types by hand, each repeating the four field names, and update all of them whenever <code dir=\"ltr\">Address</code> changes. Or you could write each one once, as a transformation. That is what a <strong>mapped type</strong> is: a loop over keys, at the type level.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The loop</h2>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FieldState</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  [</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</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:#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:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">error</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> null</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">touched</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> boolean</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">};</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> AddressForm</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FieldState</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// {</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   line1: { value: string; error: string | null; touched: boolean };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   city: { value: string; … };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   country: { value: string; … };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">//   postcode?: { value: string | undefined; … };</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// }</span></span></code></pre></div>\n<p>Read <code dir=\"ltr\">[K in keyof T]</code> as “for each key <code dir=\"ltr\">K</code> of <code dir=\"ltr\">T</code>”. The right-hand side is evaluated once per key, with <code dir=\"ltr\">K</code> bound to that key, so <code dir=\"ltr\">T[K]</code> is that property’s type. Add <code dir=\"ltr\">phone</code> to <code dir=\"ltr\">Address</code> and <code dir=\"ltr\">AddressForm</code> grows a <code dir=\"ltr\">phone</code> entry, with no edit.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-3-2-t1\"><defs><marker id=\"arrow-l-at-3-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-3-2-t1\">The keys of Address, line1, city, country and postcode, each pass through the mapped type body, which wraps the property type in an object with value, error and touched, producing one property per key in FieldState of Address.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"20\" width=\"170\" height=\"190\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"95\" y=\"46\" text-anchor=\"middle\">keyof Address</text>\n  <text class=\"d-code\" x=\"95\" y=\"84\" text-anchor=\"middle\">'line1'</text>\n  <text class=\"d-code\" x=\"95\" y=\"114\" text-anchor=\"middle\">'city'</text>\n  <text class=\"d-code\" x=\"95\" y=\"144\" text-anchor=\"middle\">'country'</text>\n  <text class=\"d-code\" x=\"95\" y=\"174\" text-anchor=\"middle\">'postcode'</text>\n  <path class=\"d-arrow\" d=\"M180 115 L238 115\" marker-end=\"url(#arrow-l-at-3-2-1)\"/>\n  <rect class=\"d-box-primary\" x=\"242\" y=\"70\" width=\"210\" height=\"90\" rx=\"12\"/>\n  <text class=\"d-code\" x=\"347\" y=\"100\" text-anchor=\"middle\">[K in keyof T]:</text>\n  <text class=\"d-code\" x=\"347\" y=\"126\" text-anchor=\"middle\">{ value: T[K];</text>\n  <text class=\"d-code\" x=\"347\" y=\"148\" text-anchor=\"middle\">error; touched }</text>\n  <path class=\"d-arrow\" d=\"M452 115 L506 115\" marker-end=\"url(#arrow-l-at-3-2-1)\"/>\n  <rect class=\"d-box-success\" x=\"510\" y=\"20\" width=\"180\" height=\"190\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"600\" y=\"46\" text-anchor=\"middle\">FieldState</text>\n  <text class=\"d-code\" x=\"600\" y=\"84\" text-anchor=\"middle\">line1: {…}</text>\n  <text class=\"d-code\" x=\"600\" y=\"114\" text-anchor=\"middle\">city: {…}</text>\n  <text class=\"d-code\" x=\"600\" y=\"144\" text-anchor=\"middle\">country: {…}</text>\n  <text class=\"d-code\" x=\"600\" y=\"174\" text-anchor=\"middle\">postcode?: {…}</text>\n</svg><figcaption>A mapped type runs its body once per key</figcaption></figure>\n<p>Notice that <code dir=\"ltr\">postcode</code> stayed optional. When a mapped type loops over <code dir=\"ltr\">keyof T</code> directly, TypeScript treats it as <strong>homomorphic</strong>: it copies each property’s <code dir=\"ltr\">?</code> and <code dir=\"ltr\">readonly</code> modifiers from <code dir=\"ltr\">T</code>. That is usually what you want, and it is why the built-in utility types preserve optionality.</p>\n<p>You do not have to loop over <code dir=\"ltr\">keyof</code>. Any union of keys works, which is all <code dir=\"ltr\">Record</code> is:</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\"> MyRecord</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PropertyKey</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">V</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\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</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\"> V</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CountsByStatus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyRecord</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</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:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span></code></pre></div>\n<h2 id=\"sec-2\" tabindex=\"-1\">Adding and removing modifiers</h2>\n<p>Put <code dir=\"ltr\">?</code> or <code dir=\"ltr\">readonly</code> in front of the property to add it, and prefix with <code dir=\"ltr\">-</code> to remove it. With those, most of the lib’s utility types are one line each:</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\"> MyPartial</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:#CF222E;--shiki-dark:#FF7B72\">=</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\"> in</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:#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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyReadonly</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">readonly</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\"> in</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:#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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyRequired</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:#CF222E;--shiki-dark:#FF7B72\">=</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\"> in</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:#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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Mutable</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-readonly</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\"> in</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:#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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyPick</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { [</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</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\">P</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] };</span></span></code></pre></div>\n<p><code dir=\"ltr\">MyPartial&lt;Address&gt;</code> is the PATCH body type. <code dir=\"ltr\">MyReadonly&lt;Address&gt;</code> is the frozen copy for the review screen. <code dir=\"ltr\">-?</code> does one more thing than you might expect: it also removes the <code dir=\"ltr\">undefined</code> that an optional property implicitly carries, so <code dir=\"ltr\">MyRequired&lt;Address&gt;['postcode']</code> is <code dir=\"ltr\">string</code>, not <code dir=\"ltr\">string | undefined</code>.</p>\n<p><code dir=\"ltr\">MyPick</code> is worth a second look. It maps over <code dir=\"ltr\">K</code>, not <code dir=\"ltr\">keyof T</code>, but because <code dir=\"ltr\">K</code> is constrained to <code dir=\"ltr\">keyof T</code>, TypeScript still treats it as homomorphic and keeps the modifiers of the picked properties.</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 deep transformations</span></p>\n<p><code dir=\"ltr\">Readonly&lt;Order&gt;</code> makes <code dir=\"ltr\">order.lines = []</code> an error but happily allows <code dir=\"ltr\">order.lines[0].sku = 'x'</code>. Every built-in mapped type is <strong>shallow</strong>: it transforms the top-level properties and leaves nested objects exactly as they were. If you need a deep version, it takes recursion, which is the subject of <a href=\"/lesson/advanced-typescript/l-at-3-5\">Recursive Types and Knowing When to Stop</a>, along with the reasons you might not want one.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Renaming keys with as</h2>\n<p>Since TypeScript 4.1, a mapped type can rename each key with an <code dir=\"ltr\">as</code> clause. The new name can be any type expression involving <code dir=\"ltr\">K</code>, typically a template literal:</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Setters</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:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  [</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `set${</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Capitalize</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> &#x26;</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">></span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span><span 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:#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:#0550AE;--shiki-dark:#79C0FF\"> void</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> AddressSetters</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Setters</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { setLine1: (value: string) => void; setCity: …; setCountry: …; setPostcode?: … }</span></span></code></pre></div>\n<p><code dir=\"ltr\">K &amp; string</code> is needed because <code dir=\"ltr\">keyof T</code> may include <code dir=\"ltr\">number</code> and <code dir=\"ltr\">symbol</code> keys, and <code dir=\"ltr\">Capitalize</code> only works on strings. You will see template literal types properly in <a href=\"/lesson/advanced-typescript/l-at-3-4\">Template Literal Types</a>.</p>\n<p>The <code dir=\"ltr\">as</code> clause has a second power: <strong>a key mapped to <code dir=\"ltr\">never</code> is dropped</strong>. That is exactly how <code dir=\"ltr\">Omit</code> is defined:</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:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyOmit</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:#953800;--shiki-dark:#FFA657\"> PropertyKey</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\">P</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> in</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> T</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> as</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Exclude</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">P</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 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\">P</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> NewAddress</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> MyOmit</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'postcode'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { line1; city; country }</span></span></code></pre></div>\n<p><code dir=\"ltr\">Exclude&lt;P, K&gt;</code> gives <code dir=\"ltr\">never</code> when <code dir=\"ltr\">P</code> is one of the omitted keys and <code dir=\"ltr\">P</code> otherwise. In the next lesson you will see how <code dir=\"ltr\">Exclude</code> works, and how to filter keys by the <em>type</em> of their value rather than by name.</p>\n<h2 id=\"sec-4\" tabindex=\"-1\">Putting it to work</h2>\n<p>Types like <code dir=\"ltr\">FieldState</code> pay off in the functions that use them. Here is the form’s update function, written once for any form, not just addresses:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"6\"><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\"> setField</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\">form</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FieldState</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\">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:#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:#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\"> FieldState</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>\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\">form, [key]: { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">form[key], value, touched: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</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:#8250DF;--shiki-dark:#D2A8FF\">setField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(addressForm, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'city'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dubai'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ok</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(addressForm, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'city'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">42</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);      </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type 'number' is not assignable to parameter of type 'string'.</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setField</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(addressForm, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'cty'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dubai'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: Argument of type '\"cty\"' is not assignable to parameter of type 'keyof Address'.</span></span></code></pre></div>\n<p>This is the <code dir=\"ltr\">pluck</code> pattern from section 2 combined with a mapped type. <code dir=\"ltr\">K</code> is inferred from the key you pass, <code dir=\"ltr\">T[K]</code> checks the value against that field’s type, and the mapped type guarantees the form has an entry for every field. When a <code dir=\"ltr\">deliveryNotes</code> field is added to <code dir=\"ltr\">Address</code>, the form type, the setters and this function all accept it immediately, and every place that builds a complete form is flagged until it handles the new field.</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>Name the transformations you reuse</span></p>\n<p>Inline mapped types in function signatures are hard to read. Give each transformation a name that says what it is for (<code dir=\"ltr\">FieldState</code>, <code dir=\"ltr\">Patch</code>, <code dir=\"ltr\">Setters</code>), keep it next to the types it serves, and let hover tooltips show the expansion. A reader should understand the signature from the name alone.</p>\n</aside>\n<p>In the exercise you will re-implement four utility types and build the address form’s types from them. Next lesson adds the missing piece, conditionals, so your transformations can make decisions.</p>\n","toc":[{"id":"sec-1","text":"The loop","level":2},{"id":"sec-2","text":"Adding and removing modifiers","level":2},{"id":"sec-3","text":"Renaming keys with as","level":2},{"id":"sec-4","text":"Putting it to work","level":2}],"takeaways":["A mapped type <code dir=\"ltr\">{ [K in Keys]: … }</code> loops over a union of keys and builds one property per key.","Mapping over <code dir=\"ltr\">keyof T</code> is homomorphic, so optional and readonly modifiers are copied from <code dir=\"ltr\">T</code> unless you add or remove them.","<code dir=\"ltr\">+?</code>, <code dir=\"ltr\">-?</code>, <code dir=\"ltr\">+readonly</code> and <code dir=\"ltr\">-readonly</code> add or strip modifiers; <code dir=\"ltr\">Required&lt;T&gt;</code> is just <code dir=\"ltr\">{ [K in keyof T]-?: T[K] }</code>.","An <code dir=\"ltr\">as</code> clause renames each key, and mapping a key to <code dir=\"ltr\">never</code> removes it, which is how <code dir=\"ltr\">Omit</code> works.","Built-in mapped types are shallow; nested objects keep their original modifiers."],"further":[{"title":"Mapped Types","url":"https://www.typescriptlang.org/docs/handbook/2/mapped-types.html"},{"title":"Utility Types","url":"https://www.typescriptlang.org/docs/handbook/utility-types.html"}],"quiz":[{"q":"<p>What is <code dir=\"ltr\">Draft</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\"> Address</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">readonly</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> line1</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">city</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#953800;--shiki-dark:#FFA657\">postcode</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\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Draft</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-readonly</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\"> in</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Address</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-?:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Address</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></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">{ line1: string; city: string; postcode: string }</code>","why":"Correct. <code dir=\"ltr\">-readonly</code> strips readonly from <code dir=\"ltr\">line1</code>, <code dir=\"ltr\">-?</code> makes <code dir=\"ltr\">postcode</code> required, and the <code dir=\"ltr\">undefined</code> that came with <code dir=\"ltr\">?</code> is removed too."},{"text":"<code dir=\"ltr\">{ readonly line1: string; city: string; postcode?: string }</code>","why":"That is what you would get without the <code dir=\"ltr\">-</code> modifiers; a homomorphic mapping copies them by default."},{"text":"<code dir=\"ltr\">{ line1: string; city: string; postcode: string | undefined }</code>","why":"<code dir=\"ltr\">-?</code> also removes the <code dir=\"ltr\">undefined</code> that the optional modifier added, so <code dir=\"ltr\">postcode</code> is plain <code dir=\"ltr\">string</code>."},{"text":"It is an error, because <code dir=\"ltr\">-?</code> is only allowed in <code dir=\"ltr\">Required</code>.","why":"<code dir=\"ltr\">Required</code> is an ordinary mapped type in the lib; the <code dir=\"ltr\">-?</code> modifier works in any mapped type."}],"answer":0},{"q":"<p>Which definition re-implements <code dir=\"ltr\">Omit&lt;T, K&gt;</code>?</p>","options":[{"text":"<code dir=\"ltr\">{ [P in keyof T]: P extends K ? never : T[P] }</code>","why":"This keeps every key and sets the omitted ones to type <code dir=\"ltr\">never</code>, so they are still required properties that nothing can satisfy."},{"text":"<code dir=\"ltr\">{ [P in K]: T[P] }</code>","why":"That keeps only the listed keys, which is <code dir=\"ltr\">Pick</code>, and it does not even compile without <code dir=\"ltr\">K extends keyof T</code>."},{"text":"<code dir=\"ltr\">{ [P in keyof T as Exclude&lt;P, K&gt;]: T[P] }</code>","why":"Correct. The <code dir=\"ltr\">as</code> clause maps omitted keys to <code dir=\"ltr\">never</code>, and a key remapped to <code dir=\"ltr\">never</code> is dropped from the result."}],"answer":2},{"q":"<p><code dir=\"ltr\">type Setters&lt;T&gt; = { [K in keyof T as </code>set${Capitalize<K>}<code dir=\"ltr\">]: (v: T[K]) =&gt; void }</code> fails to compile. Why?</p>","options":[{"text":"Template literal types are not allowed in <code dir=\"ltr\">as</code> clauses.","why":"They are allowed; renaming keys with template literals is the main use of <code dir=\"ltr\">as</code> clauses."},{"text":"<code dir=\"ltr\">keyof T</code> can include <code dir=\"ltr\">number</code> and <code dir=\"ltr\">symbol</code> keys, and <code dir=\"ltr\">Capitalize</code> only accepts strings, so write <code dir=\"ltr\">Capitalize&lt;K &amp; string&gt;</code>.","why":"Correct. Intersecting with <code dir=\"ltr\">string</code> keeps only string keys, which <code dir=\"ltr\">Capitalize</code> can work with."},{"text":"<code dir=\"ltr\">T[K]</code> is not allowed once the key has been renamed.","why":"Inside the mapped type, <code dir=\"ltr\">K</code> is still the original key, so <code dir=\"ltr\">T[K]</code> works as usual."}],"answer":1},{"q":"<p><code dir=\"ltr\">Readonly&lt;Order&gt;</code> is applied to <code dir=\"ltr\">type Order = { id: number; lines: { sku: string }[] }</code>. Which assignment is still allowed?</p>","options":[{"text":"<code dir=\"ltr\">order.id = 1043</code>","why":"<code dir=\"ltr\">id</code> is a top-level property, and <code dir=\"ltr\">Readonly</code> marks every top-level property as readonly."},{"text":"<code dir=\"ltr\">order.lines = []</code>","why":"<code dir=\"ltr\">lines</code> itself is a top-level property, so reassigning it is rejected."},{"text":"<code dir=\"ltr\">order.lines[0].sku = 'TENT-3P'</code>","why":"Correct. <code dir=\"ltr\">Readonly</code> is shallow; the array and its objects keep their original, mutable types."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"type Address = { line1: string; city: string; country: string; postcode?: string };\n\n// Re-implement these with mapped types (do not use the built-in utility types).\ntype MyPartial<T> = T;\ntype MyRequired<T> = T;\ntype MyPick<T, K extends keyof T> = T;\ntype MyOmit<T, K extends PropertyKey> = T;\n\n// For each property: its value, a validation error and whether it was touched.\ntype FieldState<T> = any;\n\n// For each property `name`, a `setName(value)` function.\ntype Setters<T> = any;\n\nfunction initialState<T extends object>(values: T): FieldState<T> {\n  return Object.fromEntries(\n    Object.entries(values).map(([key, value]) => [key, { value, error: null, touched: false }]),\n  );\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyPartial<Address>, { line1?: string; city?: string; country?: string; postcode?: string }>>;\ntype T2 = Expect<Equal<MyRequired<Address>, { line1: string; city: string; country: string; postcode: string }>>;\ntype T3 = Expect<Equal<MyPick<Address, 'city' | 'postcode'>, { city: string; postcode?: string }>>;\ntype T4 = Expect<Equal<MyOmit<Address, 'postcode' | 'country'>, { line1: string; city: string }>>;\ntype T5 = Expect<Equal<FieldState<{ city: string; floor: number }>, {\n  city: { value: string; error: string | null; touched: boolean };\n  floor: { value: number; error: string | null; touched: boolean };\n}>>;\ntype T6 = Expect<Equal<Setters<{ city: string; floor: number }>, {\n  setCity: (value: string) => void;\n  setFloor: (value: number) => void;\n}>>;\n\nconst form = initialState({ line1: '12 Nile St', city: 'Cairo', country: 'EG' });\nconsole.log(form.city);\n"},"solution":{"ts":"type Address = { line1: string; city: string; country: string; postcode?: string };\n\ntype MyPartial<T> = { [K in keyof T]?: T[K] };\ntype MyRequired<T> = { [K in keyof T]-?: T[K] };\ntype MyPick<T, K extends keyof T> = { [P in K]: T[P] };\ntype MyOmit<T, K extends PropertyKey> = { [P in keyof T as Exclude<P, K>]: T[P] };\n\ntype FieldState<T> = {\n  [K in keyof T]: { value: T[K]; error: string | null; touched: boolean };\n};\n\ntype Setters<T> = {\n  [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;\n};\n\nfunction initialState<T extends object>(values: T): FieldState<T> {\n  // Object.fromEntries cannot know the keys, so we cast once here.\n  return Object.fromEntries(\n    Object.entries(values).map(([key, value]) => [key, { value, error: null, touched: false }]),\n  ) as FieldState<T>;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<MyPartial<Address>, { line1?: string; city?: string; country?: string; postcode?: string }>>;\ntype T2 = Expect<Equal<MyRequired<Address>, { line1: string; city: string; country: string; postcode: string }>>;\ntype T3 = Expect<Equal<MyPick<Address, 'city' | 'postcode'>, { city: string; postcode?: string }>>;\ntype T4 = Expect<Equal<MyOmit<Address, 'postcode' | 'country'>, { line1: string; city: string }>>;\ntype T5 = Expect<Equal<FieldState<{ city: string; floor: number }>, {\n  city: { value: string; error: string | null; touched: boolean };\n  floor: { value: number; error: string | null; touched: boolean };\n}>>;\ntype T6 = Expect<Equal<Setters<{ city: string; floor: number }>, {\n  setCity: (value: string) => void;\n  setFloor: (value: number) => void;\n}>>;\n\nconst form = initialState({ line1: '12 Nile St', city: 'Cairo', country: 'EG' });\nconsole.log(form.city);\n"},"checks":[{"id":"types","typecheck":true},{"id":"runtime","test":"const f = initialState({ city: 'Amman', floor: 3 }); return f.city.value === 'Amman' && f.floor.touched === false && f.floor.error === null;"}],"title":"Re-implement the utility types","prompt":"<p>Using mapped types only (no built-in <code dir=\"ltr\">Partial</code>, <code dir=\"ltr\">Required</code>, <code dir=\"ltr\">Pick</code>, <code dir=\"ltr\">Omit</code> or <code dir=\"ltr\">Record</code>), implement:</p>\n<ul>\n<li><code dir=\"ltr\">MyPartial&lt;T&gt;</code>, <code dir=\"ltr\">MyRequired&lt;T&gt;</code>, <code dir=\"ltr\">MyPick&lt;T, K&gt;</code> and <code dir=\"ltr\">MyOmit&lt;T, K&gt;</code>, behaving like the built-ins.</li>\n<li><code dir=\"ltr\">FieldState&lt;T&gt;</code>: for every property, <code dir=\"ltr\">{ value: T[K]; error: string | null; touched: boolean }</code>.</li>\n<li><code dir=\"ltr\">Setters&lt;T&gt;</code>: for every property <code dir=\"ltr\">name</code>, a property <code dir=\"ltr\">setName</code> that is a function taking that property’s type and returning <code dir=\"ltr\">void</code>.</li>\n</ul>\n<p>Then make <code dir=\"ltr\">initialState</code> compile with its new return type.</p>\n","hints":["The basic loop is <code dir=\"ltr\">{ [K in keyof T]: … }</code>. Add <code dir=\"ltr\">?</code> after the key for optional, <code dir=\"ltr\">-?</code> to remove optionality.","For <code dir=\"ltr\">MyOmit</code>, rename keys with <code dir=\"ltr\">as</code> and send the omitted ones to <code dir=\"ltr\">never</code>: <code dir=\"ltr\">as Exclude&lt;P, K&gt;</code>. Built-in <code dir=\"ltr\">Exclude</code> is allowed.","For <code dir=\"ltr\">Setters</code>, rename with a template literal: <code dir=\"ltr\">as `set${Capitalize&lt;K &amp; string&gt;}` </code>. In <code dir=\"ltr\">initialState</code>, <code dir=\"ltr\">Object.fromEntries</code> returns <code dir=\"ltr\">{ [k: string]: … }</code>, so cast its result once to <code dir=\"ltr\">FieldState&lt;T&gt;</code>."],"explanation":"<p>Each utility type is a loop over keys. Mapping over <code dir=\"ltr\">keyof T</code> copies modifiers, so <code dir=\"ltr\">MyPartial</code> adds <code dir=\"ltr\">?</code>, <code dir=\"ltr\">MyRequired</code> removes it with <code dir=\"ltr\">-?</code> (which also drops the implicit <code dir=\"ltr\">undefined</code>), and <code dir=\"ltr\">MyPick</code> keeps the original modifiers because <code dir=\"ltr\">K</code> is constrained to <code dir=\"ltr\">keyof T</code>. <code dir=\"ltr\">MyOmit</code> works because a key remapped to <code dir=\"ltr\">never</code> disappears. <code dir=\"ltr\">Setters</code> shows the other use of <code dir=\"ltr\">as</code>: renaming each key with a template literal, where <code dir=\"ltr\">K &amp; string</code> filters out number and symbol keys that <code dir=\"ltr\">Capitalize</code> cannot handle. <code dir=\"ltr\">Object.fromEntries</code> cannot know your keys, so one documented cast inside <code dir=\"ltr\">initialState</code> gives callers a precise type.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including all type tests","runtime":"<code dir=\"ltr\">initialState</code> builds an untouched, error-free state for each field"}},"words":728,"readMinutes":3}