cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Mapped types

Build a new type from every property of another: make them optional or read-only, give them a new type, rename keys with as, and leave keys out.

By the end, you can
  • 01
    Read and write a mapped typeExplain how { [Key in keyof T]: T[Key] } goes through every key of a type and builds a property for each.
  • 02
    Change modifiersAdd ? or readonly to every property, and take them away with -? and -readonly.
  • 03
    Rename and filter keysUse as to give keys new names, and to leave a key out by turning it into never.

A flag for every field of a form

A signup form has a name, an email, and an age. To show an error only after someone has left a field, the page keeps a "touched" flag for each field: true or false. You could write that type by hand, but then every new field means changing two types. A mapped type builds it from the form for you:

A touched flag for every fieldPop out in the code editor (opens in a new tab)TypeScript
type SignupForm = { name: string; email: string; age: number }; type Touched = { [Field in keyof SignupForm]: boolean };//   ^?const touched: Touched = { name: true, email: false, age: false };console.log(touched.name);

Line 1 is the form's type. Line 3 is the mapped type. Read it as: "for each Field in the keys of SignupForm, make a property called Field whose type is boolean". The query on line 4 shows the result: { name: boolean; email: boolean; age: boolean }. Line 5 makes a touched object of that type, and line 6 prints true.

It works like a loop over an object's keys in JavaScript (the Object.keys, values, entries lesson covers Object.keys). Here is that loop, building the touched object while the page runs:

The same idea as a JavaScript loopPop out in the code editor (opens in a new tab)JavaScript
const form = { name: "Asha", email: "", age: 30 };

const touched = {};
for (const field of Object.keys(form)) {
  touched[field] = false;
}
console.log(touched);

Line 1 is a form's values. Line 4 goes through its keys, and line 5 gives each one the value false. Line 7 prints { name: false, email: false, age: false }. The mapped type does the same thing to a type, before the code runs: it goes through the keys and makes one property for each.

Left, a window SignupForm lists name: string, email: string, and age: number. Each one has an arrow through the rule ': boolean' to a window Touched on the right, which lists name: boolean, email: boolean, and age: boolean.SignupFormname: stringemail: stringage: number: booleanthe ruleTouchedname: booleanemail: booleanage: boolean
The mapped type on line 3 takes each key of SignupForm in turn and makes a property with the same name. The rule after the colon, boolean, is the same for all three, so Touched has three boolean properties.
Real-life analogyA form template for every guest

You are making name cards for a dinner. You go down the guest list and, for each guest, write their name on a card with the same printed design. You never invent a guest: the cards follow the list. Add a guest, and you make one more card.

In real life: The guest list
In TypeScript: The keys: keyof SignupForm
In real life: Going down the list, one guest at a time
In TypeScript: Field in … takes each key in turn
In real life: The same name card for every guest
In TypeScript: The type after the colon, boolean
In real life: A stack of name cards, one per guest
In TypeScript: The new type, Touched
The main idea

A mapped type builds a new object type from a list of keys. { [Key in keyof T]: … } goes through every key of T and makes one property for each, with the type you write after the colon. Change T, and the new type follows.

How [Key in keyof T] walks the keys

A mapped type has three parts. Here is one that shows each key it visits:

Each key, made into its own typePop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number; showTips: boolean }; type KeyNames = { [Key in keyof Settings]: Key };//   ^?
  • keyof Settings is the union of Settings's key names: "theme" | "fontSize" | "showTips". (The keyof & typeof lesson covers keyof.)
  • Key in … takes each of those names in turn. Key is a name you choose, like the variable of a for…of loop.
  • The square brackets [ ] say "the property's name is Key", and the type after the colon is the property's type.

On line 3 the type after the colon is Key itself, so the query on line 4 shows each property holding its own name: { theme: "theme"; fontSize: "fontSize"; showTips: "showTips" }. Usually you want the property's type instead. Settings[Key] reads it: for "fontSize", it is number. That is an indexed access type, which the Indexed access types lesson covers.

