Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Conditional Types in TypeScript

Conditional Types

~15 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

The last major tool for GENUINE type logic: conditional types – "if-then" decisions, but at the TYPE level instead of the value level, evaluated at compile time.

The basic syntax

type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<42>;      // false

T extends string ? true : false reads almost like the regular JavaScript ternary operator (condition ? then : else) – extends here does NOT check inheritance (as with classes, chapter 13), but rather "is T ASSIGNABLE to string?", evaluated at the TYPE level.

In practice: extracting a method's return type

Let's build a conditional type that automatically extracts the ELEMENT type from ANY Repository<T> class (chapters 17-18) – useful when you only know the type from the repository class, without writing it out a second time manually:

src/types/ExtractElementType.ts
import { Repository } from '../repository/Repository.js';

export type ExtractElementType<R> = R extends Repository<infer T> ? T : never;

// Applied:
type BookRepo = Repository<import('../models/Book.js').Book>;
type ExtractedType = ExtractElementType<BookRepo>; // Book

infer T is the KEY to conditional types: it tells TypeScript "figure OUT which concrete type sits at this position, and call it T" – infer can ONLY be used inside the extends clause of a conditional type, nowhere else.

Distributive conditional types over unions

type WithoutArray<T> = T extends any[] ? never : T;

type Mixed = string | number[] | boolean;
type Filtered = WithoutArray<Mixed>; // string | boolean - number[] got removed!

A SUBTLE but important detail: when a conditional type gets applied to a UNION, it gets AUTOMATICALLY applied to EACH member of the union INDIVIDUALLY, and the results are merged back into a union ("distributive conditional types") – never from chapter 4/19 disappears from the resulting union in the process, since string | never simplifies to string.

Preventing distribution with square brackets

type WithoutArrayNonDistributed<T> = [T] extends [any[]] ? never : T;

type Result = WithoutArrayNonDistributed<Mixed>; // never - the ENTIRE union checked, not member by member

[T] (T wrapped in square brackets) PREVENTS the automatic distribution – sometimes desired when you want to check the ENTIRE union as ONE type, instead of member by member. An advanced detail, rarely needed, but explains surprising behavior if it ever comes up.

Built-in examples: Awaited and NonNullable

type A = Awaited<Promise<string>>; // string - "unwraps" a promise
type B = NonNullable<string | null | undefined>; // string - removes null/undefined

Awaited and NonNullable from chapter 20 are THEMSELVES implemented as conditional types with infer – now you understand EXACTLY how they work internally, not just how to use them.

Tipp: Rule of thumb: writing conditional types YOURSELF is ADVANCED and rarely needed in everyday application code – usually an already built-in utility type (chapter 20) is enough. Most useful when writing REUSABLE library types meant to adapt to ANY input type – EXACTLY as we demonstrated here with ExtractElementType.