TypeScript Interview Questions
💡 Click Show Answer to generate an AI-powered answer instantly.
What is type assertion?
What is declaration merging?
What is module augmentation?
What is strict mode in TypeScript?
What is tsconfig.json?
What is decorators in TypeScript?
Explain class implementation in TypeScript.
What is abstract class in TypeScript?
What is mapped type?
What is conditional type?
Conditional types in TypeScript provide a way to select one of two possible types based on a condition, much like a conditional statement in JavaScript. They allow for type transformations and highly flexible type inference, enabling the creation of advanced and reusable utility types.
What are Conditional Types?
Conditional types let you express non-uniform type mappings by making a type depend on a condition. The condition is a type relationship test, using the extends keyword within the type definition.
They operate on the form T extends U ? X : Y, where if type T is assignable to type U, then the resulting type is X; otherwise, it's Y. This powerful feature enables advanced type manipulations and the creation of highly generic utility types, similar to how an if/else statement works at runtime, but applied to types during compilation.
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // type A = true
type B = IsString<123>; // type B = false
type C = IsString<string>; // type C = true
Syntax
The basic syntax for a conditional type is SomeType extends OtherType ? TrueType : FalseType. In this context, extends checks if SomeType is assignable to OtherType. If the condition is true, the type resolves to TrueType; otherwise, it resolves to FalseType.
interface Animal { age: number; }
interface Dog extends Animal { breed: string; }
type GetTypeOfAnimal<T> = T extends Dog ? 'dog' : 'animal';
type D = GetTypeOfAnimal<Dog>; // type D = 'dog'
type E = GetTypeOfAnimal<Animal>; // type E = 'animal'
type F = GetTypeOfAnimal<string>; // type F = 'animal' (since string is not assignable to Dog)
Key Use Cases
- Filtering Types: Selecting or excluding specific types from a union.
- Extracting Types: Deriving a part of a type based on certain criteria, like a function's return type or element type of an array.
- Mapping Types: Transforming one type into another, often used in conjunction with Mapped Types.
Example: `Exclude<T, U>`
The built-in Exclude<T, U> utility type constructs a type by excluding from T all union members that are assignable to U. It's a prime example of a conditional type in action.
type MyExclude<T, U> = T extends U ? never : T;
type T0 = MyExclude<'a' | 'b' | 'c', 'a'>; // type T0 = 'b' | 'c'
type T1 = Exclude<string | number | boolean, string | boolean>; // type T1 = number
Example: `Extract<T, U>`
The built-in Extract<T, U> utility type constructs a type by extracting from T all union members that are assignable to U. It effectively keeps only the overlapping types.
type MyExtract<T, U> = T extends U ? T : never;
type T2 = MyExtract<'a' | 'b' | 'c', 'a' | 'f'>; // type T2 = 'a'
type T3 = Extract<string | number | boolean, string>; // type T3 = string
Distributive Conditional Types
When conditional types act on a generic type T that is a *union type*, they distribute over the members of the union. This means the conditional type is applied to each member of the union individually, and the results are then combined into a new union type. This behavior is crucial for Exclude and Extract to work as expected with union types.
type ToArray<T> = T extends any ? T[] : never;
type G = ToArray<string | number>;
// This is evaluated as:
// (string extends any ? string[] : never) | (number extends any ? number[] : never)
// which results in:
// type G = string[] | number[]
`infer` Keyword
The infer keyword can be used within the extends clause of a conditional type to declare a new type variable that is inferred from the type being checked. This allows you to extract types from other types, such as the return type of a function, the element type of an array, or parameter types of a function.
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
function greet(name: string): string { return `Hello, ${name}`; }
type GreetReturnType = ReturnType<typeof greet>; // type GreetReturnType = string
type NumberArray = number[];
type ElementType<T> = T extends (infer U)[] ? U : T;
type ArrElement = ElementType<NumberArray>; // type ArrElement = number
type NotAnArray = ElementType<string>; // type NotAnArray = string