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

Contraintes (extends) & valeurs par défaut, ou de quelle capacité ai-je besoin ? Constraints (extends) & defaults, or which capability do I need?

Sur un <T> libre, .length ne compile pas — et c'est correct : on ignore tout de ses capacités. La liberté de T est une impuissance. T extends { length: number } le borne à exactement la forme dont le corps se sert, débloque cette capacité, et garde le type exact à la sortie. keyof et T[K] relient clés et valeurs. Tout l'art : contraindre juste assez, ni any déguisé, ni carcan. On a free <T>, .length won't compile — and that's correct: you know nothing of its capabilities. The freedom of T is a powerlessness. T extends { length: number } bounds it to exactly the shape the body uses, unlocks that capability, and keeps the exact type at the output. keyof and T[K] relate keys and values. The whole art: constrain just enough, neither any in disguise nor a straitjacket.

AudienceAudience
Dev qui sait faire voyager un type et veut s'en servir sans retomber dans any Dev who can make a type travel and wants to use it without falling back to any
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●●○ ContrainteskeyofTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Sur un <T> libre, on ne peut presque rien faire. Et c'est correct.On a free <T>, you can do almost nothing. And that's correct.

Le numéro précédent faisait voyager le type ; celui-ci veut s'en servir. Et là, surprise : un T totalement libre interdit d'appeler .length ou de lire .name — parce qu'on ignore tout de ses capacités. Le réflexe paresseux est de retomber dans any (et de tout perdre). Le bon geste : borner le générique à exactement ce dont on a besoin — T extends { length: number } —, ce qui débloque la capacité sans sacrifier le type précis.The previous issue made the type travel; this one wants to use it. And here's the surprise: a fully free T forbids calling .length or reading .name — because you know nothing of its capabilities. The lazy reflex is to fall back to any (and lose everything). The right move: bound the generic to exactly what you need — T extends { length: number } — which unlocks the capability without sacrificing the precise type.

T libre — aucune capacité, .length refuséFree T — no capability, .length refused
function longest<T>(a: T, b: T): T {
  return a.length >= b.length ? a : b;   // ✗ 'length' n'existe pas sur le type 'T'
}                                         // un T libre n'a AUCUNE capacité connue
Que peut-on faire de T ?What can you do with T?
<T>
riennothing
aucune capacité connueno known capability
<T extends { length }>
.length — et le type exact.length — and the exact type
la capacité voulue, T préservéthe wanted capability, T preserved

La contrainte n'est pas une limite imposée au générique — c'est ce qui le rend utilisable. T extends C dit « T, mais qui a au moins la forme de C » : elle ouvre la capacité, et garde le type précis intact à la sortie.The constraint isn't a limit imposed on the generic — it's what makes it usable. T extends C says "T, but with at least C's shape": it opens the capability, and keeps the precise type intact at the output.

T contraint — .length débloqué, type exact conservéConstrained T — .length unlocked, exact type kept
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;   // ✓ T a AU MOINS une length
}

longest([1, 2], [3]);     // T = number[]  → renvoie number[], pas { length }
longest("aaa", "bb");     // T = string    → renvoie string, type exact préservé
Le réflexe du numéroThe issue's reflex

« De quelle capacité ai-je besoin sur ce T ? Je contrains à exactement ça, pas plus. » Pas le type concret (qui tuerait la réutilisation), pas rien (qui rendrait T inutilisable) : juste la forme dont le corps se sert."Which capability do I need on this T? I constrain to exactly that, no more." Not the concrete type (which would kill reuse), not nothing (which would make T unusable): just the shape the body uses.

Pourquoi contraindre, plutôt que typer le paramètre.Why constrain, rather than type the parameter.

On pourrait écrire a: { length: number } sans générique — mais alors le retour serait { length: number }, et l'appelant perdrait que c'était un string ou un number[]. La contrainte garde le lien du Vol 5.1 : elle exige la forme à l'entrée, mais se souvient du type exact pour la sortie.You could write a: { length: number } without a generic — but then the return would be { length: number }, and the caller would lose that it was a string or a number[]. The constraint keeps Vol 5.1's link: it demands the shape at the input, but remembers the exact type for the output.

