Vol. 5 · № 01 · Un atelier TypeScriptA TypeScript workshop

Génériques & inférence, ou une relation, pas une inconnue Generics & inference, or a relation, not an unknown

Un générique n'est pas « un type qu'on ignore » — ça, c'est unknown. C'est une relation : <T> relie un paramètre au retour, deux arguments entre eux. Là où any rompt le lien et débranche la vérification, <T> garde le type et le fait voyager — inféré au site d'appel, sans rien écrire. Mais un T qui n'apparaît qu'une fois ne relie rien : c'est un as déguisé en chevrons. A generic isn't 'a type you don't know' — that's unknown. It's a relation: <T> ties a parameter to the return, two arguments together. Where any severs the link and unplugs checking, <T> keeps the type and carries it across — inferred at the call site, nothing written. But a T that appears only once relates nothing: it's an as dressed up in brackets.

AudienceAudience
Dev qui a vu les échappatoires du Vol 4 et veut garder le type sans le nommer Dev who saw Vol 4's escape hatches and wants to keep the type without naming it
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ GénériquesInférenceTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Un générique n'est pas « un type qu'on ignore ». C'est une relation entre des types.A generic isn't "a type you don't know". It's a relation between types.

Le Vol 4 parlait d'abandonner le type. Le générique fait l'inverse : il le garde, et le fait voyager. Une fonction qui doit marcher pour plusieurs types tombe vite dans le piège du any — qui rompt tout lien entre ce qui entre et ce qui sort, et débranche la vérification. <T> dit autre chose : « quel que soit le type d'élément, le type de retour est le même ». Ce n'est pas une inconnue, c'est un fil tendu entre deux positions.Vol 4 was about abandoning the type. The generic does the opposite: it keeps it, and makes it travel. A function meant to work for several types quickly falls into the any trap — which severs any link between what comes in and what goes out, and unplugs checking. <T> says something else: 'whatever the element type, the return type is the same'. It's not an unknown, it's a thread strung between two positions.

any — le lien est rompu, le type d'élément perduany — the link is severed, the element type lost
function first(arr: any[]): any {   // any : aucun lien entre l'entrée et la sortie
  return arr[0];
}

const u = first(users);   // u : any  ← le type d'élément est perdu
u.nme;                    // ✗ faute de frappe — aucune erreur, any déteint (Vol 4)
Qu'arrive-t-il au type, de l'entrée à la sortie ?What happens to the type, from input to output?
any[] → any
lien rompulink severed
le type d'élément n'arrive jamais au retourthe element type never reaches the return
T[] → T
lien préservélink preserved
le type d'élément ressort, intactthe element type comes back out, intact

any et <T> acceptent tous deux n'importe quel type d'élément. Mais any oublie lequel ; <T> s'en souvient et le rend à la sortie — un seul code, et la vérification tient pour chaque type.Both any and <T> accept any element type. But any forgets which one; <T> remembers it and returns it at the output — one body, and checking holds for every type.

Le générique — le type d'élément ressort au retourThe generic — the element type comes back at the return
function first<T>(arr: T[]): T | undefined {   // <T> RELIE : type d'élément = type de retour
  return arr[0];                                // (| undefined : le tableau peut être vide)
}

const u = first(users);   // u : User | undefined  ← T inféré à User au site d'appel
u?.nme;                   // ✗ 'nme' n'existe pas sur User — attrapé
Le réflexe du numéroThe issue's reflex

« Ce <T> — qu'est-ce qu'il relie ? » Un générique gagne sa lettre en connectant au moins deux positions (un paramètre au retour, deux paramètres entre eux). S'il ne relie rien, ce n'est pas un générique : c'est un any avec un chapeau."This <T> — what does it relate?" A generic earns its letter by connecting at least two positions (a parameter to the return, two parameters together). If it relates nothing, it's not a generic: it's an any with a hat.

Pourquoi ce numéro ouvre le Vol 5.Why this issue opens Vol 5.

