cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

The infer keyword

Use infer in a conditional type to pull one type out of another: a list's item, a function's return and parameters, a promise's value, with constraints.

By the end, you can
  • 01
    Read an infer patternExplain how T extends (infer Item)[] ? Item : never lines up a pattern with a type and names the part under the blank.
  • 02
    Pull types out of functions and promisesWrite helpers that find a function's return type, its parameters, and the value a promise gives, including a promise inside a promise.
  • 03
    Add a constraint to inferUse infer First extends string so the found type can go where only text is allowed, and say what happens when it doesn't fit.

Pull the item type out of a cart

Your code often has a big type and needs one part of it. A library gives you the type of a whole cart, a list of items, and you want the type of one item. A function returns an object, and you want that object's type. TypeScript can pull the part out for you, so you never write the same type twice. The keyword that does it is infer:

The type of one item in a cartPop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends (infer Item)[] ? Item : never; type Cart = { name: string; price: number }[];type CartItem = ItemOf<Cart>;//   ^?const tea: CartItem = { name: "Tea", price: 3 };console.log(tea.name);

Line 1 is a conditional type, from the Conditional types lesson: A extends B ? X : Y means "if type A fits B, the result is X, otherwise Y". The new part is infer Item. Read line 1 as a question with a blank in it:

  • Is List a list of something, (…)[]?
  • If yes, call that something Item, and the result is Item.
  • If no, the result is never, the type that has no values (any, unknown & never covers it).

infer Item is the blank. It tells TypeScript: "work out what goes here, and call it Item".

Line 3 is the cart type: a list ([]) of objects with a name and a price. Line 4 asks ItemOf for the type of one item, and the query on line 5 shows the answer: { name: string; price: number }. You never wrote that type a second time. Line 6 uses it for the tea, and line 7 prints Tea.

Top row, the type Cart: a card { name: string; price: number } followed by a card []. Bottom row, the pattern: a card infer Item followed by a card []. The two [] cards line up, and an arrow from infer Item points up at the item type. Underneath: CartItem = { name: string; price: number }.Cart{ name: string; price: number }[]Item names this partpatterninfer Item[][] and []matchCartItem = { name: string; price: number }
TypeScript lines the pattern up with Cart. Both end in [], so they match, and Item names the part under the blank. Line 4 gives that part a name of its own: CartItem.
Real-life analogyReading the blank on a parcel label

A parcel arrives with a printed label: "Contents: ____". Someone wrote "mugs" in the blank. You don't open the box. You know the printed words, so you read whatever was written in the blank. A parcel without that label tells you nothing, so you do something else with it.

In real life: The printed label: "Contents: ____"
In TypeScript: The pattern (infer Item)[]
In real life: The blank on the label
In TypeScript: infer Item
In real life: Reading "mugs" from the blank
In TypeScript: Item becomes the item type
In real life: A parcel with no such label
In TypeScript: A type that doesn't fit the pattern: the false branch
The main idea

infer puts a named blank in the pattern of a conditional type. TypeScript lines the pattern up with your type. If it fits, the blank's name holds the type that sits there, and you can use it in the true branch. If it doesn't fit, you get the false branch.

How infer names part of a type

Why does the blank need a keyword? Try writing the pattern without it. TypeScript reports two errors, on purpose here:

