Conditional Type Basics
If-Else for Types
A conditional type A extends B ? X : Y means: "if type A is assignable to type B, the result is X, otherwise Y". It works like a ternary operator, but on types, at compile time.
Example
Types do not exist at runtime, so we "print" a type by assigning wrong values to it: the compiler error shows what the type resolved to. IsString<number> is false, so assigning true fails.
type IsString<T> = T extends string ? true : false;
const a: IsString<"hello"> = true; // OK: "hello" extends string
const b: IsString<number> = true; // error: type is falseCompiler output (tsc)
Line 4: error TS2322: Type 'true' is not assignable to type 'false'.
Conditional Return Types
Example
A function can return a different type depending on its argument type. Here the input decides whether you get one product or an array.
type Product = { id: number; name: string };
type LookupResult<T> = T extends number[] ? Product[] : Product;
const db: Product[] = [{ id: 1, name: "Kettle" }, { id: 2, name: "Toaster" }, { id: 3, name: "Iron" }];
function lookup<T extends number | number[]>(ids: T): LookupResult<T> {
const find = (id: number): Product => db.find((p) => p.id === id) ?? { id, name: "?" };
return (Array.isArray(ids) ? ids.map(find) : find(ids as number)) as LookupResult<T>;
}
const one = lookup(2); // Product
const many = lookup([1, 3]); // Product[]
console.log(one.name);
console.log(many.map((p) => p.name));Output
Toaster [ 'Kettle', 'Iron' ]
Note: Inside the implementation TypeScript cannot follow conditional types, so a cast (as LookupResult<T>) is normal here. Overloads are an alternative.
Testing Types With Type Tests
Example
Library authors write type tests: an Equal helper plus an Expect that only accepts true. If any line is wrong the file does not compile.
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends (<T>() => T extends Y ? 1 : 2) ? true : false;
type Expect<T extends true> = T;
type IsString<T> = T extends string ? true : false;
type ElementType<T> = T extends readonly (infer E)[] ? E : T;
type tests = [
Expect<Equal<IsString<"a">, true>>,
Expect<Equal<IsString<42>, false>>,
Expect<Equal<ElementType<string[]>, string>>,
Expect<Equal<ElementType<number>, number>>,
];
console.log("All type tests compiled");Output
All type tests compiled