Vol. 6 · № 02 · Un atelier TypeScriptA TypeScript workshop

Conditional types & infer, ou brancher et extraire Conditional types & infer, or branch and extract

Le mapped type transforme chaque clé pareil ; pour décider, il faut le if du langage des types : T extends U ? X : Y. infer R capture un type dans le motif — c'est ainsi qu'on extrait le retour d'une fonction (ReturnType). Sur une union, le test se distribue membre par membre, et never s'évapore : voilà Exclude. La puissance, et son prix : la pyramide de conditionnels que plus personne ne lit. The mapped type transforms every key alike; to decide, you need the type language's if: T extends U ? X : Y. infer R captures a type in the pattern — that's how you extract a function's return (ReturnType). Over a union, the test distributes member by member, and never evaporates: that's Exclude. The power, and its price: the conditional pyramid no one reads anymore.

AudienceAudience
Dev qui sait transformer un type et veut le faire dépendre d'une condition Dev who can transform a type and wants to make it depend on a condition
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●● Conditional typesinferTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Le mapped type transforme chaque clé pareil. Pour décider, il faut brancher.The mapped type transforms every key alike. To decide, you must branch.

Le numéro précédent réécrivait une forme uniformément. Mais certains types dépendent d'une décision : « garde ce membre, jette celui-là », « si c'est une Promise, déballe-la, sinon laisse ». Une boucle ne sait pas trancher. Le type conditionnel le fait : T extends U ? X : Y choisit X ou Y selon que T est assignable à U. C'est un if au niveau type — et il rend calculable ce qu'on réécrivait à la main, cas par cas.The previous issue rewrote a shape uniformly. But some types depend on a decision: 'keep this member, drop that one', 'if it's a Promise, unwrap it, else leave it'. A loop can't decide. The conditional type can: T extends U ? X : Y picks X or Y depending on whether T is assignable to U. It's an if at the type level — and it makes computable what you used to rewrite by hand, case by case.

Réécrit à la main — un cas oublié, et ça mentHand-rewritten — one missed case, and it lies
type Maybe = string | number | null | undefined;

// réécrit à la main — oublie un membre dès que Maybe change :
type Defined = string | number;
// Ajoute 'boolean' à Maybe → Defined l'ignore, et ment.
Que devient la variante quand la source change ?What happens to the variant when the source changes?
Réécrit à la mainHand-rewritten
liste figée des casfrozen list of cases
un membre ajouté, et un cas oubliéadd a member, miss a case
Type conditionnelConditional type
une règle qui décidea rule that decides
rejoue le branchement à chaque sourcere-runs the branch on each source

Réécrire les cas à la main, c'est figer une décision qui devrait suivre la source. T extends U ? X : Y exprime la règle — « garde si…, jette sinon » — et la rejoue à chaque fois que le type d'entrée change.Hand-rewriting the cases freezes a decision that should track the source. T extends U ? X : Y states the rule — "keep if…, drop otherwise" — and replays it every time the input type changes.

Le conditionnel — la décision, calculéeThe conditional — the decision, computed
type Maybe = string | number | null | undefined;

type Defined = NonNullable<Maybe>;   // string | number — CALCULÉ par branchement
//   NonNullable<T> = T extends null | undefined ? never : T
Le réflexe du numéroThe issue's reflex

« Ce type dépend-il d'une décision sur un autre type ? » Si oui — garder ou jeter, déballer ou non, telle forme ou telle autre —, c'est un extends ? :, pas une liste de cas figée à la main."Does this type depend on a decision about another type?" If so — keep or drop, unwrap or not, this shape or that — it's an extends ? :, not a hand-frozen list of cases.

Le if du langage des types.The if of the type language.

Le mapped type était la boucle ; le conditionnel est le if. Avec lui, le langage des types gagne de quoi brancher — et, via infer (chapitre 3), de quoi extraire un type capturé. ReturnType, Awaited, Exclude : toute une famille en découle. Et le même extends qu'au Vol 5, mais cette fois comme test, pas comme borne.The mapped type was the loop; the conditional is the if. With it, the type language gains the means to branch — and, via infer (chapter 3), to extract a captured type. ReturnType, Awaited, Exclude: a whole family follows. And the same extends as in Vol 5, but this time as a test, not a bound.

