Skip to Content
Course content

31: Infer Keyword in Conditional Types

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're working in a warehouse. You have a bunch of shipping crates coming off a conveyor belt. You know that some of these crates are "Priority" crates, and every Priority crate contains a specific item—maybe a laptop, a phone, or a tablet. Now, you don't just want to know if a crate is a Priority crate; you want to reach inside, grab that item, and be able to describe exactly what it is for the rest of your logistics process.

In TypeScript, conditional types let us ask "Is this type X or Y?" But the infer keyword is what allows us to actually "reach inside" the type and pluck a piece of it out to use later. It's the difference between saying "Yes, this is a Promise" and saying "Yes, this is a Promise, and by the way, the value it eventually resolves to is a User object."

Plucking types out of the void

I've seen a lot of developers struggle with infer because it only exists within the extends clause of a conditional type. You can't just use it anywhere. It's specifically designed for when you're saying: "If this type matches this pattern, I want you to take the part that matches this specific spot and call it T."

Let's look at a real scenario. Suppose you have a lot of API response wrappers in your project. They all look something like this:

type ApiResponse<T> = {
  data: T;
  status: number;
  error: string | null;
};

Now, imagine you have a function that returns a Promise<ApiResponse<User>>. You don't want to manually write User everywhere in your code; you want a utility type that can look at that complex Promise-wrapped-Response and just give you the User part. Here is how we do that using infer:

type UnwrapResponse<T> = T extends Promise<ApiResponse<infer Payload>> 
  ? Payload 
  : T;

Let's break down exactly what's happening here. I'm telling TypeScript: "Check if T extends a Promise that contains an ApiResponse. If it does, look at the generic type inside that response, infer what it is, and call it Payload. If it matches, return that Payload. Otherwise, just return T."

Why we can't just use generics normally

You might be thinking, "Why can't I just use a regular generic?" The key is that infer is used when you don't know the type beforehand. You aren't providing the type; you're asking TypeScript to figure it out by observing the structure of another type.

I use this all the time when dealing with third-party libraries where the types are deeply nested. For example, if you're using a library that returns a complex event object and you only care about the type of the detail property, infer is your best friend. It saves you from having to manually define dozens of mirrored interfaces just to keep your code type-safe.

The logic flow in action

To make sure this clicks, look at how the compiler handles this in practice:

type User = { id: string; name: string };
type MyPromise = Promise<ApiResponse<User>>؛

// The compiler sees: Does MyPromise extend Promise<ApiResponse<infer Payload>>?
// Yes. It sees that 'User' is in the spot where 'infer Payload' is.
// Therefore, UnwrapResponse<MyPromise> becomes 'User'.
type FinalType = UnwrapResponse<MyPromise>; // FinalType is User

It's a powerful pattern. Once you get comfortable with it, you'll start seeing opportunities to remove a lot of redundant type declarations from your codebase. You stop telling TypeScript what things are and start letting TypeScript discover what they are.




📋 Practical Task

Building a Type-Safe Event Payload Extractor

In many frontend frameworks, event listeners provide an event object where the actual data is buried inside a detail property (common in CustomEvents). Your task is to create a utility type called ExtractPayload that takes a CustomEvent type and extracts the type of the detail property using the infer keyword.

Requirements:

  • Define an interface CustomEventWithDetail<T> that has a property detail: T and a property type: string.
  • Create the ExtractPayload<T> conditional type. It should check if T extends CustomEventWithDetail<infer P>.
  • If it matches, it should return P. Otherwise, it should return never.
  • Test your utility with a specific payload (e.g., a UserRegistrationPayload interface) to ensure the inferred type is correct.
// Start your code here:
interface UserRegistrationPayload {
  username: string;
  email: string;
}

// 1. Define CustomEventWithDetail<T> here...

// 2. Define ExtractPayload<T> here...

// 3. Test it:
type RegistrationEvent = CustomEventWithDetail<UserRegistrationPayload>;
type Result = ExtractPayload<RegistrationEvent>; // Result should be UserRegistrationPayload
Rating
0 0

There are no comments for now.

to be the first to leave a comment.