cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Writing your own utility types

Rebuild Partial, Readonly, and Pick from mapped types, write recursive types and a DeepPartial for nested settings, and keep your own helper types readable.

By the end, you can
  • 01
    Rebuild the built-in helpersWrite Partial, Readonly, Pick, and Omit yourself from mapped types, keyof, and constraints.
  • 02
    Write recursive typesDescribe trees and nested data with a type that refers to itself, and build DeepPartial and DeepReadonly.
  • 03
    Keep helpers readableFlatten what a hover shows, and know the limits of deep helpers, like functions inside an object.

The built-in helpers are types you can write

Partial, Pick, Readonly, and the other helpers from the Partial, Pick, Omit & friends lesson aren't magic. Each one is a short type alias, a name for a type, written in a file that comes with TypeScript. They are built from the same parts you already know: mapped types and conditional types. So you can write them yourself:

Partial, written by handPop out in the code editor (opens in a new tab)TypeScript
type MyPartial<T> = {  [Key in keyof T]?: T[Key];}; type Profile = { name: string; email: string; age: number };type ProfileChange = MyPartial<Profile>;//   ^?const change: ProfileChange = { email: "asha@example.com" };console.log(change);

Lines 1 to 3 make MyPartial. T is a type parameter: a name for whatever type you pass in the angle brackets. Line 2 is a mapped type: for each Key in keyof T, make a property with that name, add ? so it may be left out, and give it the type T[Key], the same type it had in T.

Line 5 describes a profile. Line 6 uses the helper, and the query on line 7 shows every property with a ?. TypeScript also writes | undefined after each type, because a property that is left out reads as undefined. Line 8 gives only an email, and line 9 prints { email: 'asha@example.com' }.

Two editor windows. lib.es5.d.ts holds type Partial<T> = { [P in keyof T]?: T[P]; }. my-types.ts holds type MyPartial<T> = { [Key in keyof T]?: T[Key]; }. A label between them says the same recipe.lib.es5.d.tstype Partial<T> = {[P in keyof T]?: T[P];};built into TypeScriptmy-types.tstype MyPartial<T> = {[Key in keyof T]?: T[Key];};written by you
TypeScript's own Partial is a mapped type in a library file. Your MyPartial on lines 1 to 3 is the same recipe with longer names, and it makes the same type.
Real-life analogyRecipes in a cookbook

A cookbook's recipes look fixed until you read one closely: it's just steps you could follow yourself. Copy it onto your own card and it makes the same dish. Change one step and you have a dish the book never printed.

In real life: A recipe printed in the cookbook
In TypeScript: A built-in helper like Partial
In real life: The ingredient you bring
In TypeScript: The type you pass in, T
In real life: Copying a recipe onto your own card
In TypeScript: Writing MyPartial yourself
In real life: Changing one step to make a new dish
In TypeScript: Changing ? to readonly for a new helper
The main idea

A utility type is a type alias with type parameters. The built-in ones are mapped and conditional types in TypeScript's library, and you can write your own the same way: walk the keys, change each property, or call the helper again for each level.

Rebuild Partial, Readonly, and Pick

Most object helpers are the same mapped type with one small change. Press the buttons to swap the helper's body on line 3, and watch what it does to the profile:

Playground: change the helper's recipe
One helper, applied to a profilePop out in the code editor (opens in a new tab)TypeScript
type Profile = { name: string; email: string; age?: number }; type Helper<T> = { [Key in keyof T]?: T[Key] }; type Draft = Helper<Profile>;//   ^?const draft: Draft = { name: "Asha", email: "asha@example.com" };draft.name = "Ravi";console.log(draft.name);
Console outputRavi
What happens
RecipePartial
TypeScriptNo errors
Try it yourself

? after the brackets makes every property optional. This is the recipe of the built-in Partial.

TypeScript checks the code for real, and the output is what the code prints. Each button changes only line 3. Reset goes back to Partial.

Line 1 is a profile whose age may be missing. Line 5 applies the helper, and the query on line 6 shows the result. Line 7 makes a draft with a name and an email, and line 8 changes the name. Each recipe changes one thing:

  • ? after the brackets adds a question mark to every key: Partial.
  • -? takes them away: Required. Now line 7 is missing age.
  • readonly in front locks every key: Readonly. Now line 8 can't change the name.
  • T[Key] | null changes the value part instead, which makes a helper TypeScript doesn't have.