№ 02La surpriseThe surprise4 min

Sur une union, le conditionnel se distribue — un test par membre, recombiné.Over a union, the conditional distributes — one test per member, recombined.

Pourquoi NonNullable<string | null> donne string, proprement ? Parce qu'un conditionnel sur un paramètre nu, appliqué à une union, ne teste pas l'union en bloc : il s'applique à chaque membre, puis réunit les résultats. null devient never, et never s'évapore d'une union. C'est ce qui fait marcher Exclude et NonNullable — mais c'est aussi un piège quand on voulait tester l'union entière. On désactive la distribution en enveloppant les deux côtés dans un tuple : [T] extends [U].Why does NonNullable<string | null> cleanly give string? Because a conditional over a naked type parameter, applied to a union, doesn't test the union as a whole: it applies to each member, then unites the results. null becomes never, and never evaporates from a union. That's what makes Exclude and NonNullable work — but it's also a trap when you meant to test the whole union. You disable distribution by wrapping both sides in a tuple: [T] extends [U].

L'union éclate, chaque membre est testé, puis réunieThe union splits, each member is tested, then reunited
type NoNull<T> = T extends null | undefined ? never : T;

type R = NoNull<string | null | number>;
// le conditionnel se DISTRIBUE sur chaque membre de l'union :
//   (string extends … ? …) | (null extends … ? never) | (number extends … ? …)
//   = string | never | number  =  string | number   ('never' disparaît de l'union)

Le conditionnel s'ouvre sur chaque membre puis recolle l'union. null tombe dans la branche never — et comme never est l'union vide (Vol 3.2), il disparaît du résultat. La distribution + never = un filtre sur les unions : c'est exactement la mécanique d'Exclude.The conditional opens on each member then re-glues the union. null falls into the never branch — and since never is the empty union (Vol 3.2), it disappears from the result. Distribution + never = a filter over unions: it's exactly Exclude's mechanism.

Un conditionnel sur une union ne la teste pas — il l'éclate, juge chaque membre, recolle. Et never qui tombe d'une union la nettoie : voilà tout le secret d'Exclude.A conditional over a union doesn't test it — it splits it, judges each member, re-glues. And never dropping out of a union cleans it: that's the whole secret of Exclude.
№ 03La mécaniqueThe mechanics5 min

infer — capturer un type à l'intérieur d'un motif, et le nommer.infer — capture a type inside a pattern, and name it.

Le conditionnel ne fait pas que brancher : il sait aussi filer le type. infer R, dans le motif d'un extends, dit « il y a un type ici — capture-le et appelle-le R, que je m'en serve dans la branche vraie ». C'est de la reconnaissance de forme : extraire le retour d'une fonction, l'élément d'un tableau, la valeur d'une Promise. ReturnType, Parameters, Awaited — toute la lib en vit. Et le extends ici est un test, pas une borne comme au Vol 5.The conditional doesn't only branch: it can also spin out the type. infer R, inside an extends pattern, says 'there's a type here — capture it and call it R, so I can use it in the true branch'. It's pattern matching: extract a function's return, an array's element, a Promise's value. ReturnType, Parameters, Awaited — the whole lib lives on it. And extends here is a test, not a bound like in Vol 5.

infer dans le motif — la captureinfer in the pattern — the capture
// infer R : "capture le type qui se trouve ICI, et nomme-le R".
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ElementOf<T>  = T extends (infer E)[] ? E : never;
type Awaited<T>    = T extends Promise<infer V> ? V : T;
// (versions simplifiées — la lib contraint T, et son Awaited déballe RÉCURSIVEMENT)
L'usage — extraire au lieu de réécrireThe use — extract instead of rewrite
function load() { return { user: getUser(), token: "abc" }; }

type Result = ReturnType<typeof load>;     // { user: User; token: string }
type Items  = ElementOf<User[]>;            // User
type Value  = Awaited<Promise<number>>;     // number
Les deux pouvoirs du conditionnelThe conditional's two powers
BrancherBranch
T extends U ?test d'assignabilitéassignability test
? X : Yvraie / fausse branchetrue / false branch
extendsextendsun test, pas une borne (≠ 5.2)a test, not a bound (≠ 5.2)
ExtraireExtract
infer Rcapture un type du motifcaptures a type from the pattern
=> infer Rle retour d'une fonctiona function's return
Promise<infer V>la valeur résoluethe resolved value
Les voisins, seulement nommés.The neighbors, only named.