keyof Settings gives three names: "theme", "fontSize", and "showTips". Key takes each name in turn. Settings[Key] reads that property type: string, number, and boolean.keyof Settings"theme""fontSize""showTips"Key1st2nd3rdSettings[Key]stringnumberboolean
keyof Settings is the list of key names. Key takes each name in turn, like the loop variable of a for…of loop. Settings[Key] reads the type of that property: string, number, then boolean.

Now try it. Type a different type after the colon on line 3, and watch the answer after ^? on line 4 and the errors on line 5. It starts as boolean, so line 5's text and number are both errors, on purpose:

Playground: type each property's type
Saved settings, mapped from SettingsPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number; showTips: boolean }; type Saved = { [Key in keyof Settings]: boolean };//   ^?const saved: Saved = { theme: "dark", fontSize: 16, showTips: true };console.log(saved.fontSize);
Each property isboolean
What TypeScript makes
Saved{ theme: boolean; fontSize: boolean; showTips: boolean }
Errors2 errors: line 5
Try it yourself

Every property becomes a boolean. Line 5 gives theme the text "dark" and fontSize the number 16, so TypeScript reports both: Type 'string' is not assignable to type 'boolean' and Type 'number' is not assignable to type 'boolean'.

The code is checked by the real TypeScript compiler each time you type. Only line 3 changes. Reset puts boolean back.

With Settings[Key], every property keeps its own type, so Saved is a copy of Settings and the errors are gone. Settings[Key] | null keeps each type and also allows null. string turns every property into text, so the number and the boolean on line 5 are errors.

The keys don't have to come from keyof. Any union of names works, like the names of a site's feature flags:

A setting for each feature flagPop out in the code editor (opens in a new tab)TypeScript
type Flag = "darkMode" | "newCheckout"; type FlagSettings = { [Name in Flag]: boolean };//   ^?const flags: FlagSettings = { darkMode: true, newCheckout: false };console.log(flags.newCheckout);

Line 1 lists the flags as a union of literal types. Line 3 maps over that union directly: Name takes "darkMode", then "newCheckout". The query on line 4 shows { darkMode: boolean; newCheckout: boolean }. Line 5 sets both, and line 6 prints false. Add a third name to line 1, and line 5 gets an error until it sets that flag too.

Adding and removing ? and readonly

A property can have two modifiers: ? makes it optional, and readonly stops code from changing it (Object types covers both). A mapped type can add them to every property at once. A profile update, where the user changes only some fields, needs every field optional:

A profile update where every field is optionalPop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; bio: string }; type ProfileUpdate = { [Key in keyof Profile]?: Profile[Key] };//   ^?const update: ProfileUpdate = { bio: "Loves green tea" };console.log(update.bio);

Line 3 has ? after the square brackets, so every property is optional. The query on line 4 shows { name?: string | undefined; bio?: string | undefined }. TypeScript adds | undefined because reading a property that was left out gives undefined. Line 5 sends only a new bio, and line 6 prints Loves green tea.

readonly goes before the square brackets. Saved settings shouldn't change by mistake, so here every property is read-only, and line 5 is an error on purpose:

Saved settings that are read-only (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number }; type FrozenSettings = { readonly [Key in keyof Settings]: Settings[Key] };const saved: FrozenSettings = { theme: "dark", fontSize: 16 };saved.fontSize = 18;console.log(saved.fontSize);

Line 5 gets Cannot assign to 'fontSize' because it is a read-only property. It means the type says nobody may change fontSize after the object is made. Notice that line 6 still prints 18 if you run it: the types are removed before the code runs, so readonly doesn't freeze the object in the browser.

A minus sign takes a modifier away: -? makes every property required, and -readonly makes every property changeable. Try each one on a profile whose bio is optional:

Playground: add or remove a modifier
A profile update with modifiersPop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; bio?: string }; type Changed = { [Key in keyof Profile]: Profile[Key] };//   ^?const update: Changed = { name: "Asha" };update.name = "Ravi";console.log(update.name);
Console outputRavi
What TypeScript makes
Changed{ name: string; bio?: string | undefined }
ErrorsNo errors
Try it yourself