Après les échappatoires, le polymorphisme. Le générique est la réponse positive à « ce code marche-t-il pour plusieurs types ? » : oui, sans rien abandonner de la vérification. Le reste du volume affine la maille — contraindre juste assez (5.2), comprendre la variance (5.3). Mais tout part de cette idée : <T> est une relation, pas une inconnue.After the escape hatches, polymorphism. The generic is the positive answer to 'does this code work for several types?': yes, abandoning none of the checking. The rest of the volume refines the mesh — constrain just enough (5.2), understand variance (5.3). But it all starts here: <T> is a relation, not an unknown.

№ 02La surpriseThe surprise4 min

On n'écrit presque jamais <T> à l'appel. TypeScript le déduit de l'argument.You almost never write <T> at the call. TypeScript deduces it from the argument.

Venant de Java ou C#, on s'attend à écrire first<User>(users). En TypeScript, c'est rare : on appelle first(users), et le compilateur lit l'argument pour en déduire que T vaut User. Le paramètre de type se remplit au site d'appel, automatiquement. Et comme le même T peut apparaître à plusieurs endroits, il les relie : passer des arguments qui ne concordent pas devient une erreur. L'inférence et la relation sont les deux faces du même mécanisme.Coming from Java or C#, you expect to write first<User>(users). In TypeScript, that's rare: you call first(users), and the compiler reads the argument to deduce that T is User. The type parameter fills in at the call site, automatically. And since the same T can appear in several places, it ties them together: passing mismatched arguments becomes an error. Inference and relation are two faces of the same mechanism.

Un seul code, le type exact à chaque appelOne body, the exact type at each call
const a = first(users);       // T = User    ← inféré de l'argument
const b = first([1, 2, 3]);   // T = number  ← inféré
const c = first(["x", "y"]);  // T = string  ← un seul code, trois types exacts
// on n'a jamais écrit first<User> : TS lit l'argument et remplit T tout seul

Le compilateur regarde ce qu'on passe et en déduit T : un User[] donne T = User, un number[] donne T = number. Tu écris le code une fois ; l'inférence le spécialise à chaque appel, sans jamais perdre le type exact en route.The compiler looks at what you pass and deduces T: a User[] gives T = User, a number[] gives T = number. You write the body once; inference specializes it at each call, never losing the exact type on the way.

Tu ne donnes pas le type au générique — il le lit dans tes arguments. Et là où le même T revient, il devient une règle que tes appels doivent respecter.You don't give the generic its type — it reads it from your arguments. And wherever the same T recurs, it becomes a rule your calls must obey.
№ 03La mécaniqueThe mechanics5 min

Un paramètre — mais au niveau type. Le site d'appel fournit l'argument.A parameter — but at the type level. The call site supplies the argument.

Le mécanisme tient en une analogie : <T> est un paramètre, comme (x) en est un, sauf qu'il vit au niveau des types. À l'appel, on fournit l'argument — explicitement (rare) ou par inférence (presque toujours). Plusieurs paramètres sont possibles : <K, V> pour une Map. Et ce n'est pas une curiosité : Array<T>, Map<K,V>, Promise<T>, Set<T> — toute la bibliothèque standard est faite de relations génériques que tu utilises déjà sans y penser.The mechanism is one analogy: <T> is a parameter, just as (x) is, except it lives at the type level. At the call, you supply the argument — explicitly (rare) or by inference (almost always). Several parameters are possible: <K, V> for a Map. And it's no curiosity: Array<T>, Map<K,V>, Promise<T>, Set<T> — the whole standard library is made of generic relations you already use without thinking.

Plusieurs paramètres de type, inférés ensembleSeveral type parameters, inferred together
// Un générique est une fonction au niveau TYPE : <…> = ses paramètres.
function lookup<K, V>(map: Map<K, V>, key: K): V | undefined {
  return map.get(key);
}                       // K et V sont fournis au site d'appel, comme des arguments