Parameters, InstanceType, ConstructorParameters : tous des infer sur un autre motif. Plusieurs infer peuvent coexister ; en position contravariante (paramètres), ils peuvent produire une intersection plutôt qu'une union. Et infer se combine avec la récursion de types — d'où les types qui mettent une seconde à compiler, et le 6.3 qui dira quand s'arrêter.Parameters, InstanceType, ConstructorParameters: all infer over another pattern. Several infer can coexist; in contravariant position (parameters), they may produce an intersection rather than a union. And infer combines with type recursion — hence types that take a second to compile, and 6.3 on when to stop.

№ 04Le piègeThe trap4 min

La pyramide de conditionnels : ça marche, c'est impressionnant, et personne ne la relit.The conditional pyramid: it works, it's impressive, and no one rereads it.

C'est le danger qui monte tout au long du volume : le type trop malin. Les conditionnels imbriqués permettent d'encoder à peu près n'importe quelle logique au niveau type — et c'est précisément le problème. On y déverse des règles métier, on empile les branches, et on obtient un type que tsc met une seconde à résoudre, que l'éditeur peine à afficher, et que personne — toi compris, dans six mois — ne sait déboguer quand un cas tombe sur never. La question n'est pas « puis-je ? » mais « qu'est-ce que ça m'achète ? ».It's the danger that rises through the whole volume: the too-clever type. Nested conditionals let you encode just about any logic at the type level — and that's precisely the problem. You pour business rules in, stack the branches, and get a type that tsc takes a second to resolve, the editor struggles to display, and no one — you included, in six months — can debug when a case lands on never. The question isn't 'can I?' but 'what does it buy me?'.

Des branches empilées que rien ne rend lisiblesStacked branches nothing makes readable
// "Le type de réponse selon la méthode" — encodé en pyramide de conditionnels :
type Response<M, Mode, Body> =
  M extends "GET"    ? (Body extends { id: infer I } ? { data: I } : never) :
  M extends "POST"   ? { created: true; id: string } :
  M extends "DELETE" ? (Mode extends "soft" ? { archived: true } : void) :
  M extends "PATCH"  ? Partial<Body> :
  never;
// illisible, lent à typer, impossible à déboguer — et personne ne sait pourquoi
// un cas tombe sur 'never'. La logique métier a fui dans le système de types.

Le conditionnel est Turing-puissant à force d'imbrication — et c'est un mirage. On a réécrit de la logique métier dans un langage sans débogueur, lent à évaluer, impossible à tracer. Le contre-exemple obligatoire : la type-gymnastique posée là où une structure simple suffisait.The conditional is Turing-powerful by dint of nesting — and that's a mirage. You've rewritten business logic in a language with no debugger, slow to evaluate, impossible to trace. The mandatory counter-example: type-gymnastics where a simple structure would do.

Le test, et les deux écueils techniques.The test, and the two technical pitfalls.

Test : un conditionnel achète quelque chose s'il évite une duplication ou une dérive réelle — pas s'il prouve que tu sais le faire. Deux écueils en prime : la distribution qui s'active à ton insu (chapitre 2), et la profondeur d'instanciation — un conditionnel récursif trop profond fait carrément échouer tsc. Le langage des types n'a ni point d'arrêt, ni pile d'appels lisible.Test: a conditional buys something if it avoids real duplication or drift — not if it proves you can do it. Two pitfalls thrown in: distribution firing without your noticing (chapter 2), and instantiation depth — a too-deep recursive conditional makes tsc outright fail. The type language has no breakpoint, no readable call stack.

« Puis-je l'écrire en conditionnels ? » n'est pas la question. « Une structure simple ne dirait-elle pas la même chose, en lisible ? » l'est presque toujours."Can I write it in conditionals?" isn't the question. "Wouldn't a simple structure say the same thing, readably?" almost always is.
№ 05Bilan & éditoWrap-up & editorial2 min