No modifier, so each property keeps the ones it had: name is required and bio stays optional. Line 5 leaves out the bio, which is allowed, and line 6 changes the name. No errors.

TypeScript checks the code for real each time. Each button changes only line 3. Reset goes back to Copy as it is.

Copy as it is keeps bio optional: a mapped type over keyof Profile copies the modifiers each property already had. Add ? makes name optional too. Add readonly makes line 6 an error. Remove ? makes bio required, so line 5, which has no bio, is an error. Every version prints Ravi when it runs.

Three columns. Profile: name: string and bio?: string. With ? added: name?: string and bio?: string. With -?: name: string and bio: string.Profilename: stringbio?: stringwith ?name?: stringbio?: stringall optionalwith -?name: stringbio: stringall required
Profile has one optional property, bio. Adding ? makes both optional. Writing -? takes the ? away, so both are required. readonly and -readonly work the same way.

Here is -readonly, turning a saved order into a draft you can edit:

An editable draft of a saved orderPop out in the code editor (opens in a new tab)TypeScript
type SavedOrder = { readonly id: number; readonly total: number }; type OrderDraft = { -readonly [Key in keyof SavedOrder]: SavedOrder[Key] };//   ^?const draft: OrderDraft = { id: 7, total: 9 };draft.total = 12;console.log(draft.total);

Line 1's order has two read-only properties. Line 3 writes -readonly before the square brackets, and the query on line 4 shows { id: number; total: number } with no readonly left. Line 6 changes the total without an error, and line 7 prints 12.

The table below lists the five ways to write it. Read each row as: what you write, what it does, and an example.

Modifiers in a mapped type
What you writeWhat it doesFor example
[Key in keyof T]: T[Key]Copies each property, keeping the ? and readonly it already had.bio?: string stays optional.
[Key in keyof T]?: T[Key]Makes every property optional.A profile update can leave out any field.
[Key in keyof T]-?: T[Key]Makes every property required.bio?: string becomes bio: string.
readonly [Key in keyof T]: T[Key]Makes every property read-only.Saved settings can't be changed by mistake.
-readonly [Key in keyof T]: T[Key]Takes readonly away from every property.A draft of a saved order can be edited.

Renaming keys with as

So far each new property kept its key's name. Add as after the keys, and you choose a new name for each one. A settings page needs a label for every setting:

A label for every settingPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number }; type SettingLabels = { [Key in keyof Settings as `${Key}Label`]: string };//   ^?const labels: SettingLabels = { themeLabel: "Theme", fontSizeLabel: "Font size" };console.log(labels.fontSizeLabel);

On line 3, as `${Key}Label` says what each key is called in the new type. The backticks make a template literal type: like a JavaScript template literal, but it builds a string type. For "theme" it gives "themeLabel". The next lesson, Template literal types, covers them. The query on line 4 shows { themeLabel: string; fontSizeLabel: string }. Line 5 fills in both labels, and line 6 prints Font size.

With as, the union after in doesn't even have to be key names. Here it is a union of objects, and as turns each one into a key. An app has two kinds of events, and you want one handler function for each kind, named after it:

One handler for each kind of eventPop out in the code editor (opens in a new tab)TypeScript
type ClickEvent = { kind: "click"; x: number; y: number };type SearchEvent = { kind: "search"; query: string };type AppEvent = ClickEvent | SearchEvent; type Handlers = { [Event in AppEvent as Event["kind"]]: (event: Event) => void };//   ^?const handlers: Handlers = {  click: (event) => console.log(`Clicked at ${event.x}`),  search: (event) => console.log(`Searching for ${event.query}`),};handlers.search({ kind: "search", query: "tea" });

Lines 1 and 2 describe a click and a search, each with a kind. Line 3 puts them in one union (the Discriminated unions lesson uses the same pattern). On line 5, Event takes each event type in turn, and as Event["kind"] names the property after its kind. The handler's type uses the whole event. The query on line 6 shows { click: (event: ClickEvent) => void; search: (event: SearchEvent) => void }. So on line 9, TypeScript knows event has a query. Line 11 calls the search handler, which prints Searching for tea.