Notice that age keeps its ? in the Readonly, Nullable, and Plain copy recipes. A mapped type over keyof T copies each property's ? and readonly unless you add or remove them.

Pick walks only some keys. It takes a second type parameter, K, for the keys to keep, and K extends keyof T says they must be real keys of T (the keyof & typeof lesson covers keyof). A wrong key is an error, on purpose here:

Pick, written by hand (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type MyPick<T, K extends keyof T> = { [Key in K]: T[Key] }; type Profile = { name: string; email: string; age: number };type Contact = MyPick<Profile, "name" | "email">;//   ^?type Phone = MyPick<Profile, "phone">;

Line 1 walks Key in K instead of every key, and keeps each type with T[Key]. Line 4 keeps the name and the email, and the query on line 5 shows just those two. Line 6 asks for a phone. TypeScript says Type '"phone"' does not satisfy the constraint 'keyof Profile': phone isn't one of the profile's keys, so it can't be picked.

Omit is built from other helpers: pick every key except the ones you name. Exclude, from the ReturnType, Awaited & more lesson, takes them out of the union of keys. Writing it yourself lets you make it stricter than the built-in one, on purpose here:

Omit, written by hand and stricter (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; type User = { id: number; name: string; email: string };type PublicUser = MyOmit<User, "email">;//   ^?type Typo = MyOmit<User, "emial">;type BuiltInTypo = Omit<User, "emial">;

Line 1 picks the keys of T that are left after excluding K. Line 4 leaves out the email, and the query on line 5 shows the id and name. Line 6 has a typo, "emial", and gets the same error as MyPick, because MyOmit says K extends keyof T. Line 7 makes the same typo with the built-in Omit, and there is no error: its K only has to be some key name, not one of User's.

The table below shows how TypeScript's library writes each helper. Read each row as: the helper, its recipe, and what it does.

How the built-in object helpers are written
The helperHow lib.es5.d.ts writes itWhat it does
Partial<T>{ [P in keyof T]?: T[P] }Adds a ? to every property.
Required<T>{ [P in keyof T]-?: T[P] }Takes every ? away.
Readonly<T>{ readonly [P in keyof T]: T[P] }Makes every property read-only.
Pick<T, K extends keyof T>{ [P in K]: T[P] }Walks only the keys in K.
Omit<T, K extends keyof any>Pick<T, Exclude<keyof T, K>>Picks every key except the ones in K.

Helpers TypeScript doesn't have

Once you can read the recipes, you can write helpers for your own app. A common one: a new user has no id or createdAt yet, because the server adds them. So those two keys should be optional, and the rest required:

A helper that makes only some keys optionalPop out in the code editor (opens in a new tab)TypeScript
type User = { id: number; name: string; email: string; createdAt: string }; type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; type NewUser = Optional<User, "id" | "createdAt">;//   ^?const asha: NewUser = { name: "Asha", email: "asha@example.com" };console.log(asha.name);

Line 3 makes Optional from built-in helpers. Omit<T, K> is everything except those keys, required as before. Partial<Pick<T, K>> is just those keys, made optional. The & joins the two into one type with all the properties (the Intersection types lesson covers &). Line 7 makes a user without an id, and line 8 prints Asha.

It works, but look at the query on line 6. TypeScript shows the recipe, not the result. A later section fixes that. First, one more kind of helper: one that uses itself.

Types that refer to themselves

Some data has the same shape at every level: a menu whose items have their own items, or comments with replies. A type for it can mention itself. That's called a recursive type, like a recursive function, which calls itself (the Recursion lesson covers those):

A menu item whose children are menu itemsPop out in the code editor (opens in a new tab)TypeScript
type MenuItem = { label: string; children: MenuItem[] }; const menu: MenuItem = {  label: "Shop",  children: [    { label: "Tea", children: [] },    { label: "Cakes", children: [{ label: "Birthday", children: [] }] },  ],};console.log(menu.children[1].children[0].label);

Line 1 says a MenuItem has a label and a list of children, and each child is a MenuItem too. Lines 3 to 9 are a shop menu three levels deep, and the one type fits every level. A child with no children of its own has an empty list. Line 10 reads the second child's first child and prints Birthday.

A big crate labelled Shop holds two crates, Tea and Cakes. The Cakes crate holds one more crate, Birthday. Every crate is a MenuItem.TeaBirthdayCakesShopMenuItem inside MenuItem
Every crate is a MenuItem: a label, and children that are MenuItems too. The type is written once, and it fits a menu of any depth.

A recursive type can also be a union. Here is a type for any value JSON.stringify can save and load again (the Typing fetch & JSON lesson works with JSON). Line 4 is an error on purpose:

A type for JSON values (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; const reply: Json = { user: "Asha", tags: ["tea", 3], seen: true };const withDate: Json = { when: new Date() };

Line 1 says a JSON value is text, a number, true or false, null, a list of JSON values, or an object whose values are JSON values. Line 3 nests a list inside an object, and it fits. Line 4 puts a Date inside, and TypeScript says Type '{ when: Date; }' is not assignable to type 'Json': a date isn't one of the kinds of value JSON can hold.

The self-reference has to sit inside something, like an object property or a list, so TypeScript can look at one level at a time. A type that is directly itself is an error, on purpose here:

A type that is itself (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Price = Price | number;

To work out Price, TypeScript would first need to know Price. It says Type alias 'Price' circularly references itself. You met the other kind of recursive type, a conditional type that calls itself, as UnwrapAll in the infer keyword lesson.

DeepPartial and DeepReadonly

Partial and Readonly only change the top level of an object. A settings object with groups inside needs a helper that goes into every level. Combine a mapped type, a conditional type, and recursion:

DeepPartial, applied to one groupPop out in the code editor (opens in a new tab)TypeScript
type DeepPartial<T> = {  [Key in keyof T]?: T[Key] extends object ? DeepPartial<T[Key]> : T[Key];}; type Settings = {  theme: { mode: "light" | "dark"; accent: string };  alerts: { email: boolean; push: boolean };}; type ThemeChange = DeepPartial<Settings["theme"]>;//   ^?

Line 2 does the work. For each key, it makes the property optional, then asks a question about its type: T[Key] extends object, "is this property an object?" If yes, the answer is DeepPartial<T[Key]>: the same helper again, one level down. If no, like a string or a boolean, the type stays as it is, and the recursion stops there.

Lines 5 to 8 describe settings with two groups. Line 10 applies the helper to the theme group, and the query on line 11 shows that the group's own properties are optional now. Press the buttons to compare it with Partial on a whole change:

Playground: Partial or DeepPartial
A settings change, typed two waysPop out in the code editor (opens in a new tab)TypeScript
type DeepPartial<T> = {  [Key in keyof T]?: T[Key] extends object ? DeepPartial<T[Key]> : T[Key];}; type Settings = {  theme: { mode: "light" | "dark"; accent: string };  alerts: { email: boolean; push: boolean };}; const change: Partial<Settings> = { theme: { mode: "dark" } };console.log(JSON.stringify(change));
Console output{"theme":{"mode":"dark"}}
What happens
HelperPartial
TypeScript1 error: line 10
Try it yourself

Partial makes only theme optional. Once you give a theme, it must be a whole one: Property 'accent' is missing in type '{ mode: "dark"; }'.

TypeScript checks the code for real, and the output is what the code prints. Each button changes only line 10. Reset goes back to Partial.

With Partial, line 10 gets an error, because a theme you give must still have every property. With DeepPartial, you can give just the part that changed. What you do give is still checked: "blue" isn't a mode.

Left, Partial<Settings>: a crate labelled theme? holds the cards mode and accent, with no question marks. Right, DeepPartial<Settings>: a crate labelled theme? holds mode? and accent?.Partial<Settings>modeaccenttheme?a theme must be wholeDeepPartial<Settings>mode?accent?theme?every level optional
Both make theme optional. Only DeepPartial also goes inside it, so mode and accent become optional too.

DeepReadonly is the same shape with readonly instead of ?. Line 8 and line 9 are errors on purpose:

DeepReadonly locks every level (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type DeepReadonly<T> = {  readonly [Key in keyof T]: T[Key] extends object ? DeepReadonly<T[Key]> : T[Key];}; type Settings = { theme: { mode: "light" | "dark" }; tags: string[] }; const settings: DeepReadonly<Settings> = { theme: { mode: "light" }, tags: ["new"] };settings.theme.mode = "dark";settings.tags.push("sale");console.log(settings.theme.mode, settings.tags);

With the built-in Readonly, line 8 would be allowed: it only locks the top level (the Mapped types lesson shows this). With DeepReadonly, TypeScript says Cannot assign to 'mode' because it is a read-only property. It even goes into the list: a mapped type over an array type makes a read-only array, so line 9 gets Property 'push' does not exist on type 'readonly string[]'. Like every type, it doesn't freeze anything while the page runs: line 10 still prints dark [ 'new', 'sale' ].

Keeping types readable

A helper is only useful if people can read what it makes. The Optional helper showed its recipe in the hover, not its result. The plain copy recipe from the playground fixes that:

Flatten a helper's result for the hoverPop out in the code editor (opens in a new tab)TypeScript
type User = { id: number; name: string; email: string; createdAt: string }; type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;type Prettify<T> = { [Key in keyof T]: T[Key] }; type NewUser = Optional<User, "id" | "createdAt">;//   ^?type NewUserFlat = Prettify<NewUser>;//   ^?

Line 4 makes Prettify, a mapped type that copies every key with its type and modifiers. It changes nothing about which values fit. But to show a mapped type, TypeScript has to list its keys, so the query on line 9 shows one flat object: the name and the email required, the id and createdAt optional. The query on line 7, without it, shows the recipe. You can also put it inside the helper, so every use is flat: Prettify<Omit<T, K> & Partial<Pick<T, K>>>.

A few habits keep your own helpers easy to read and to trust:

  • Reach for a built-in helper first. Write your own only when none fits.
  • Give each helper a plain name that says what it makes, like Optional or DeepPartial, and type parameter names that say what they hold, like Key.
  • Build big helpers from small ones, as Optional uses Omit, Pick, and Partial.
  • Check each helper with a // ^? line on a real type, and try one value that should fail.
  • Only go deep when your data is deep. A recursive helper makes every hover and error message longer.

Saving part of a nested settings object

A settings page saves only what the reader changed: maybe the theme's mode and one alert. DeepPartial types that change, and the function fills in the rest from the saved settings:

Merge a deep change into saved settingsPop out in the code editor (opens in a new tab)TypeScript
type DeepPartial<T> = {  [Key in keyof T]?: T[Key] extends object ? DeepPartial<T[Key]> : T[Key];}; type Settings = {  theme: { mode: "light" | "dark"; accent: string };  alerts: { email: boolean; push: boolean };}; function updateSettings(current: Settings, change: DeepPartial<Settings>): Settings {  return {    theme: { ...current.theme, ...change.theme },    alerts: { ...current.alerts, ...change.alerts },  };} const saved: Settings = {  theme: { mode: "light", accent: "orange" },  alerts: { email: true, push: true },};const updated = updateSettings(saved, { theme: { mode: "dark" }, alerts: { push: false } });console.log(updated.theme.mode, updated.theme.accent, updated.alerts.push);

Lines 1 to 8 are the helper and the settings from before. Line 10 says updateSettings takes the current settings and a change of type DeepPartial<Settings>, and gives back whole Settings. Lines 12 and 13 copy each group and then lay the changed parts over it with the spread syntax ... (the Spread and rest lesson covers it). Lines 17 to 20 are the saved settings. Line 21 changes only the mode and push alerts, and line 22 prints dark orange false: the accent was kept.

Error or fine?
  • type P<T> = { [K in keyof T]?: T[K] };
  • type P<T> = { [K in T]: string };
  • type Pk<T, K> = { [P in K]: T[P] };
  • type Pk<T, K extends keyof T> = { [P in K]: T[P] };
  • type Price = Price | number;
  • type Tree = { value: number; children: Tree[] };
  • type Nested = Nested[];
  • type Box<T> = { readonly [K in keyof T]: Box<T[K]> };
Try it yourself
0 of 8 correct

Sort each helper 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

T[Key] extends object is true for more than plain objects. Arrays are objects, and so are functions. For arrays that works out, as you saw. For a function, it doesn't, and line 15 is an error on purpose:

A deep helper walks into a function (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type DeepReadonly<T> = {  readonly [Key in keyof T]: T[Key] extends object ? DeepReadonly<T[Key]> : T[Key];}; type Cart = {  items: { name: string; price: number }[];  checkout: (total: number) => string;}; function lock(cart: Cart): DeepReadonly<Cart> {  return cart;} const cart = lock({ items: [{ name: "Tea", price: 3 }], checkout: (total) => `Paid ${total}` });console.log(cart.checkout(3));

Line 7 gives the cart a checkout function. DeepReadonly sees an object and turns it into a mapped type, and a mapped type can't be called. Line 15 gets This expression is not callable. The code still runs and prints Paid 3: only the type is wrong. The fix is to check for a function first and leave it as it is:

DeepReadonly that leaves functions alonePop out in the code editor (opens in a new tab)TypeScript
type DeepReadonly<T> = {  readonly [Key in keyof T]: T[Key] extends (...args: never[]) => unknown    ? T[Key]    : T[Key] extends object      ? DeepReadonly<T[Key]>      : T[Key];}; type Cart = {  items: { name: string; price: number }[];  checkout: (total: number) => string;}; function lock(cart: Cart): DeepReadonly<Cart> {  return cart;} const cart = lock({ items: [{ name: "Tea", price: 3 }], checkout: (total) => `Paid ${total}` });console.log(cart.checkout(3));

Line 2 asks first whether the property is a function: (...args: never[]) => unknown is a type every function fits. If it is, line 3 keeps it. Only otherwise do lines 4 to 6 go deeper. Now line 19 type checks and prints Paid 3.

  • "Utility types are built into the compiler." No. Partial, Pick, Omit, and friends are type aliases in a library file. You can read them and write your own. Only a few, like the Uppercase from Template literal types, are built into the compiler.
  • "A mapped type always drops ? and readonly." No. Walking keyof T keeps each property's modifiers unless you write -? or -readonly.
  • "Pick<T, K> works without K extends keyof T." No. Without it, K could be anything, and T[Key] might not exist.
  • "Partial makes nested objects optional too." No. It changes only the top level. Use a recursive DeepPartial.
  • "A type can refer to itself any way it likes." No. The self-reference must sit inside an object, a list, or a conditional type, not be the whole type.
  • "A deep helper is safe on any object." No. Functions are objects too. Check for them first.
  • "Prettify changes the type." No. It changes only how the type is shown.

The table below sets the built-in helpers beside the deep ones. Read each row as: the helper, how far it goes, and what that means.

Top-level helpers and deep helpers
The helperHow far it goesWhat that means
Partial<Settings>Only the top-level properties.A theme you give must still be a whole theme.
DeepPartial<Settings>Every level, all the way down.A theme with only a mode is fine.
Readonly<Settings>Only the top-level properties.settings.theme.mode = "dark" is allowed.
DeepReadonly<Settings>Every level, including arrays.settings.theme.mode = "dark" is an error.

Practice exercises

Each exercise is TypeScript. Press Edit & run on the starter code: the errors and ^? answers update as you type. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead what a helper makes

What type does title have in TodoDraft? Guess first. Then press Edit & run and add a // ^? line under line 4 to check.

Starter codePop out in the code editor (opens in a new tab)TypeScript
type Nullable<T> = { [Key in keyof T]: T[Key] | null }; type Todo = { title: string; done: boolean };type TodoDraft = Nullable<Todo>;

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

    Exercise 2 · Warm-upFind the line TypeScript marks

    Two lines use a hand-written MyPick. Which line will TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type MyPick<T, K extends keyof T> = { [Key in K]: T[Key] };
    
    type Todo = { title: string; done: boolean };
    type Summary = MyPick<Todo, "title">;
    type Planned = MyPick<Todo, "title" | "due">;

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

      Exercise 3 · PracticeWrite your own Readonly

      Line 5 changes a todo, and TypeScript allows it. Change line 1 so MyReadonly works like Readonly and line 5 is marked.

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type MyReadonly<T> = { [Key in keyof T]: T[Key] }; type Todo = { title: string; done: boolean };const todo: MyReadonly<Todo> = { title: "Buy tea", done: false };todo.done = true;console.log(todo.done);

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

        Exercise 4 · PracticeWrite a ValueOf helper

        ValueOf<Settings> should be the type of any one setting's value. Replace unknown on line 1, so line 4, which stores true, is marked.

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type ValueOf<T> = unknown; type Settings = { theme: "light" | "dark"; fontSize: number };const value: ValueOf<Settings> = true;

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

          Exercise 5 · ChallengeMake Readonly go deep

          Line 7 moves the order to another city, and TypeScript allows it. Change the type after the colon on line 2 so every level is read-only.

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type DeepReadonly<T> = {  readonly [Key in keyof T]: T[Key];}; type Order = { id: number; address: { city: string } };const order: DeepReadonly<Order> = { id: 7, address: { city: "Pune" } };order.address.city = "Goa";console.log(order.address.city);

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

            Exercise 6 · ChallengeType a comment thread

            Line 4 gets Object is of type 'unknown', because TypeScript doesn't know what a reply is. Change the type of replies on line 1 so each reply is a comment with replies of its own.

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Comment = { text: string; replies: unknown[] }; const thread: Comment = { text: "Love this tea", replies: [{ text: "Me too", replies: [] }] };console.log(thread.replies[0].text);

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

              Check your understanding

              For each question, ask: which keys does the helper walk, and what does it do to each property? The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The writing utility types quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What is Partial in TypeScript's own library?

                Choose an answer to see the explanation.

              2. Question 2 of 7With this helper, what is the type of age in Full?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Helper<T> = { [Key in keyof T]-?: T[Key] };
                
                type Profile = { name: string; age?: number };
                type Full = Helper<Profile>;

                Choose an answer to see the explanation.

              3. Question 3 of 7Why does TypeScript mark line 4 of this MyPick code?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type MyPick<T, K extends keyof T> = { [Key in K]: T[Key] };
                
                type Profile = { name: string; email: string };
                type Phone = MyPick<Profile, "phone">;

                Choose an answer to see the explanation.

              4. Question 4 of 7Which of these types refers to itself in a way TypeScript accepts?

                Choose an answer to see the explanation.

              5. Question 5 of 7Both lines give a theme with only a mode. Which line does TypeScript mark?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type DeepPartial<T> = {
                  [Key in keyof T]?: T[Key] extends object ? DeepPartial<T[Key]> : T[Key];
                };
                type Settings = { theme: { mode: "light" | "dark"; accent: string } };
                
                const a: Partial<Settings> = { theme: { mode: "dark" } };
                const b: DeepPartial<Settings> = { theme: { mode: "dark" } };

                Choose an answer to see the explanation.

              6. Question 6 of 7What does TypeScript say about the call on line 8?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type DeepReadonly<T> = {
                  readonly [Key in keyof T]: T[Key] extends object ? DeepReadonly<T[Key]> : T[Key];
                };
                
                type Cart = { checkout: (total: number) => string };
                const plain: Cart = { checkout: (total) => `Paid ${total}` };
                const cart: DeepReadonly<Cart> = plain;
                cart.checkout(3);

                Choose an answer to see the explanation.

              7. Question 7 of 7What does Prettify<T> = { [Key in keyof T]: T[Key] } change about a type?

                Choose an answer to see the explanation.

              Key takeaways

              • Utility types are type aliases with type parameters. Partial is { [P in keyof T]?: T[P] } in TypeScript's library.
              • Most object helpers are one mapped type with one change: ?, -?, readonly, or a new value type. Walking keyof T keeps each property's modifiers.
              • K extends keyof T keeps a helper's keys real, which makes your own Omit stricter than the built-in one.
              • A recursive type refers to itself inside an object, a list, or a conditional type, to describe trees and nested data.
              • DeepPartial and DeepReadonly call themselves for each property that is an object. Check for functions first.
              • Prettify, a plain copy mapped type, makes a hover show the result instead of the recipe.

              Remember the one-liner.
              A utility type is a recipe: walk the keys, change each property, and call it again for the next level.

              Coming next: Modules & type imports. It shares types between files with import and export, so a helper like DeepPartial is written once for the whole app.

              CompleteFrontend Clear concepts. Working examples.