№ 02La surpriseThe surprise4 min

La liberté de T est une impuissance. La contrainte est ce qui le rend capable.The freedom of T is a powerlessness. The constraint is what makes it capable.

On arrive en croyant qu'un générique non contraint est le plus puissant — il accepte tout ! C'est l'inverse. Plus T est libre, moins on peut en faire : si je ne sais rien de lui, je ne peux ni lire une propriété, ni l'appeler, ni l'additionner. C'est exactement la leçon d'unknown (Vol 4), au niveau d'un paramètre de type. La contrainte ne bride pas T : elle débloque précisément la capacité qu'on a déclarée — et seulement celle-là.You arrive thinking an unconstrained generic is the most powerful — it accepts everything! It's the opposite. The freer T is, the less you can do with it: if I know nothing about it, I can't read a property, call it, or add it. That's exactly unknown's lesson (Vol 4), at the level of a type parameter. The constraint doesn't curb T: it unlocks precisely the capability you declared — and only that one.

Tout accès est refusé : T n'a aucune forme connueEvery access refused: T has no known shape
function f<T>(x: T) {
  x.length;     // ✗ pas de length sur T
  x.name;       // ✗ pas de name
  x.toFixed();  // ✗ rien : T peut être N'IMPORTE QUOI
}               // plus T est libre, MOINS on peut en faire

Le compilateur n'est pas tatillon : il ignore vraiment ce qu'est T, donc il ne peut garantir aucune opération. Chaque refus est la même question qu'avec unknown — « es-tu sûr que ça a une length ? » —, mais posée une fois pour toutes, dans la signature.The compiler isn't being fussy: it genuinely doesn't know what T is, so it can guarantee no operation. Each refusal is the same question as with unknown — "are you sure it has a length?" — but asked once and for all, in the signature.

Un T sans contrainte peut être tout — donc on n'en sait rien, donc on n'en fait rien. Contraindre, c'est échanger de la généralité contre de la capacité, à la dose exacte.An unconstrained T can be anything — so you know nothing, so you do nothing. Constraining trades generality for capability, in the exact dose.
№ 03La mécaniqueThe mechanics5 min

extends borne, keyof énumère, T[K] relie — et = donne un défaut.extends bounds, keyof enumerates, T[K] relates — and = gives a default.

Quatre outils. extends C exige que T soit assignable à C. keyof T est l'union littérale des clés de T. T[K] est le type à la clé K (avec K extends keyof T). Et T = Default fournit une valeur de repli quand ni l'inférence ni l'appel ne donnent T. Mis ensemble, ils produisent le chef-d'œuvre des génériques contraints : un accès à une propriété entièrement typé, qui refuse les clés qui n'existent pas.Four tools. extends C requires T to be assignable to C. keyof T is the literal union of T's keys. T[K] is the type at key K (with K extends keyof T). And T = Default supplies a fallback when neither inference nor the call provides T. Together they produce the masterpiece of constrained generics: a fully typed property access that refuses keys which don't exist.

Le chef-d'œuvre : un accès à une clé, entièrement typéThe masterpiece: a fully typed key access
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}                  // K est contraint aux clés de T ; le retour est le type À cette clé

const u = { name: "Ana", age: 30 };
get(u, "name");    // string  — K = "name", retour T["name"]
get(u, "age");     // number  — K = "age",  retour T["age"]
get(u, "email");   // ✗ "email" n'est pas une clé de u
Une valeur par défaut quand T n'est pas fourniA default when T isn't provided
type Box<T = string> = { value: T };