Leaving keys out

as can also leave a key out: if the new name is never, the type that has no values, that property isn't made. A form for a new product has no id yet, because the server gives it one. Press the buttons to change the rule on line 2:

Playground: which keys to drop
A new product without an idPop out in the code editor (opens in a new tab)TypeScript
type Product = { id: number; name: string; price: number };type Dropped<Key> = Key extends "id" ? never : Key; type NewProduct = { [Key in keyof Product as Dropped<Key>]: Product[Key] };//   ^?const draft: NewProduct = { name: "Tea", price: 3 };console.log(draft.name);
Keys keptname, price
What happens
TypeScriptNo errors
When it runsprints Tea
Try it yourself

For "id", line 2 gives never, and a key that becomes never is left out. NewProduct has only name and price, so line 6's draft, which has no id yet, is fine.

TypeScript checks the code for real each time. Each button changes only line 2. Reset goes back to Drop id.

Line 2 is a conditional type: Key extends "id" ? never : Key means "if the key is "id", give never; otherwise give the key back". Line 4 runs it on each key with as Dropped<Key>. With Drop id, the query on line 5 shows { name: string; price: number }. With Keep every key, the draft on line 6 needs an id. With Drop id and price, the draft's price is one key too many. The code prints Tea every time.

Three keys, "id", "name", and "price", go through the rule as Dropped<Key>. "id" comes out as never, shown faded and crossed out, and is left out. "name" and "price" come out unchanged and become the properties of NewProduct."id""name""price"as Dropped<Key>the ruleneverleft out"name"kept"price"kept
The as rule runs once for each key. For "id" it gives never, and a key that becomes never is left out. "name" and "price" pass through, so NewProduct has two properties.

The rule can look at each property's type instead of its name. A search box only searches text fields:

Only the text fields of a productPop out in the code editor (opens in a new tab)TypeScript
type Product = { id: number; name: string; price: number; description: string }; type TextFields = {  [Key in keyof Product as Product[Key] extends string ? Key : never]: Product[Key];};type SearchKey = keyof TextFields;//   ^?

Line 4 keeps a key when its property's type, Product[Key], is a string, and turns it into never otherwise. id and price are numbers, so they are left out. Line 6 asks for the keys that are left, and the query on line 7 shows "name" | "description".

Typing a form's state

A real form keeps three things for its fields: the values, which fields were touched, and an error message for each field. Two mapped types make the last two from the form's type, so the form's fields are written in one place. Press the buttons to change the form on line 1:

Playground: change the form's fields
A signup form's statePop out in the code editor (opens in a new tab)TypeScript
type SignupForm = { name: string; email: string; age: number }; type FormErrors<Form> = { [Field in keyof Form]?: string };type FormTouched<Form> = { [Field in keyof Form]: boolean }; const values: SignupForm = { name: "Asha", email: "", age: 30 };const touched: FormTouched<SignupForm> = { name: true, email: true, age: false };const errors: FormErrors<SignupForm> = {}; if (touched.email && values.email === "") {  errors.email = "Enter your email";}console.log(errors.email);
Console outputEnter your email
What TypeScript says
ErrorsNo errors
Try it yourself

FormTouched gives every field a boolean and FormErrors gives every field an optional message. The email was touched and is empty, so line 11 stores a message and line 13 prints Enter your email.

TypeScript checks the code for real each time. Each button changes only line 1. Reset goes back to The form as it is.

Line 3 gives every field an optional error message, and line 4 gives every field a boolean. Both take the form as a type parameter, Form, so they work for any form (Generic types & interfaces covers types with type parameters). Lines 6 to 8 are the form's state. Line 10 checks whether the email was touched and is still empty, and line 11 stores a message. Line 13 prints Enter your email.

With Add a phone field, both values and touched need a phone, and TypeScript points at lines 6 and 7. With Rename email to mail, every line that still says email gets an error, with the fix in the message. Without mapped types, you would have three types to keep in step by hand.