const score = lookup(byName, "ana");   // K = string, V = number — inférés ensemble
La lib standard : des relations génériques, partoutThe standard library: generic relations, everywhere
// Toute la bibliothèque standard est bâtie là-dessus :
const xs: Array<number>   = [1, 2, 3];      // Array<T>
const m:  Map<string, User> = new Map();    // Map<K, V>
const p:  Promise<User>   = fetchUser();    // Promise<T>
const s:  Set<string>     = new Set();      // Set<T>
Le générique, pièce par pièceThe generic, piece by piece
La déclarationThe declaration
<T>le paramètre de typethe type parameter
<K, V>plusieurs, reliés ensembleseveral, related together
PositionsPositionsparams + retour partagent Tparams + return share T
Le site d'appelThe call site
InféréInferreddepuis les arguments (défaut)from the arguments (default)
ExpliciteExplicitfn<User>(...)
ContextuelContextualun callback infère ses paramsa callback infers its params
Les voisins, seulement nommés.The neighbors, only named.

On peut écrire les arguments de type (fn<User>(...)) quand l'inférence échoue ou pour la guider. Le typage contextuel est l'inférence à l'envers : dans xs.map(x => ...), le type de x vient du tableau, sans annotation. Reste la vraie question du volume : un <T> libre ne permet presque rien sur la valeur (on ignore ses capacités) — d'où les contraintes extends du 5.2.You can write the type arguments (fn<User>(...)) when inference fails or to guide it. Contextual typing is inference in reverse: in xs.map(x => ...), the type of x comes from the array, with no annotation. The volume's real question remains: a free <T> lets you do almost nothing with the value (you don't know its capabilities) — hence the extends constraints of 5.2.

№ 04Le piègeThe trap4 min

Le <T> qui n'apparaît qu'une fois ne relie rien — c'est un as déguisé.A <T> that appears only once relates nothing — it's an as in disguise.

Le faux générique a l'air savant et ne prouve rien. Le test est mécanique : compte les positions où T apparaît. S'il n'est qu'à un seul endroit — souvent le seul retour — il ne relie aucune entrée à aucune sortie : le compilateur n'a rien d'où l'inférer, et l'appelant le choisit lui-même. parseJson<User>(raw) n'est alors qu'un JSON.parse(raw) as User avec des chevrons : le mensonge du Vol 4, rhabillé en polymorphisme.The fake generic looks clever and proves nothing. The test is mechanical: count the positions where T appears. If it's in only one place — often the return alone — it relates no input to any output: the compiler has nothing to infer it from, and the caller picks it themselves. parseJson<User>(raw) is then just a JSON.parse(raw) as User with angle brackets: Vol 4's lie, dressed up as polymorphism.

T au retour seul : l'appelant invente le typeT at the return alone: the caller invents the type
function parseJson<T>(s: string): T {   // ✗ T n'apparaît QU'au retour
  return JSON.parse(s);                  // rien dans les params ne détermine T
}

const u = parseJson<User>(raw);   // u : User — mais RIEN n'a été vérifié
u.name.trim();                    // 💥 c'est JSON.parse(raw) as User, déguisé en <>

Aucun paramètre ne porte T : le compilateur ne peut pas l'inférer, donc l'appelant l'affirme (<User>) sans le moindre contrôle. Le générique ne crée ici aucun lien — il n'est qu'une façon plus cérémonieuse d'écrire as. Le contre-exemple obligatoire : un <T> qui ne relie rien.No parameter carries T: the compiler can't infer it, so the caller asserts it (<User>) with zero checking. The generic creates no link here — it's just a more ceremonious way to write as. The mandatory counter-example: a <T> that relates nothing.

Le test, en une ligne.The test, in one line.

Compte les apparitions de T. Deux ou plus, reliées (un param au retour, ou deux params) : vrai générique, l'inférence travaille. Une seule : il ne relie rien — c'est unknown si l'on ignore le type (et l'on prouvera), ou as si l'on l'affirme. Le miroir du sur-branding (3.3) et de l'unknown partout (4.2) : ne mets de cérémonie que là où elle achète une garantie réelle.Count the appearances of T. Two or more, related (a param to the return, or two params): a real generic, inference works. Just one: it relates nothing — it's unknown if you don't know the type (and you'll prove it), or as if you assert it. The mirror of over-branding (3.3) and unknown-everywhere (4.2): spend ceremony only where it buys a real guarantee.

Un <T> qui n'apparaît qu'une fois ne fait pas voyager un type — il en fabrique un sur commande, sans preuve. Le polymorphisme est dans le lien, pas dans les chevrons.A <T> appearing only once carries no type — it fabricates one on demand, unproven. Polymorphism is in the link, not in the brackets.
№ 05Bilan & éditoWrap-up & editorial2 min

