Master Conditional Types in TypeScript

Available in :

Ah, conditional types in TypeScript… They're like chameleons, changing shape and adapting to your code's needs! Maybe you’re already familiar with basic types, but conditional types take it to the next level. Think of them as smart types that adapt to situations—kind of like superheroes of typing. So, get comfortable and prepare to add some magic to your TypeScript code! 🧙‍♂️

Why Use Conditional Types? 🧐

In TypeScript, we love it when everything is neatly organized and strictly typed. But sometimes, our code needs a type that adapts based on certain conditions. Imagine you have a function that might return either a string or a number, depending on the input parameters. 😱 Without conditional types, you’d have to manually manage each case, and that quickly becomes tedious! Luckily, conditional types bring flexibility and responsiveness, like a typing ninja. 🥷

The Basics: Syntax of Conditional Types 🧩

Conditional types are somewhat like a ternary operator, but for types. Here’s an example to give you a taste:

typescript
1type IsString<T> = T extends string ? "It's a string" : "It's not a string";

In this example, IsString is a conditional type that checks if T is a string. If it is, it returns "It's a string", otherwise "It's not a string". Simple enough, but wait until you see what we can do in more advanced cases!

Practical Example: Adapting a Type Based on Configuration 🛠️

Let’s imagine a function that takes a configuration parameter and must return a different type depending on that configuration. Conditional types are perfect for this kind of magic ✨!

Code Example

typescript
1type Config = {
2 mode: 'simple' | 'detailed';
3};
4
5type Response<T extends Config> = T['mode'] extends 'simple'
6 ? { data: string }
7 : { data: string; details: string[] };
8
9function fetchData<T extends Config>(config: T): Response<T> {
10 if (config.mode === 'simple') {
11 return { data: 'Simplified data' } as Response<T>;
12 } else {
13 return {
14 data: 'Full data',
15 details: ['detail1', 'detail2'],
16 } as Response<T>;
17 }
18}
19
20const simpleConfig = { mode: 'simple' };
21const detailedConfig = { mode: 'detailed' };
22
23const resultSimple = fetchData(simpleConfig); // { data: "Simplified data" }
24const resultDetailed = fetchData(detailedConfig); // { data: "Full data", details: [...] }

In this example, Response<T> adapts the type based on whether mode is "simple" or "detailed". The magic happens: fetchData(simpleConfig) returns an object with only data, while fetchData(detailedConfig) also includes details. Convenient, right?

Nested Conditional Types: Typing à la Carte 🧇

Why stop there? You can also nest conditional types to handle even more precise cases! Imagine you want to adapt the type not only based on mode but also on whether the user is authenticated or not. Ready for some advanced type juggling? 🎢

typescript
1type UserResponse<
2 T extends Config,
3 Authenticated extends boolean,
4> = Authenticated extends true
5 ? T['mode'] extends 'simple'
6 ? { data: string }
7 : { data: string; details: string[] }
8 : { error: 'Not authenticated' };
9
10function fetchUserData<T extends Config, Authenticated extends boolean>(
11 config: T,
12 isAuthenticated: Authenticated,
13): UserResponse<T, Authenticated> {
14 if (!isAuthenticated) {
15 return { error: 'Not authenticated' } as UserResponse<T, Authenticated>;
16 }
17 if (config.mode === 'simple') {
18 return { data: 'Simplified data' } as UserResponse<T, Authenticated>;
19 } else {
20 return {
21 data: 'Full data',
22 details: ['detail1', 'detail2'],
23 } as UserResponse<T, Authenticated>;
24 }
25}
26
27const resultAuthSimple = fetchUserData(simpleConfig, true); // { data: "Simplified data" }
28const resultAuthDetailed = fetchUserData(detailedConfig, true); // { data: "Full data", details: [...] }
29const resultNotAuth = fetchUserData(detailedConfig, false); // { error: "Not authenticated" }

Here, UserResponse adapts the type based on two criteria: mode and isAuthenticated. The result? Ultra-precise typing that covers all possible cases!

Advanced Conditional Types with infer: Type Deduction 🕵️‍♂️

Ready for some advanced tricks? TypeScript has a special keyword for use in conditional types: infer. It lets you infer a type directly within your conditional type. Handy for extracting information from complex types!

Example with infer

typescript
1type ReturnTypeOfFunction<T> = T extends (...args: any[]) => infer R
2 ? R
3 : never;
4
5function getHello(): string {
6 return 'Hello, world!';
7}
8
9type HelloReturnType = ReturnTypeOfFunction<typeof getHello>; // Result: string

Here, ReturnTypeOfFunction uses infer R to deduce the return type of a function. In this example, HelloReturnType will be of type string because getHello returns a string.

Conclusion 🎉

Conditional types in TypeScript are like superpowers for your code. They let you create dynamic types and maintain strict typing while making your code simpler. Ready to impress your colleagues? Experiment with these conditional types and discover new ways to structure your TypeScript code! 🚀

No more excuses for approximate types—with conditional types, your TypeScript code becomes virtually bulletproof! 👌

Share this article


Sébastien Timoner

Sébastien TIMONER

Lead Developer
Custom Development Expert
Aix-en-Provence, France

Expert in web development and team management, I specialize in creating and optimizing high-performance digital solutions. With extensive expertise in modern technologies like React.js, Node.js, TypeScript, Symfony, and Zephyr OS for IoT, I ensure the success of complex SaaS and IoT projects, from design to production, for companies across various sectors, at offroadLabs.

At offroadLabs, I offer custom development services that combine technical expertise with a collaborative approach. Whether creating an innovative SaaS solution, developing IoT systems with Zephyr OS, modernizing an existing application, or supporting the upskilling of a team, I am committed to delivering robust and high-performance solutions tailored to the specific needs of each project.

I am available for projects in the Aix-en-Provence area or fully remote.