A pattern without infer (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends Item[] ? Item : never;

Cannot find name 'Item' means TypeScript has never heard of a type called Item. It reports it twice: once in the pattern Item[], and once in the true branch ? Item. Every type name has to be made somewhere, for example with type Item = …, as a type parameter in <…>, or with infer. Here is what each part of the working version does:

  • List extends … asks: does the type in List fit the pattern?
  • (infer Item)[] is the pattern: a list of something. infer Item makes a new type name, Item, for whatever stands in that spot. The round brackets keep infer Item together; leave them out and TypeScript can't read the line.
  • ? Item is the true branch: if the pattern fits, the result is the type TypeScript found.
  • : never is the false branch: the result when the pattern doesn't fit.

Working out the type in the blank is called inference, the same word as when TypeScript works out that let total = 3 is a number (Type annotations & inference). That's where the keyword's name comes from. Now try it: type a different list type on line 3, and watch the answer after ^? on line 5 and the errors under the code.

Playground: type the list's type
Tags and the type of one tagPop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends (infer Item)[] ? Item : never; type Tags = string[];type Tag = ItemOf<Tags>;//   ^?const firstTag: Tag = "sale";console.log(firstTag);
Line 3 saysstring[]
What TypeScript finds
Tagstring
ErrorsNo errors
Try it yourself

string[] is a list of strings, so the pattern matches and Item is string. Line 6 stores the text "sale", which is a string, so there are no errors.

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

With number[], Tag becomes number, so storing "sale" on line 6 is an error. With (string | number)[] it's fine again. A plain string isn't a list at all: the pattern doesn't fit, ItemOf gives never, and nothing can be stored in a never. The same happens with readonly string[], a list you can't change, because the pattern describes a list you can change. Arrays and tuples covers read-only lists.

The name infer makes only lives in the true branch. Using it in the false branch is an error, on purpose here:

Item used in the false branch (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends (infer Item)[] ? Item : Item;

The error is Cannot find name 'Item' again, this time once, for the last Item. When the pattern doesn't fit, there is nothing in the blank, so there is no Item to give back.

Return and parameter types of a function

A pattern can be any type with a blank in it, including a function type. A function type looks like an arrow function with types instead of code: (price: number) => string takes a number and returns a string (Function types covers them). Put the blank where the return type goes:

The type a function returnsPop out in the code editor (opens in a new tab)TypeScript
type ReturnOf<Fn> = Fn extends (...args: any[]) => infer Result ? Result : never; function getUser() {  return { name: "Asha", cartItems: 2 };} type User = ReturnOf<typeof getUser>;//   ^?const user: User = { name: "Ravi", cartItems: 0 };console.log(user.name);

Line 1's pattern is (...args: any[]) => infer Result. The part before the arrow, ...args: any[], is a rest parameter that accepts any arguments at all, so every function fits. It has to be any[] here, not unknown[]: a function that only takes a string can't take every value, so it wouldn't fit (...args: unknown[]). The blank sits after the arrow, where the return type goes.

Lines 3 to 5 are an ordinary function that returns a user. On line 7, typeof getUser gives the function's type. In a type, typeof followed by a value's name means "the type of this value" (keyof & typeof covers it); here that is () => { name: string; cartItems: number }. The query on line 8 shows what ReturnOf pulls out: { name: string; cartItems: number }. Line 9 makes another user of that type, and line 10 prints Ravi. When getUser changes, User changes with it.

Move the blank before the arrow, and you get the parameters instead:

The parameters a function takesPop out in the code editor (opens in a new tab)TypeScript
type ParamsOf<Fn> = Fn extends (...args: infer Params) => any ? Params : never; function searchProducts(query: string, page: number) {  return `${query} on page ${page}`;} type SearchArgs = ParamsOf<typeof searchProducts>;//   ^?const lastSearch: SearchArgs = ["tea", 2];console.log(searchProducts(...lastSearch));

Line 1 puts the blank where ...args goes. A rest parameter collects every argument into one list, so Params is all of the parameters together, as a tuple: a list with a fixed length and a type for each place. The query on line 8 shows [query: string, page: number]. The words query: and page: are labels for each place; they don't change the type. Line 9 saves a search as a tuple, and line 10 spreads it back into a call and prints tea on page 2.

Top row, the type of searchProducts: (query: string, page: number), an arrow sign, and string. Middle row, ParamsOf's pattern: (...args: infer Params), an arrow sign, and any. Bottom row, ReturnOf's pattern: (...args: any[]), an arrow sign, and infer Result. Underneath: Params is [query: string, page: number] and Result is string.function(query: string, page: number)=>stringParamsOf(...args: infer Params)=>anyReturnOf(...args: any[])=>infer ResultParams = [query: string, page: number]Result = string
The type of searchProducts has two parts: its parameters and what it returns. ParamsOf puts its blank over the parameters and gets a tuple. ReturnOf puts its blank over the return type and gets string.

A pattern can also pick out one parameter. Here is the type of the event a click handler takes:

The first parameter of a click handlerPop out in the code editor (opens in a new tab)TypeScript
type FirstParam<Fn> = Fn extends (first: infer First, ...rest: any[]) => any ? First : never; function onCartClick(event: MouseEvent, productId: number) {  console.log(productId);} type ClickEvent = FirstParam<typeof onCartClick>;//   ^?

Line 1's pattern has two parameters: first: infer First catches the first one, and ...rest: any[] accepts any others. Lines 3 to 5 are a click handler that takes the browser's MouseEvent and a product id. The query on line 8 shows ClickEvent is MouseEvent.

TypeScript has these built in

TypeScript comes with helpers that work this way: ReturnType<Fn>, Parameters<Fn>, and Awaited<T> for promises. They are written with infer too. In your projects, use them. The lesson ReturnType, Awaited & more covers them; writing your own here shows how they work.

Unwrapping promises and lists

An async function always returns a promise, even when its code returns a plain object. So its return type is wrapped in Promise<…>. To reach the value inside, put the blank inside the promise:

The value inside a promisePop out in the code editor (opens in a new tab)TypeScript
type ReturnOf<Fn> = Fn extends (...args: any[]) => infer Result ? Result : never;type Unwrap<T> = T extends Promise<infer Value> ? Value : T; async function fetchUser() {  return { name: "Asha", cartItems: 2 };} type Reply = ReturnOf<typeof fetchUser>;//   ^?type User = Unwrap<Reply>;//   ^?

Line 1 is ReturnOf from before. Line 2 is new: its pattern is Promise<infer Value>, a promise of something. Lines 4 to 6 are an async function that returns a user. Line 8 asks for its return type, and the query on line 9 shows it's a promise: Promise<{ name: string; cartItems: number }>. Line 10 unwraps it, and the query on line 11 shows the user object itself.

Look at Unwrap's false branch: it's T, not never. When T isn't a promise, you get it back unchanged. You choose what the false branch gives. Try it with different replies:

Playground: what the reply holds
Unwrapping a reply typePop out in the code editor (opens in a new tab)TypeScript
type Unwrap<T> = T extends Promise<infer Value> ? Value : T; type Reply = Promise<string>;type Name = Unwrap<Reply>;//   ^?const name: Name = "Asha";console.log(name);
Name isstring
What happens
ReplyPromise<string>
TypeScriptNo errors
When it runsprints Asha
Try it yourself

Promise<string> fits Promise<infer Value>, so Value is string and Name is string. Line 6 stores "Asha", so there are no errors.

TypeScript checks the code for real each time. Each button changes only line 3. Reset goes back to A promise of text.

A promise of text gives string. Plain text gives string too, from the false branch. A promise of a number gives number, so line 6 is an error. A promise in a promise gives Promise<string>: Unwrap opened one layer and stopped. When the code runs, the types are gone, so it prints Asha every time.

To open every layer, let the true branch call Unwrap again on what it found:

Unwrapping every layerPop out in the code editor (opens in a new tab)TypeScript
type UnwrapAll<T> = T extends Promise<infer Value> ? UnwrapAll<Value> : T; type Reply = Promise<Promise<string>>;type Name = UnwrapAll<Reply>;//   ^?const name: Name = "Asha";console.log(name);

Line 1's true branch is UnwrapAll<Value>: it unwraps what it found, again and again, until the false branch gives back something that isn't a promise. A type that uses itself like this is called recursive. Line 3 is a promise in a promise, and the query on line 5 shows string. Line 6 stores "Asha" without an error, and line 7 prints it.

Two crates, one inside the other, both labelled Promise. The inner crate holds a card that says string. Beside them: Unwrap opens one crate and gives Promise<string>. UnwrapAll opens every crate and gives string.Promise<Promise<string>>stringPromisePromiseUnwrapopens one crate:Promise<string>UnwrapAllopens every crate:string
Promise<Promise<string>> is a promise inside a promise. Unwrap opens the outer one and stops, so you get Promise<string>. UnwrapAll calls itself on what it finds, until no promise is left: string.

Lists work the same way. ItemOf unwraps a list. For a tuple, a pattern can name one place, like the first or the last. A breadcrumb trail at the top of a shop page is a good example:

The first and last page of a breadcrumb trailPop out in the code editor (opens in a new tab)TypeScript
type FirstOf<List> = List extends [infer First, ...unknown[]] ? First : never;type LastOf<List> = List extends [...unknown[], infer Last] ? Last : never; type Trail = ["Home", "Shop", "Tea"];type Start = FirstOf<Trail>;//   ^?type Here = LastOf<Trail>;//   ^?type Nothing = FirstOf<[]>;//   ^?

In a tuple pattern, ...unknown[] means "any number of other items, of any type". unknown is the type every value fits. Line 1 puts the blank first and the rest after it. Line 2 puts the rest first and the blank last. Line 4 is the trail. The query on line 6 shows "Home", the first page, and the query on line 8 shows "Tea", the last. Line 9 tries an empty tuple, which has no first item, so the query on line 10 shows never.

The table below collects the patterns so far. Read each row as: this pattern, the types it matches, and what its blank holds.

Five patterns and what they pull out
The patternWhat it matchesWhat the blank holds
(infer Item)[]A list, like string[] or CartItem[].The type of one item: string, CartItem.
Promise<infer Value>A promise, like the reply of an async function.The value the promise gives when it's done.
(...args: any[]) => infer ResultAny function.What the function returns.
(...args: infer Params) => anyAny function.Its parameters, as a tuple like [query: string, page: number].
[infer First, ...unknown[]]A tuple with at least one item.The type of the first item.

infer with a constraint

Sometimes the type you find has to go somewhere picky. Here, PageTitle only takes text, and you want to give it the first page of a trail. TypeScript reports an error, on purpose here:

A found type that might not be text (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type PageTitle<Name extends string> = { title: Name; path: string }; type FirstPage<Trail> = Trail extends [infer First, ...unknown[]]  ? PageTitle<First>  : never;

Line 1 says PageTitle's Name must be a string: Name extends string is a constraint, as in Generic constraints. Lines 3 to 5 find the first item of a trail and pass it to PageTitle. Line 4 gets Type 'First' does not satisfy the constraint 'string'. It means: PageTitle only takes text, and nothing promises that First is text. A trail's first item could be any type.

The fix is a constraint on the blank itself: infer First extends string.

A blank that only takes textPop out in the code editor (opens in a new tab)TypeScript
type PageTitle<Name extends string> = { title: Name; path: string }; type FirstPage<Trail> = Trail extends [infer First extends string, ...unknown[]]  ? PageTitle<First>  : never; type Home = FirstPage<["Home", "Shop"]>;//   ^?type Missing = FirstPage<[404, "Shop"]>;//   ^?const home: Home = { title: "Home", path: "/" };console.log(home.title);

The constraint on line 3 does two jobs:

  • In the true branch, First is known to be a string, so line 4 may pass it to PageTitle.
  • When the first item isn't text, the pattern doesn't fit, and you get the false branch.

The query on line 8 shows Home is { title: "Home"; path: string }. Line 9 tries a trail that starts with the number 404, and the query on line 10 shows never. Line 11 makes a home page of the Home type, and line 12 prints Home.

Two trails meet a gate that says First extends string. The trail ["Home", "Shop"] starts with text, so it passes and First is "Home". The trail [404, "Shop"] starts with a number, so there is no match and the result is never.["Home", "Shop"][404, "Shop"]infer Firstextends stringFirst = "Home"no match: never
The constraint is a second check on the blank. "Home" is text, so it passes and First is "Home". 404 is a number, so the pattern doesn't match and FirstPage gives never.

The constraint can be any type, like a union. Here it accepts an id that is a string or a number:

An id that must be a string or a numberPop out in the code editor (opens in a new tab)TypeScript
type IdOf<T> = T extends { id: infer Id extends string | number } ? Id : never; type ProductId = IdOf<{ id: number; name: string }>;//   ^?type OrderId = IdOf<{ id: "A-7"; total: number }>;//   ^?type FlagId = IdOf<{ id: boolean }>;//   ^?

Line 1's pattern is an object with an id. Objects with more properties fit too, as long as they have an id. Line 3's product has a number id, and the query on line 4 shows number. Line 5's order has the id "A-7", and the query on line 6 shows exactly "A-7", a literal type. Line 7's id is a boolean, which the constraint turns away, so the query on line 8 shows never.

Types from an API helper

Here is how infer helps in a real app. A fetchCart function stands in for a call to the server, and its reply is the only place the cart's shape is written. The rest of the page works its types out from it. Press the buttons to change what fetchCart returns on line 6:

Playground: change what fetchCart returns
Types worked out from an API helperPop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends (infer Item)[] ? Item : never;type Unwrap<T> = T extends Promise<infer Value> ? Value : T;type ReturnOf<Fn> = Fn extends (...args: any[]) => infer Result ? Result : never; async function fetchCart() {  return { items: [{ name: "Tea", price: 3 }], total: 3 };} type CartReply = Unwrap<ReturnOf<typeof fetchCart>>;type CartLine = ItemOf<CartReply["items"]>;//   ^? function showLine(line: CartLine) {  return `${line.name}: $${line.price}`;}console.log(showLine({ name: "Cake", price: 4 }));
Console outputCake: $4
What TypeScript finds
CartLine{ name: string; price: number }
ErrorsNo errors
Try it yourself

CartLine is { name: string; price: number }, worked out from line 6. Line 16 passes a cake with the number 4, which fits, so there are no errors and it prints Cake: $4.

TypeScript checks the code for real each time. Each button changes only line 6. Reset goes back to Price as a number.

Lines 1 to 3 are the three helpers from this lesson. Lines 5 to 7 are fetchCart. Read line 9 from the inside out: typeof fetchCart is the function's type, ReturnOf gives its promise, and Unwrap opens the promise. On line 10, CartReply["items"] is the type of the reply's items property (the Indexed access types lesson covers this), and ItemOf gives one item. The query on line 11 shows CartLine. Lines 13 to 15 show one line of the cart, and line 16 prints Cake: $4.

With Price as text, CartLine's price becomes a string, and line 16, which passes a number, gets an error. With Add a size, CartLine gets a size, and line 16's cake has none. You changed one line, and TypeScript pointed at the one place that no longer matches. Without infer, you would have written the item type by hand, and it would have quietly gone out of date.

Now sort some types. Each card uses one of these helpers. Every helper gives never when its pattern doesn't match:

The helpers the cards usePop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends (infer Item)[] ? Item : never;type ReturnOf<Fn> = Fn extends (...args: any[]) => infer Result ? Result : never;type FirstOf<List> = List extends [infer First, ...unknown[]] ? First : never;type FirstText<List> = List extends [infer First extends string, ...unknown[]] ? First : never;
Does the pattern match?
  • ItemOf<string[]>
  • ItemOf<"tea">
  • ReturnOf<() => number>
  • ReturnOf<{ total: 7 }>
  • FirstOf<["Home", "Shop"]>
  • FirstOf<[]>
  • FirstText<[3, "tea"]>
  • ItemOf<[string, number]>
Try it yourself
0 of 8 correct

Sort each type by whether its helper's pattern matches, or the helper takes its false branch and gives never.

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

Common mix-ups

infer only works inside the pattern of a conditional type. Here it is on its own, an error on purpose:

infer outside a conditional type (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Cart = { name: string; price: number }[];type CartItem = infer Item;

Line 2 gets 'infer' declarations are only permitted in the 'extends' clause of a conditional type. It means infer needs a pattern to line up with a type, and only the part after extends is a pattern. Line 2 has no type to compare with, so there is nothing to work out.

A function with overloads, several call signatures for one function, has a surprise too:

infer reads the last overloadPop out in the code editor (opens in a new tab)TypeScript
type ReturnOf<Fn> = Fn extends (...args: any[]) => infer Result ? Result : never; function formatPrice(price: number): string;function formatPrice(price: string): number;function formatPrice(price: number | string) {  return typeof price === "number" ? String(price) : Number(price);} type Formatted = ReturnOf<typeof formatPrice>;//   ^?

Line 3 says a number gives back a string, and line 4 says a string gives back a number. Line 5 is the code that runs. The query on line 10 shows number: when a function has several signatures, infer reads the last one, line 4. It can't pick the signature that fits a call, because there is no call here.

  • "I can write infer anywhere in a type." No. Only after extends in a conditional type. Anywhere else is error 1338.
  • "infer looks at my values while the page runs." No. It works on types, before the code runs. The playgrounds print Asha and Cake: $4 whatever the types say.
  • "When the pattern doesn't match, infer gives unknown." No. You get the false branch, exactly as you wrote it: never in ItemOf, T itself in Unwrap.
  • "(infer Item)[] matches every kind of list." No. A readonly string[] doesn't fit it. Write readonly (infer Item)[] to match both kinds.
  • "infer picks the overload that fits my call." No. It reads the last signature.
  • "I have to write ReturnOf myself in real code." No. TypeScript has ReturnType, Parameters, and Awaited built in. Writing your own shows how they work.

infer isn't the only way to reach part of a type. The table below compares it with reading a property type by name, CartReply["items"] from the API helper. Read each row as a question, then each way's answer.

Reading a property type by name, or with infer
QuestionBy name: CartReply["items"]With infer
How do you say which part you want?By name: the property called items.By its place in a pattern, like a list's item or what a function returns.
Can it reach a function's return type?No. A function type has no property that holds it.Yes: Fn extends (...args: any[]) => infer Result.
What if the part isn't there?An error: the property doesn't exist on that type.No error. The false branch is used, often never.
Where can you write it?In any type.Only after extends in a conditional type.

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 found. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead what ItemOf finds

What type is Price? Guess first. Then point at Price, or add a // ^? line under it.

Starter codePop out in the code editor (opens in a new tab)TypeScript
type ItemOf<List> = List extends (infer Item)[] ? Item : never; type Prices = number[];type Price = ItemOf<Prices>;

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

    Exercise 2 · Warm-upA reply that isn't a promise

    What type is Theme? Read Unwrap's false branch before you answer.

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    type Unwrap<T> = T extends Promise<infer Value> ? Value : T; type Theme = Unwrap<string>;

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

      Exercise 3 · PracticeThe last page of a trail

      Write LastOf so it gives the last page of a breadcrumb trail. What type is Here then?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      // Replace never with a pattern that finds the last item.type LastOf<List> = never; type Here = LastOf<["Home", "Shop", "Tea"]>;

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

        Exercise 4 · PracticeThe event a key handler takes

        FirstParam gives unknown, so line 8 can't read the key property. Change line 1 so it finds the first parameter. What type does it find for onSearch?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type FirstParam<Fn> = Fn extends (...args: any[]) => any ? unknown : never; function onSearch(event: KeyboardEvent, query: string) {  console.log(query);} type SearchEvent = FirstParam<typeof onSearch>;const pressed: SearchEvent["key"] = "Enter";

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

          Exercise 5 · ChallengeA setting that must be text or a number

          Line 3 has an error. Fix it with a constraint on infer. Then, what type is value in FontSize?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type Setting<Value extends string | number> = { value: Value; label: string }; type FirstSetting<List> = List extends [infer First, ...unknown[]] ? Setting<First> : never; type FontSize = FirstSetting<[16, "large"]>;const fontSize: FontSize = { value: 16, label: "Font size" };

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

            Exercise 6 · ChallengeA promise inside a promise

            Line 5 has an error. What type does this Unwrap give for Reply? Then change Unwrap so it opens every layer.

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Unwrap<T> = T extends Promise<infer Value> ? Value : T; type Reply = Promise<Promise<string>>;type Name = Unwrap<Reply>;const name: Name = "Asha";console.log(name);

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

              Check your understanding

              For each question, line the pattern up with the type. Does it fit? If yes, what sits in the blank? If no, what does the false branch say? The quiz code isn't checked on the page, so no error list or type hint gives the answer away.

              The infer keyword quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In List extends (infer Item)[] ? Item : never, what does infer Item do?

                Choose an answer to see the explanation.

              2. Question 2 of 7What type is Flag?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type ItemOf<List> = List extends (infer Item)[] ? Item : never;
                
                type Flag = ItemOf<boolean[]>;

                Choose an answer to see the explanation.

              3. Question 3 of 7string isn't a list. What type is Size?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type ItemOf<List> = List extends (infer Item)[] ? Item : never;
                
                type Size = ItemOf<string>;

                Choose an answer to see the explanation.

              4. Question 4 of 7What type is SaveArgs?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type ParamsOf<Fn> = Fn extends (...args: infer Params) => any ? Params : never;
                
                type SaveArgs = ParamsOf<(id: number, note: string) => void>;

                Choose an answer to see the explanation.

              5. Question 5 of 7This Unwrap doesn't call itself. What type is Total?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Unwrap<T> = T extends Promise<infer Value> ? Value : T;
                
                type Total = Unwrap<Promise<Promise<number>>>;

                Choose an answer to see the explanation.

              6. Question 6 of 7Where can you write infer?

                Choose an answer to see the explanation.

              7. Question 7 of 7With the constraint extends string, what type is Label?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type FirstText<List> = List extends [infer First extends string, ...unknown[]] ? First : never;
                
                type Label = FirstText<[3, "tea"]>;

                Choose an answer to see the explanation.

              Key takeaways

              • infer Name puts a named blank in the pattern of a conditional type, after extends.
              • If the pattern fits, the name holds the type in the blank, and you use it in the true branch.
              • If it doesn't fit, you get the false branch, exactly as you wrote it: often never, or the type itself.
              • Put the blank where you need it: a list's item, a function's return type or parameters, a promise's value, a tuple's first or last place.
              • A true branch that calls its own type again, like UnwrapAll, opens every layer.
              • infer First extends string adds a constraint: the blank only accepts that type, so you can use it where that type is needed.

              Remember the one-liner.
              infer is a named blank in a pattern: TypeScript lines the pattern up with your type and fills the blank in.

              Coming next: Mapped types. They go through every property of a type and change each one: make them all optional, read-only, or a new type.

              CompleteFrontend Clear concepts. Working examples.