Error or fine?
  • const flags: { [Flag in "dark" | "beta"]: boolean } = { dark: true };
  • const flags: { [Flag in "dark" | "beta"]?: boolean } = { dark: true };
  • const saved: { readonly [Key in "theme"]: string } = { theme: "dark" }; saved.theme = "light";
  • const draft: { -readonly [Key in "theme"]: string } = { theme: "dark" }; draft.theme = "light";
  • const labels: { [Key in "theme" as `${Key}Label`]: string } = { theme: "Theme" };
  • const labels: { [Key in "theme" as `${Key}Label`]: string } = { themeLabel: "Theme" };
  • const product: { [Key in "id" | "name" as Key extends "id" ? never : Key]: string } = { name: "Tea" };
  • const product: { [Key in "id" | "name" as Key extends "id" ? never : Key]: string } = { id: "7", name: "Tea" };
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it. Each line is checked on its own.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

readonly from a mapped type only covers the top level. A nested object can still change:

readonly is one level deepPop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; layout: { sidebar: boolean } };type FrozenSettings = { readonly [Key in keyof Settings]: Settings[Key] }; const settings: FrozenSettings = { theme: "dark", layout: { sidebar: true } };settings.layout.sidebar = false;console.log(settings.layout.sidebar);

Line 2 makes theme and layout read-only. Line 5 doesn't replace layout; it changes a property inside it, and that inner sidebar isn't read-only. So there is no error, and line 6 prints false. Making every level read-only needs a mapped type that calls itself, which the Writing your own utility types lesson builds.

A mapped type is also different from an index signature. Here are both, with a misspelled key, an error on purpose:

Exact keys or any keys (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number }; type AnyFlags = { [key: string]: boolean };type SettingFlags = { [Key in keyof Settings]: boolean }; const loose: AnyFlags = { theme: true, colour: true };const exact: SettingFlags = { theme: true, colour: true };

Line 3 is an index signature: any text key may hold a boolean, so line 6's colour is fine. Line 4 is a mapped type with exactly the keys of Settings. Line 7 gets Object literal may only specify known properties, and 'colour' does not exist in type 'SettingFlags': the key is misspelled. fontSize is missing too, but TypeScript reports only the extra key here.

  • "A mapped type copies my object while the page runs." No. It builds a type, before the code runs. To build an object, you still write code, like the loop in the overview.
  • "readonly in a mapped type freezes the object." No. It only makes changing it a type error. The frozen settings example still prints 18 when it runs.
  • "readonly reaches every level." No. It covers the properties of the top object only.
  • "Copying a type with [Key in keyof T] drops its ?." No. A mapped type over keyof T keeps each property's ? and readonly. Use -? or -readonly to take them away.
  • "A key that maps to never stays, with the type never." No. With as, a key that becomes never is left out. A property whose type is never, after the colon, would stay.
  • "I have to write these myself every time." No. TypeScript has Partial, Required, Readonly, Pick, Omit, and Record built in, and they are built from mapped types. The Partial, Pick, Omit & friends lesson covers them.

The table below compares the two from the example above. Read each row as a question, then the mapped type's answer, then the index signature's.

A mapped type or an index signature
QuestionMapped type: { [Key in keyof Settings]: boolean }Index signature: { [key: string]: boolean }
Which keys may the object have?Exactly the keys of Settings: theme and fontSize.Any text key at all.
Must every key be there?Yes, unless the mapped type adds ?.No. It may have none of them.
What does a misspelled key like colour do?An error: colour isn't one of the keys.Nothing. colour is just one more key.
Where do the keys come from?From another type, through keyof, or from a union like "darkMode" | "newCheckout".It doesn't list keys. [key: string] allows any text key.

Practice exercises

Each exercise is TypeScript. Press Edit & run on the starter code: the errors update as you type, and a // ^? line under a type name shows what TypeScript made. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead a mapped property's type

What type is FontSize? Guess first, then add a // ^? line under it to check.