const a: Box = { value: "x" };        // T par défaut = string
const b: Box<number> = { value: 42 }; // T fourni explicitement
La trousse à contraintesThe constraint toolkit
Borner & relierBound & relate
T extends CT assignable à CT assignable to C
keyof Tl'union des clés de Tthe union of T's keys
T[K]le type à la clé Kthe type at key K
AssouplirSoften
T = Ddéfaut si T absentdefault if T absent
T extends A & Bplusieurs contraintesseveral constraints
ButGoaljuste assez, jamais plusjust enough, never more
Les voisins, seulement nommés.The neighbors, only named.

L'extends d'une contrainte n'est pas l'héritage de classe : il veut dire « assignable à / sous-type de » (le structurel du Vol 1). Le même mot-clé reviendra dans les types conditionnels (T extends U ? X : Y) au Vol 6, avec un autre rôle. Pour plusieurs capacités, on intersecte : T extends A & B. Et les const type parameters (<const T>) gardent l'inférence littérale plutôt que de l'élargir.A constraint's extends is not class inheritance: it means "assignable to / subtype of" (Vol 1's structural typing). The same keyword returns in conditional types (T extends U ? X : Y) in Vol 6, with another role. For several capabilities, you intersect: T extends A & B. And const type parameters (<const T>) keep literal inference instead of widening it.

№ 04Le piègeThe trap4 min

Sur-contraindre rigidifie autant que sous-contraindre fragilise.Over-constraining rigidifies as much as under-constraining weakens.

« Juste assez » a deux falaises. À gauche, le générique trop libre qui ne relie rien — un any avec un chapeau, vu au numéro précédent. À droite, le piège propre au 5.2 : contraindre à plus que ce dont on se sert. Borner T à User entier alors qu'on ne lit que .id rejette tout objet qui aurait un id sans être un User — un order, un littéral de test, un mock. On a confondu « ce que je manipule » avec « ce dont j'ai besoin ».'Just enough' has two cliffs. On the left, the too-free generic that relates nothing — an any with a hat, seen last issue. On the right, the trap specific to 5.2: constraining to more than you use. Bounding T to a whole User when you only read .id rejects every object that has an id without being a User — an order, a test literal, a mock. You confused 'what I pass' with 'what I need'.

La contrainte trop large ferme la porte à des appelants validesThe too-wide constraint shuts out valid callers
// On ne lit que .id — mais on contraint à User tout entier :
function idOf<T extends User>(x: T): string {
  return x.id;
}

idOf(currentUser);              // ✓
idOf({ id: "x" });             // ✗ pas un User complet — rejeté pour RIEN
idOf(order);                    // ✗ order a pourtant un id… mais n'est pas un User

Exiger T extends User pour ne lire qu'un .id, c'est demander un passeport pour vérifier une initiale. Tout objet qui a la capacité requise mais pas le type entier est rejeté sans raison — la fonction perd sa réutilisabilité, et c'est le contraire du polymorphisme qu'on visait.Requiring T extends User just to read a .id is demanding a passport to check an initial. Any object that has the required capability but not the whole type is rejected for no reason — the function loses its reusability, the opposite of the polymorphism you aimed for.

Les deux extrêmes du même réflexe.The two extremes of the same reflex.

Trop libre : le <T> qu'on doit aussitôt caster à l'intérieur, ou qui n'apparaît qu'une fois — un any déguisé (5.1). Trop serré : la contrainte au type concret, qui rejette des appelants légitimes. Le bon dosage se trouve en lisant le corps : quelles propriétés, quelles méthodes est-ce que j'utilise vraiment ? La contrainte, c'est cette liste — exactement, et seulement elle.Too free: the <T> you must immediately cast inside, or that appears only once — an any in disguise (5.1). Too tight: the constraint to the concrete type, rejecting legitimate callers. The right dose is found by reading the body: which properties, which methods do I actually use? The constraint is that list — exactly, and only it.

La bonne contrainte n'est pas « d'où vient cette valeur », c'est « qu'est-ce que j'en fais ». Contrains la capacité que tu utilises — jamais l'identité que tu imagines.The right constraint isn't "where this value comes from", it's "what I do with it". Constrain the capability you use — never the identity you picture.
№ 05Bilan & éditoWrap-up & editorial2 min

