TypeScript
Completed
-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
31: Infer Keyword in Conditional Types
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 propertydetail: Tand a propertytype: string. - Create the
ExtractPayload<T>conditional type. It should check ifTextendsCustomEventWithDetail<infer P>. - If it matches, it should return
P. Otherwise, it should returnnever. - Test your utility with a specific payload (e.g., a
UserRegistrationPayloadinterface) 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
There are no comments for now.