{"id":"l-at-3-1","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Derive types from runtime values and from other types with typeof, keyof and indexed access, so a change in one place flows through the codebase instead of drifting.","html":"<p>Cartwheel’s shipping zones live in a config object. The first version of the code described them twice: once as data, once as types.</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SHIPPING_ZONES</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\">  domestic: { countries: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'US'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'CA'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">499</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  gulf: { countries: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'AE'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SA'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1299</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  europe: { countries: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'GB'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'DE'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">999</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\"> Zone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'domestic'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'gulf'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> |</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'europe'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// written by hand</span></span></code></pre></div>\n<p>Six months later someone added a <code dir=\"ltr\">levant</code> zone to the object and forgot the type. Nothing failed to compile; the new zone just could not be selected anywhere. Every hand-written copy of a fact is a copy that can drift. This lesson covers the three operators that let you write the fact once and <strong>derive</strong> the rest.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">typeof: from values to types</h2>\n<p>In a type position, <code dir=\"ltr\">typeof</code> takes a <em>value</em> and gives you the type TypeScript inferred for it. It is not the JavaScript <code dir=\"ltr\">typeof</code> operator (which returns strings like <code dir=\"ltr\">'object'</code> at runtime); it only exists at compile time.</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SHIPPING_ZONES</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\">  domestic: { countries: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'US'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'CA'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">499</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  gulf: { countries: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'AE'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'SA'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1299</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  europe: { countries: [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'GB'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'DE'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">], feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">999</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:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</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\"> ShippingZones</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> typeof</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SHIPPING_ZONES</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { readonly domestic: { readonly countries: readonly ['US', 'CA']; readonly feeCents: 499 }; … }</span></span></code></pre></div>\n<p><code dir=\"ltr\">as const</code> matters here, as it did in <a href=\"/lesson/advanced-typescript/l-at-1-5\">Literal Types, as const and satisfies</a>. Without it, <code dir=\"ltr\">countries</code> would be <code dir=\"ltr\">string[]</code> and <code dir=\"ltr\">feeCents</code> would be <code dir=\"ltr\">number</code>, and you would lose the details worth deriving.</p>\n<p><code dir=\"ltr\">typeof</code> works on functions too: <code dir=\"ltr\">typeof formatPrice</code> is the function’s full signature, which you can feed to utility types such as <code dir=\"ltr\">ReturnType&lt;typeof formatPrice&gt;</code>. You will build <code dir=\"ltr\">ReturnType</code> yourself in two lessons.</p>\n<p>When should the value be the source and the type derived, rather than the other way round? Use <code dir=\"ltr\">typeof</code> when the data <em>is</em> the definition: lookup tables, zone configs, lists of options that also render in the UI. Write the type first when the shape is a contract with someone else, such as an API payload, and check values against it.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 700 230\" role=\"img\" aria-labelledby=\"l-at-3-1-t1\"><defs><marker id=\"arrow-l-at-3-1-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-1-t1\">Two worlds. In the value world sits the SHIPPING_ZONES object. typeof carries it into the type world as a type. From there, keyof produces the union of zone names, and indexed access with those names produces the union of zone configs, and indexing further gives fees.</title>\n  <rect class=\"d-box\" x=\"10\" y=\"20\" width=\"200\" height=\"190\" rx=\"14\"/>\n  <text class=\"d-label-strong\" x=\"110\" y=\"48\" text-anchor=\"middle\">Values (runtime)</text>\n  <rect class=\"d-box-accent\" x=\"30\" y=\"90\" width=\"160\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"110\" y=\"120\" text-anchor=\"middle\">SHIPPING_ZONES</text>\n  <path class=\"d-arrow\" d=\"M190 115 L268 115\" marker-end=\"url(#arrow-l-at-3-1-1)\"/>\n  <text class=\"d-code\" x=\"229\" y=\"105\" text-anchor=\"middle\">typeof</text>\n  <rect class=\"d-box\" x=\"272\" y=\"20\" width=\"418\" height=\"190\" rx=\"14\"/>\n  <text class=\"d-label-strong\" x=\"481\" y=\"48\" text-anchor=\"middle\">Types (compile time)</text>\n  <rect class=\"d-box-primary\" x=\"292\" y=\"90\" width=\"120\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"352\" y=\"120\" text-anchor=\"middle\">Zones</text>\n  <path class=\"d-arrow\" d=\"M412 104 L462 80\" marker-end=\"url(#arrow-l-at-3-1-1)\"/>\n  <rect class=\"d-box-success\" x=\"466\" y=\"58\" width=\"210\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"571\" y=\"83\" text-anchor=\"middle\">keyof → 'domestic' | …</text>\n  <path class=\"d-arrow\" d=\"M412 126 L462 150\" marker-end=\"url(#arrow-l-at-3-1-1)\"/>\n  <rect class=\"d-box-success\" x=\"466\" y=\"132\" width=\"210\" height=\"40\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"571\" y=\"157\" text-anchor=\"middle\">[K]['feeCents'] → 499 | …</text>\n</svg><figcaption>typeof crosses from values to types; keyof and indexing work on types</figcaption></figure>\n<h2 id=\"sec-2\" tabindex=\"-1\">keyof: the names of the properties</h2>\n<p><code dir=\"ltr\">keyof T</code> is the union of <code dir=\"ltr\">T</code>’s property names:</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\"> Zone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> typeof</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SHIPPING_ZONES</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'domestic' | 'gulf' | 'europe'</span></span></code></pre></div>\n<p>Add <code dir=\"ltr\">levant</code> to the object and <code dir=\"ltr\">Zone</code> updates itself. That is the whole point.</p>\n<p>Two details are worth knowing. First, for a type with an index signature such as <code dir=\"ltr\">Record&lt;string, number&gt;</code>, <code dir=\"ltr\">keyof</code> gives <code dir=\"ltr\">string</code> (and <code dir=\"ltr\">string | number</code> for <code dir=\"ltr\">{ [k: string]: … }</code>, because JavaScript converts numeric keys to strings). Second, <code dir=\"ltr\">keyof</code> on a union gives only the keys <strong>every</strong> member has. <code dir=\"ltr\">keyof (ShippedOrder | CancelledOrder)</code> is <code dir=\"ltr\">'id' | 'status'</code>, because reading <code dir=\"ltr\">trackingNumber</code> from something that might be a cancelled order is not safe.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">Indexed access: the type of a property</h2>\n<p><code dir=\"ltr\">T[K]</code> looks up the type of property <code dir=\"ltr\">K</code> in <code dir=\"ltr\">T</code>, using the same bracket syntax as runtime property access:</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\"> GulfZone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippingZones</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'gulf'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];             </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// { readonly countries: …; readonly feeCents: 1299 }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> GulfFee</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippingZones</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'gulf'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'feeCents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 1299</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ZoneConfig</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippingZones</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Zone</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];             </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// union of all three configs</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> FeeCents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippingZones</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Zone</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'feeCents'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// 499 | 1299 | 999</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">type</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippingCountry</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ShippingZones</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">Zone</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">][</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'countries'</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 style=\"color:#6E7781;--shiki-dark:#8B949E\">// 'US' | 'CA' | 'AE' | …</span></span></code></pre></div>\n<p>Indexing with a union gives a union of the results, so <code dir=\"ltr\">ShippingZones[Zone]</code> is “the value type of any zone”. The idiom <code dir=\"ltr\">(typeof X)[keyof typeof X]</code> is how you get the union of an object’s values, and it comes up often enough that many codebases name it <code dir=\"ltr\">ValueOf&lt;T&gt;</code>.</p>\n<p>Arrays and tuples are indexed by <code dir=\"ltr\">number</code>: <code dir=\"ltr\">T[number]</code> is the element type. That is the <code dir=\"ltr\">(typeof COUNTRIES)[number]</code> you used in section 1, and the last line above, which digs through two levels of objects and one array to collect every country in every zone.</p>\n<p>The same trick works on API types you did not write. If a generated client gives you a big <code dir=\"ltr\">CheckoutResponse</code>, you can name the line-item type as <code dir=\"ltr\">CheckoutResponse['lines'][number]</code> instead of copying its fields.</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>Using dot access in types</span></p>\n<p><code dir=\"ltr\">CheckoutResponse.lines</code> in a type position does not mean “the type of the <code dir=\"ltr\">lines</code> property”. TypeScript reads the dot as a namespace lookup and reports <em>Cannot access ‘CheckoutResponse.lines’ because ‘CheckoutResponse’ is a type, but not a namespace</em>, helpfully suggesting the fix. Types always use brackets: <code dir=\"ltr\">CheckoutResponse['lines']</code>.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">Why Object.keys returns string[]</h2>\n<p>Sooner or later you will write this and be annoyed:</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\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> zone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Object.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">keys</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">SHIPPING_ZONES</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  SHIPPING_ZONES</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[zone]; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// Error: 'string' can't be used to index type …</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p><code dir=\"ltr\">Object.keys</code> returns <code dir=\"ltr\">string[]</code>, not <code dir=\"ltr\">Zone[]</code>, and this is deliberate. Remember structural typing: a value whose type is <code dir=\"ltr\">{ a: number }</code> can be an object with ten more properties at runtime. If <code dir=\"ltr\">Object.keys</code> promised <code dir=\"ltr\">(keyof T)[]</code>, it would be lying about those ten.</p>\n<p>For a <code dir=\"ltr\">const</code> object you defined yourself, with no way for extra keys to sneak in, a local cast is reasonable and honest: <code dir=\"ltr\">Object.keys(SHIPPING_ZONES) as Zone[]</code>. For objects that came from elsewhere, keep <code dir=\"ltr\">string</code> and check each key.</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>Derive in one direction</span></p>\n<p>Pick a single source of truth and derive from it in one direction. Either the runtime object is the source and types come from <code dir=\"ltr\">typeof</code>, or a type is the source and runtime values are checked with <code dir=\"ltr\">satisfies</code>. Mixing both (some fields hand-typed, some derived) is how drift comes back.</p>\n</aside>\n<p>In the exercise you will derive five types from Cartwheel’s shipping config and API response. Next lesson goes one step further: instead of <em>reading</em> properties from a type, you will <em>transform</em> every property of a type at once.</p>\n","toc":[{"id":"sec-1","text":"typeof: from values to types","level":2},{"id":"sec-2","text":"keyof: the names of the properties","level":2},{"id":"sec-3","text":"Indexed access: the type of a property","level":2},{"id":"sec-4","text":"Why Object.keys returns string[]","level":2}],"takeaways":["In a type position, <code dir=\"ltr\">typeof value</code> gives the type TypeScript inferred for that value, bridging runtime code into the type system.","<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 (or properties) named by <code dir=\"ltr\">K</code>.","<code dir=\"ltr\">(typeof CONFIG)[keyof typeof CONFIG]</code> gives the union of an object’s value types, and <code dir=\"ltr\">T[number]</code> gives an array’s element type.","<code dir=\"ltr\">Object.keys</code> returns <code dir=\"ltr\">string[]</code> on purpose: structural typing means an object can have more keys than its type lists."],"further":[{"title":"Keyof Type Operator","url":"https://www.typescriptlang.org/docs/handbook/2/keyof-types.html"},{"title":"Typeof Type Operator","url":"https://www.typescriptlang.org/docs/handbook/2/typeof-types.html"},{"title":"Indexed Access Types","url":"https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.html"}],"quiz":[{"q":"<p>What is <code dir=\"ltr\">Zone</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\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SHIPPING_ZONES</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\">  domestic: { feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">499</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  gulf: { feeCents: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1299</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:#CF222E;--shiki-dark:#FF7B72\">as</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> const</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\"> Zone</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> typeof</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> SHIPPING_ZONES</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n","options":[{"text":"<code dir=\"ltr\">'domestic' | 'gulf'</code>","why":"Correct. <code dir=\"ltr\">typeof</code> turns the value into its type, and <code dir=\"ltr\">keyof</code> takes the union of its property names."},{"text":"<code dir=\"ltr\">string</code>","why":"<code dir=\"ltr\">keyof</code> on an object type with known properties gives those exact names, not <code dir=\"ltr\">string</code>. You get <code dir=\"ltr\">string</code> only from an index signature."},{"text":"<code dir=\"ltr\">{ feeCents: 499 } | { feeCents: 1299 }</code>","why":"That is the union of the values, which you would get with <code dir=\"ltr\">(typeof SHIPPING_ZONES)[Zone]</code>."},{"text":"It is an error, because <code dir=\"ltr\">keyof</code> needs a type, not a value.","why":"<code dir=\"ltr\">typeof</code> in a type position converts the value first, so <code dir=\"ltr\">keyof</code> does receive a type."}],"answer":0},{"q":"<p><code dir=\"ltr\">type CheckoutResponse = { lines: { sku: string; qty: number }[] }</code>. Which type is <code dir=\"ltr\">{ sku: string; qty: number }</code>?</p>","options":[{"text":"<code dir=\"ltr\">CheckoutResponse.lines[0]</code>","why":"Dot access does not exist in type positions; TypeScript reads <code dir=\"ltr\">CheckoutResponse.lines</code> as a namespace lookup and fails."},{"text":"<code dir=\"ltr\">CheckoutResponse['lines']</code>","why":"That is the whole array type, <code dir=\"ltr\">{ sku: string; qty: number }[]</code>, not one element."},{"text":"<code dir=\"ltr\">keyof CheckoutResponse['lines']</code>","why":"That gives the array’s keys (<code dir=\"ltr\">number</code>, <code dir=\"ltr\">'length'</code>, <code dir=\"ltr\">'map'</code> …), not its elements."},{"text":"<code dir=\"ltr\">CheckoutResponse['lines'][number]</code>","why":"Correct. Indexing an array type with <code dir=\"ltr\">number</code> gives its element type."}],"answer":3},{"q":"<p>What is <code dir=\"ltr\">keyof (ShippedOrder | CancelledOrder)</code> when both have <code dir=\"ltr\">id</code> and <code dir=\"ltr\">status</code>, only <code dir=\"ltr\">ShippedOrder</code> has <code dir=\"ltr\">trackingNumber</code>, and only <code dir=\"ltr\">CancelledOrder</code> has <code dir=\"ltr\">cancelReason</code>?</p>","options":[{"text":"<code dir=\"ltr\">'id' | 'status' | 'trackingNumber' | 'cancelReason'</code>","why":"That would let you read <code dir=\"ltr\">trackingNumber</code> from a value that might be a cancelled order, which is unsafe."},{"text":"<code dir=\"ltr\">'id' | 'status'</code>","why":"Correct. A key of a union must be a key of every member, so only the shared properties remain."},{"text":"<code dir=\"ltr\">never</code>, because the two types are different","why":"The members share <code dir=\"ltr\">id</code> and <code dir=\"ltr\">status</code>, so those keys survive."}],"answer":1},{"q":"<p>Why does <code dir=\"ltr\">Object.keys(order)</code> return <code dir=\"ltr\">string[]</code> instead of <code dir=\"ltr\">(keyof Order)[]</code>?</p>","options":[{"text":"Because the TypeScript team has not got round to typing it yet.","why":"It is a deliberate choice that follows from how the type system works, not an omission."},{"text":"Because <code dir=\"ltr\">keyof</code> is not available in lib files.","why":"Lib files use <code dir=\"ltr\">keyof</code> all over the place; this is not a technical limitation."},{"text":"Because a value of type <code dir=\"ltr\">Order</code> can have extra properties at runtime that the type does not list.","why":"Correct. Structural typing lets a <code dir=\"ltr\">DetailedOrder</code> pass as an <code dir=\"ltr\">Order</code>, so its extra keys would show up at runtime."}],"answer":2}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"const SHIPPING_ZONES = {\n  domestic: { countries: ['US', 'CA'], feeCents: 499 },\n  gulf: { countries: ['AE', 'SA'], feeCents: 1299 },\n  levant: { countries: ['EG', 'JO'], feeCents: 899 },\n  europe: { countries: ['GB', 'DE'], feeCents: 999 },\n} as const;\n\n// Shape of the checkout API response (generated from the backend's schema).\ntype CheckoutResponse = {\n  order: { id: number; status: 'processing' | 'shipped'; totalCents: number };\n  lines: { sku: string; quantity: number; unitPriceCents: number }[];\n};\n\ntype Zone = string;\ntype ShippingCountry = string;\ntype FeeCents = number;\ntype LineItem = unknown;\ntype CheckoutStatus = string;\n\nfunction zoneFor(country: ShippingCountry): Zone {\n  for (const [zone, config] of Object.entries(SHIPPING_ZONES)) {\n    if ((config.countries as readonly string[]).includes(country)) return zone;\n  }\n  throw new Error(`No shipping zone for ${country}`);\n}\n\nfunction feeFor(country: ShippingCountry): FeeCents {\n  return SHIPPING_ZONES[zoneFor(country)].feeCents;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<Zone, 'domestic' | 'gulf' | 'levant' | 'europe'>>;\ntype T2 = Expect<Equal<ShippingCountry, 'US' | 'CA' | 'AE' | 'SA' | 'EG' | 'JO' | 'GB' | 'DE'>>;\ntype T3 = Expect<Equal<FeeCents, 499 | 1299 | 899 | 999>>;\ntype T4 = Expect<Equal<LineItem, { sku: string; quantity: number; unitPriceCents: number }>>;\ntype T5 = Expect<Equal<CheckoutStatus, 'processing' | 'shipped'>>;\n\nfunction typeTests() {\n  // @ts-expect-error: France is not in any shipping zone\n  zoneFor('FR');\n}\n\nconsole.log(zoneFor('JO'), feeFor('SA'));\n"},"solution":{"ts":"const SHIPPING_ZONES = {\n  domestic: { countries: ['US', 'CA'], feeCents: 499 },\n  gulf: { countries: ['AE', 'SA'], feeCents: 1299 },\n  levant: { countries: ['EG', 'JO'], feeCents: 899 },\n  europe: { countries: ['GB', 'DE'], feeCents: 999 },\n} as const;\n\n// Shape of the checkout API response (generated from the backend's schema).\ntype CheckoutResponse = {\n  order: { id: number; status: 'processing' | 'shipped'; totalCents: number };\n  lines: { sku: string; quantity: number; unitPriceCents: number }[];\n};\n\ntype ShippingZones = typeof SHIPPING_ZONES;\ntype Zone = keyof ShippingZones;\ntype ShippingCountry = ShippingZones[Zone]['countries'][number];\ntype FeeCents = ShippingZones[Zone]['feeCents'];\ntype LineItem = CheckoutResponse['lines'][number];\ntype CheckoutStatus = CheckoutResponse['order']['status'];\n\nfunction zoneFor(country: ShippingCountry): Zone {\n  // SHIPPING_ZONES is our own const object, so it has no extra keys.\n  for (const zone of Object.keys(SHIPPING_ZONES) as Zone[]) {\n    if ((SHIPPING_ZONES[zone].countries as readonly string[]).includes(country)) return zone;\n  }\n  throw new Error(`No shipping zone for ${country}`);\n}\n\nfunction feeFor(country: ShippingCountry): FeeCents {\n  return SHIPPING_ZONES[zoneFor(country)].feeCents;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<Zone, 'domestic' | 'gulf' | 'levant' | 'europe'>>;\ntype T2 = Expect<Equal<ShippingCountry, 'US' | 'CA' | 'AE' | 'SA' | 'EG' | 'JO' | 'GB' | 'DE'>>;\ntype T3 = Expect<Equal<FeeCents, 499 | 1299 | 899 | 999>>;\ntype T4 = Expect<Equal<LineItem, { sku: string; quantity: number; unitPriceCents: number }>>;\ntype T5 = Expect<Equal<CheckoutStatus, 'processing' | 'shipped'>>;\n\nfunction typeTests() {\n  // @ts-expect-error: France is not in any shipping zone\n  zoneFor('FR');\n}\n\nconsole.log(zoneFor('JO'), feeFor('SA'));\n"},"checks":[{"id":"types","typecheck":true},{"id":"zone","test":"return zoneFor('JO') === 'levant' && zoneFor('CA') === 'domestic';"},{"id":"fee","test":"return feeFor('SA') === 1299 && feeFor('GB') === 999;"}],"title":"Derive the shipping types","prompt":"<p>Replace the five hand-written types with types <strong>derived</strong> from <code dir=\"ltr\">SHIPPING_ZONES</code> and <code dir=\"ltr\">CheckoutResponse</code>, so that adding a zone or changing the API type updates them automatically:</p>\n<ul>\n<li><code dir=\"ltr\">Zone</code>: the zone names.</li>\n<li><code dir=\"ltr\">ShippingCountry</code>: every country in every zone.</li>\n<li><code dir=\"ltr\">FeeCents</code>: every zone’s fee, as literal numbers.</li>\n<li><code dir=\"ltr\">LineItem</code>: one element of <code dir=\"ltr\">CheckoutResponse['lines']</code>.</li>\n<li><code dir=\"ltr\">CheckoutStatus</code>: the type of the order’s <code dir=\"ltr\">status</code>.</li>\n</ul>\n<p>Then make <code dir=\"ltr\">zoneFor</code> and <code dir=\"ltr\">feeFor</code> compile with the new types (no <code dir=\"ltr\">any</code>).</p>\n","hints":["Start from <code dir=\"ltr\">typeof SHIPPING_ZONES</code>; <code dir=\"ltr\">keyof</code> of that gives the zone names.","Index with the union of zone names to reach every config at once: <code dir=\"ltr\">ShippingZones[Zone]['feeCents']</code>. Arrays are indexed with <code dir=\"ltr\">number</code>.","<code dir=\"ltr\">Object.entries</code> and <code dir=\"ltr\">Object.keys</code> give you <code dir=\"ltr\">string</code> keys. Since <code dir=\"ltr\">SHIPPING_ZONES</code> is your own <code dir=\"ltr\">as const</code> object, <code dir=\"ltr\">Object.keys(SHIPPING_ZONES) as Zone[]</code> is an honest, local cast."],"explanation":"<p><code dir=\"ltr\">typeof</code> brings the config object into the type world, <code dir=\"ltr\">keyof</code> lists its zone names, and indexed access with the union <code dir=\"ltr\">Zone</code> reaches into every zone at once, so <code dir=\"ltr\">ShippingZones[Zone]['countries'][number]</code> collects all countries. <code dir=\"ltr\">CheckoutResponse['lines'][number]</code> names the line-item type without copying its fields. <code dir=\"ltr\">Object.keys</code> returns <code dir=\"ltr\">string[]</code> because objects can carry extra keys at runtime; for a const object you defined, casting to <code dir=\"ltr\">Zone[]</code> is safe, and the cast sits next to a comment saying why.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including all type tests","zone":"<code dir=\"ltr\">zoneFor</code> finds the right zone for a country","fee":"<code dir=\"ltr\">feeFor</code> returns the zone’s fee"}},"words":755,"readMinutes":4}