Contraindre, c'est rendre capable — à la dose exacte.To constrain is to make capable — in the exact dose.

Un T libre ne permet presque rien : sans connaître ses capacités, on ne peut ni le lire, ni l'appeler. extends le borne à exactement la forme dont le corps se sert, et débloque cette capacité sans perdre le type précis. keyof et T[K] relient clés et valeurs ; un défaut adoucit l'appel. Tout l'art est le dosage : ni any déguisé, ni carcan au type concret.A free T allows almost nothing: without knowing its capabilities, you can neither read nor call it. extends bounds it to exactly the shape the body uses, and unlocks that capability without losing the precise type. keyof and T[K] relate keys and values; a default softens the call. The whole art is the dosage: neither any in disguise, nor a straitjacket to the concrete type.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Utiliser une capacité de TUse a capability of TT extends { la capacité }.T extends { the capability }.Tu n'as plus accès qu'à cette capacité, pas au reste.You only get that capability, nothing else.
Accéder à une clé en sûretéAccess a key safelyK extends keyof T, retour T[K].K extends keyof T, return T[K].Relie la clé à l'objet — refuse les clés absentes.Relates key to object — refuses absent keys.
Un type par défautA default typeT = Default.T = Default.Appliqué quand l'inférence et l'appel ne donnent rien.Applied when inference and the call give nothing.
Ne PAS sur-contraindreNOT over-constrainBorne à la capacité utilisée, pas au type concret.Bound to the used capability, not the concrete type.Sur-contraindre rejette des appelants valides.Over-constraining rejects valid callers.
Un générique sans contrainte est libre et impuissant ; trop contraint, capable et stérile. La bonne dose est la liste exacte des capacités que le corps emploie.An unconstrained generic is free and powerless; over-constrained, capable and barren. The right dose is the exact list of capabilities the body uses.
Mot de l'éditeurFrom the editor

« De quelle capacité ai-je besoin ? »"Which capability do I need?"

Le générique libre a quelque chose de paradoxal : il accepte tout, et ne permet rien. C'est cohérent — accepter tout type, c'est ne rien savoir d'aucun, donc ne pouvoir garantir aucune opération. La contrainte n'est pas une entrave : c'est le geste qui rend le générique utilisable. T extends { length: number } ne ferme pas T, il l'ouvre — il déclare la seule chose que le corps a besoin de savoir, débloque exactement cette capacité, et se tait sur le reste. Et il préserve le lien du numéro précédent : la forme est exigée à l'entrée, le type exact rendu à la sortie.The free generic is paradoxical: it accepts everything, and permits nothing. It's coherent — accepting every type means knowing nothing about any, hence guaranteeing no operation. The constraint isn't a shackle: it's the move that makes the generic usable. T extends { length: number } doesn't close T, it opens it — it declares the one thing the body needs to know, unlocks exactly that capability, and stays silent on the rest. And it preserves the previous issue's link: the shape is demanded at the input, the exact type returned at the output.

Le réflexe tient en une question posée au corps de la fonction : de quelle capacité ai-je réellement besoin ? On contraint à cette liste, exactement — ni le type concret qui rigidifie, ni rien qui rende T stérile. Reste une dernière question, plus retorse, que soulève tout générique sur un conteneur : si Cat est un sous-type d'Animal, est-ce qu'un Cat[] est un Animal[] ? Dans quel sens le sous-typage traverse-t-il un générique ? C'est la variance — covariance, contravariance, et le piège des paramètres de fonction — qui clôt le volume.The reflex is one question asked of the function body: which capability do I actually need? You constrain to that list, exactly — neither the concrete type that rigidifies, nor nothing that leaves T barren. One last, trickier question remains, raised by any generic over a container: if Cat is a subtype of Animal, is a Cat[] an Animal[]? In which direction does subtyping flow through a generic? That's variance — covariance, contravariance, and the trap of function parameters — which closes the volume.