Starter codePop out in the code editor (opens in a new tab)TypeScript
type Settings = { theme: string; fontSize: number; showTips: boolean }; type AsText = { [Key in keyof Settings]: string };type FontSize = AsText["fontSize"];

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upWhat a copy keeps

    Which property of Copy is optional?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type Profile = { name: string; bio?: string; age: number }; type Copy = { [Key in keyof Profile]: Profile[Key] };

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWrite a Touched type

      Touched is unknown, so line 5 can't read touched.address. Write Touched as a mapped type. What does the code print then?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type CheckoutForm = { address: string; card: string };type Touched<Form> = unknown; const touched: Touched<CheckoutForm> = { address: true, card: false };console.log(touched.address);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeA profile that is still loading

        The bio hasn't loaded yet, so it is null, and line 4 has an error. Write Nullable so every property may also be null. What type is bio then?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type Profile = { name: string; bio: string };type Nullable<T> = T; const profile: Nullable<Profile> = { name: "Asha", bio: null };console.log(profile.bio);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeError keys for a form

          The code wants keys like emailError, but SignupErrors has email. Rename the keys on line 3. Which key holds the email's message then?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type SignupForm = { name: string; email: string }; type SignupErrors = { [Field in keyof SignupForm]?: string };const errors: SignupErrors = { emailError: "Enter your email" };console.log(errors.emailError);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeSave only the cart's data

            To save a cart, you only want its data, not its functions. Line 4 has an error because DataOnly still has checkout and clear. Leave out every key whose property is a function. Which keys are left?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Cart = { items: string[]; total: number; checkout: () => void; clear: () => void }; type DataOnly = { [Key in keyof Cart]: Cart[Key] };const saved: DataOnly = { items: ["Tea"], total: 3 };console.log(saved.total);

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, go through the keys one at a time: what is the new key called, is it left out, and what type and modifiers does it get? The quiz code isn't checked on the page, so no error list or type hint gives the answer away.

              The mapped types quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In { [Key in keyof Settings]: boolean }, what does Key in keyof Settings do?

                Choose an answer to see the explanation.

              2. Question 2 of 7What type does fontSize have in Saved?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Settings = { theme: string; fontSize: number };
                
                type Saved = { [Key in keyof Settings]: boolean };

                Choose an answer to see the explanation.

              3. Question 3 of 7Is bio optional in Complete?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Profile = { name: string; bio?: string };
                
                type Complete = { [Key in keyof Profile]-?: Profile[Key] };

                Choose an answer to see the explanation.

              4. Question 4 of 7Does TypeScript report an error here, and what does the code print?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Settings = { fontSize: number };
                const saved: { readonly [Key in keyof Settings]: Settings[Key] } = { fontSize: 16 };
                saved.fontSize = 18;
                console.log(saved.fontSize);

                Choose an answer to see the explanation.

              5. Question 5 of 7Which of these is a key of Labels?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Settings = { theme: string; fontSize: number };
                
                type Labels = { [Key in keyof Settings as `${Key}Label`]: string };

                Choose an answer to see the explanation.

              6. Question 6 of 7Which keys does Listing keep?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Product = { id: number; name: string; price: number };
                
                type Listing = { [Key in keyof Product as Key extends "id" ? never : Key]: Product[Key] };

                Choose an answer to see the explanation.

              7. Question 7 of 7How is { [Key in keyof Settings]: boolean } different from { [key: string]: boolean }?

                Choose an answer to see the explanation.

              Key takeaways

              • { [Key in keyof T]: … } goes through every key of T and makes one property for each.
              • The type after the colon is each property's type. T[Key] keeps the original type.
              • ? and readonly add modifiers to every property; -? and -readonly take them away. A plain copy keeps the ones it had.
              • as gives each key a new name. A key that becomes never is left out.
              • Mapped types only change types. They don't copy, freeze, or check objects while the page runs.
              • Build related types from one source type, and they all follow it when it changes.

              Remember the one-liner.
              A mapped type goes down a list of keys and makes one property for each, with the name, type, and modifiers you choose.

              Coming next: Template literal types. They build string types from other types, like the themeLabel keys in this lesson, and event names like onClick.

              CompleteFrontend Clear concepts. Working examples.