Une relation, pas une inconnue — un type qu'on garde et qu'on fait voyager.A relation, not an unknown — a type you keep and carry across.

Le générique ouvre le polymorphisme sans rien céder de la vérification. <T> n'est pas « un type qu'on ignore » : c'est un fil qui relie des positions — un paramètre au retour, deux paramètres entre eux. TypeScript l'infère au site d'appel, à partir des arguments. Et il ne mérite sa lettre que s'il relie : un T solitaire est un as ou un unknown déguisé.The generic opens polymorphism without giving up any checking. <T> isn't 'a type you don't know': it's a thread relating positions — a parameter to the return, two parameters together. TypeScript infers it at the call site, from the arguments. And it only earns its letter if it relates: a lone T is an as or unknown in disguise.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Faire passer un type à traversPass a type throughUn <T> qui relie un paramètre au retour.A <T> relating a parameter to the return.Rien : T est inféré au site d'appel.Nothing: T is inferred at the call site.
Relier plusieurs argumentsRelate several argumentsLe même T sur plusieurs positions.The same T on several positions.TS refuse si les types ne concordent pas.TS refuses if the types don't agree.
Forcer T explicitementForce T explicitlyfn<User>(...) quand l'inférence ne suffit pas.fn<User>(...) when inference falls short.À ne pas confondre avec un as : T doit être relié.Don't confuse with an as: T must be related.
Ne PAS l'employerNOT use itUn <T> qui n'apparaît qu'une fois.A <T> appearing only once.Il ne relie rien — c'est unknown ou as déguisé.It relates nothing — unknown or as in disguise.
unknown dit « je ne connais pas ce type ». <T> dit « je n'ai pas besoin de le nommer — la relation tient pour tous ». La nuance fait tout le polymorphisme.unknown says "I don't know this type". <T> says "I don't need to name it — the relation holds for all". That nuance is the whole of polymorphism.
Mot de l'éditeurFrom the editor

« Une relation, pas une inconnue. »"A relation, not an unknown."

On confond souvent le générique avec l'inconnu : <T>, ce serait « un type que je ne connais pas ». Mais ça, c'est unknown, et le Vol 4 l'a déjà nommé. Le générique dit autre chose, de plus subtil : « je n'ai pas besoin de connaître ce type, parce que ce que j'affirme est vrai pour tous ». identity<T>(x: T): T ne sait rien de T — et n'en a pas besoin : la relation « ce qui entre ressort identique » tient, que T soit un User, un number ou une fonction. C'est le renversement du volume précédent : là où any, unknown et as renonçaient au type, le générique le conserve et le fait circuler, sans jamais éteindre la vérification.People often confuse the generic with the unknown: <T>, surely, is 'a type I don't know'. But that's unknown, and Vol 4 already named it. The generic says something else, subtler: 'I don't need to know this type, because what I claim is true for all of them'. identity<T>(x: T): T knows nothing about T — and needs nothing: the relation 'what comes in goes back out identical' holds whether T is a User, a number or a function. It's the reversal of the previous volume: where any, unknown and as gave up the type, the generic keeps it and circulates it, never turning off checking.

Reste le revers, qui fait tout l'art du volume. Un T totalement libre ne permet presque rien : si je ne sais rien de lui, je ne peux ni lire .length, ni accéder à .name, ni l'additionner — et c'est correct. Pour faire quelque chose d'un générique, il faut souvent le borner : « T, mais qui a au moins une longueur », « T, mais une clé de cet objet ». C'est l'objet du prochain numéro — les contraintes extends —, où l'on apprend à contraindre juste assez : ni any déguisé, ni carcan qui rigidifie.The flip side remains, and it's the volume's whole art. A fully free T lets you do almost nothing: if I know nothing about it, I can't read .length, access .name, or add it — and that's correct. To do something with a generic, you often have to bound it: 'T, but with at least a length', 'T, but a key of this object'. That's the next issue's subject — the extends constraints — where you learn to constrain just enough: neither any in disguise, nor a straitjacket that rigidifies.