Brancher et extraire — la puissance, et la tentation d'en abuser.Branch and extract — the power, and the temptation to overdo it.

Le type conditionnel est le if du langage des types : T extends U ? X : Y décide, et infer R capture un type dans le motif. Sur une union, le test se distribue membre par membre — d'où Exclude et NonNullable. La puissance est réelle ; le prix aussi : la pyramide de conditionnels, illisible et lente, où une union discriminée aurait dit la même chose en clair.The conditional type is the if of the type language: T extends U ? X : Y decides, and infer R captures a type in the pattern. Over a union, the test distributes member by member — hence Exclude and NonNullable. The power is real; so is the price: the conditional pyramid, unreadable and slow, where a discriminated union would have said the same thing plainly.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Brancher selon un typeBranch on a typeT extends U ? X : Y.T extends U ? X : Y.extends est un test ici, pas une borne (≠ 5.2).extends is a test here, not a bound (≠ 5.2).
Extraire un type capturéExtract a captured typeinfer R dans le motif (ReturnType, Awaited).infer R in the pattern (ReturnType, Awaited).Reconnaissance de forme — à garder simple.Pattern matching — keep it simple.
Filtrer une unionFilter a unionLa distribution + never (Exclude, NonNullable).Distribution + never (Exclude, NonNullable).S'active sur un paramètre nu — [T] la désactive.Fires on a naked parameter — [T] disables it.
Ne PAS l'employerNOT use itUne pyramide de conditionnels qui encode de la logique.A conditional pyramid encoding logic.Illisible, lente — une union discriminée suffisait.Unreadable, slow — a discriminated union sufficed.
Le conditionnel donne au type un if et une reconnaissance de forme. Assez de puissance pour tout coder — donc assez pour tout rendre illisible.The conditional gives types an if and pattern matching. Enough power to encode anything — hence enough to make anything unreadable.
Mot de l'éditeurFrom the editor

« Brancher, capturer — et savoir s'arrêter. »"Branch, capture — and know when to stop."

Avec le conditionnel, le langage des types a tout ce qu'il faut pour calculer : une boucle (le mapped type), un if (extends ? :), et même de la reconnaissance de forme (infer). C'est ce qui rend possibles ReturnType, Awaited, Exclude — des types qui suivent leur source sans qu'on les recopie. Et la distribution sur les unions, déroutante au début, est en réalité un cadeau : c'est elle qui transforme un simple conditionnel en filtre, et fait que never, l'union vide du Vol 3, s'évapore proprement des résultats. Le calcul de types n'est plus un mystère ; c'est un petit langage fonctionnel qu'on lit, désormais, comme du code.With the conditional, the type language has everything it needs to compute: a loop (the mapped type), an if (extends ? :), and even pattern matching (infer). That's what makes ReturnType, Awaited, Exclude possible — types that track their source without being recopied. And distribution over unions, baffling at first, is really a gift: it's what turns a plain conditional into a filter, and makes never, Vol 3's empty union, evaporate cleanly from results. Type computation is no longer a mystery; it's a little functional language you now read like code.

Mais c'est aussi un langage sans débogueur, sans point d'arrêt, sans pile d'appels lisible — et assez puissant pour qu'on s'y perde. La pyramide de conditionnels imbriqués est le piège classique : on encode de la logique métier dans les types parce qu'on le peut, et l'on obtient un édifice que tsc met une seconde à résoudre et que personne ne sait corriger. Le prochain numéro pousse cette puissance à son comble — les template literal types, qui fabriquent des types à partir de chaînes — et porte donc la leçon centrale du volume : non pas « jusqu'où peut-on aller ? », mais « quand faut-il s'arrêter ? ». La magie au niveau type est contagieuse ; le bon goût est de savoir y renoncer.But it's also a language with no debugger, no breakpoint, no readable call stack — and powerful enough to get lost in. The pyramid of nested conditionals is the classic trap: you encode business logic into types because you can, and you get an edifice tsc takes a second to resolve and no one can fix. The next issue pushes this power to its peak — template literal types, which build types from strings — and so carries the volume's central lesson: not 'how far can I go?', but 'when should I stop?'. Type-level magic is contagious; good taste is knowing when to refuse it.