Vol. 2 · № 03 · Un atelier TypeScriptA TypeScript workshop

Type guards & assertion functions, ou la preuve qui traverse une fonction Type guards & assertion functions, or the proof that crosses a function

Sortir un test dans une fonction, et le narrowing disparaît : un boolean ne dit pas QUOI on a prouvé. Le prédicat x is Foo le fait voyager — mais le compilateur ne vérifie jamais que le corps tient la promesse. Un guard qui ment compile. C'est un as déguisé, en pire. Extract a test into a function, and narrowing vanishes: a boolean doesn't say WHAT you proved. The x is Foo predicate carries it across — but the compiler never checks the body keeps the promise. A lying guard compiles. It's an as in disguise, but worse.

AudienceAudience
Dev qui maîtrise le narrowing et veut l'emballer dans des fonctions Dev fluent in narrowing who wants to wrap it into functions
Format
Self-paced
ChapitresChapters
5
Date
Juin 2026 Jun 2026
≈ 18 min ●●○○ Type guardAssertionTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

Le test marche. Mais sorti dans une fonction, la preuve reste à l'intérieur.The test works. But pulled into a function, the proof stays inside.

Le narrowing du numéro précédent vivait dans un if, sur place. Dès qu'on factorise ce test dans une fonction, le type rétréci s'évapore : un retour boolean dit seulement « vrai ou faux », jamais « et donc x est un string ». Le prédicat de type x is string est la seule façon de faire voyager cette preuve à travers la frontière de la fonction.The narrowing of the previous issue lived in an if, on the spot. The moment you factor that test into a function, the narrowed type evaporates: a boolean return only says 'true or false', never 'and therefore x is a string'. The type predicate x is string is the only way to carry that proof across the function boundary.

Le retour boolean qui perd la preuveThe boolean return that loses the proof
function isString(x: unknown): boolean {   // ': boolean' écrit à la main — sans lui,
  return typeof x === "string";                // TS 5.5+ inférerait x is string (voir ch. 3)
}

function f(x: unknown) {
  if (isString(x)) {
    x.toUpperCase();   // ✗ x est toujours 'unknown' — boolean ne dit pas QUOI
  }
}
Ce qui franchit le returnWhat crosses the return
: boolean
« vrai » / « faux »"true" / "false"
la forme prouvée reste dedansthe proven shape stays inside
: x is string
« vrai, et x est string »"true, and x is string"

Un boolean répond à la question mais oublie ce qu'il a appris. Le prédicat x is string emporte la conclusion avec lui — le narrowing reprend de l'autre côté de l'appel.A boolean answers the question but forgets what it learned. The predicate x is string takes the conclusion with it — narrowing resumes on the far side of the call.

Le prédicat qui fait voyager la preuveThe predicate that carries the proof
function isString(x: unknown): x is string {
  return typeof x === "string";
}

function f(x: unknown) {
  if (isString(x)) {
    x.toUpperCase();   // ✓ x : string — la preuve a traversé la fonction
  }
}
Le réflexe du numéroThe issue's reflex

« Cette fonction qui teste — rend-elle un simple oui/non, ou une preuve réutilisable ? » Si le résultat doit rétrécir un type chez l'appelant, son retour doit être un prédicat, pas un boolean."This function that tests — does it return a plain yes/no, or a reusable proof?" If the result must narrow a type for the caller, its return must be a predicate, not a boolean.

Pourquoi ce numéro ferme le Vol 2.Why this issue closes Vol 2.

L'effacement a posé le problème (le type s'évapore), le narrowing a montré comment le compilateur reconstruit une preuve sur place. Restait à la rendre transportable — réutilisable, nommée, partagée. Le type guard est ce dernier maillon : la preuve devient une fonction.Erasure posed the problem (the type evaporates), narrowing showed how the compiler rebuilds a proof on the spot. What remained was making it portable — reusable, named, shared. The type guard is that final link: the proof becomes a function.

№ 02La surpriseThe surprise4 min

Le compilateur vérifie que tu rends un boolean. Pas que tu dis vrai.The compiler checks you return a boolean. Not that you tell the truth.

Voici le marché caché du type guard. Quand tu écris : x is User, tu fais une promesse — « si je rends true, alors x est un User ». Le compilateur ne vérifie jamais que le corps tient cette promesse. Il te croit sur parole. La responsabilité de la correction bascule entièrement sur toi.Here's the hidden bargain of the type guard. When you write : x is User, you make a promise — 'if I return true, then x is a User'. The compiler never checks that the body keeps that promise. It takes your word. The responsibility for correctness shifts entirely onto you.

La seule chose contrôlée : le type de retourThe only thing checked: the return type
function isString(x: unknown): x is string {
  return typeof x === "string";
  // ✓ le compilateur vérifie UNE chose : que tu rends bien un boolean.
}

Le compilateur s'assure que la fonction renvoie bien un boolean — c'est tout son contrôle. Le lien entre ce boolean et la forme x is string, il ne le vérifie pas : il l'accepte comme une déclaration.The compiler makes sure the function returns a boolean — that's the whole of its check. The link between that boolean and the shape x is string, it does not verify: it accepts it as a declaration.

Un type guard est une promesse que le compilateur ne lit pas. Tout son pouvoir, et tout son danger, tiennent dans la justesse de son corps.A type guard is a promise the compiler doesn't read. All its power, and all its danger, rest on the correctness of its body.
№ 03La mécaniqueThe mechanics5 min

Deux formes de la même promesse : retourner, ou lever.Two forms of the same promise: return, or throw.

Le prédicat de type (v is Point) rend un boolean : on l'utilise dans un if, et le narrowing s'applique dans la branche. L'assertion function (asserts v is Point) ne rend rien : elle lève si la forme est mauvaise, et narrowe pour tout ce qui suit l'appel — sans if. Même promesse, deux régimes de flot de contrôle.The type predicate (v is Point) returns a boolean: you use it in an if, and narrowing applies inside the branch. The assertion function (asserts v is Point) returns nothing: it throws if the shape is wrong, and narrows everything after the call — with no if. Same promise, two control-flow regimes.

Le prédicat — un vrai contrôle de formeThe predicate — a real shape check
type Point = { x: number; y: number };

function isPoint(v: unknown): v is Point {
  return typeof v === "object" && v !== null
    && "x" in v && typeof v.x === "number"   // "x" in v suffit à rétrécir (TS 4.9+)
    && "y" in v && typeof v.y === "number";
}

if (isPoint(data)) data.x;   // ✓ data : Point dans le bloc
L'assertion — lève au lieu de retournerThe assertion — throws instead of returning
function assertIsPoint(v: unknown): asserts v is Point {
  if (!isPoint(v)) throw new Error("pas un Point");
}

assertIsPoint(data);
data.x;   // ✓ data : Point — SANS if, pour tout le reste de la fonction
Lequel, quand ?Which, when?
v is Point
RendReturnsun booleana boolean
S'utiliseUsed inun if / filter / ternairean if / filter / ternary
Si fauxIf falsel'autre branche, normalementthe other branch, normally
PourForbrancher selon la formebranching on the shape
asserts v is Point
RendReturnsrien (void)nothing (void)
S'utiliseUsed inun appel sec, en gardea bare call, as a guard
Si fauxIf falselève une exceptionthrows an exception
PourForexiger la forme et continuerdemanding the shape and moving on
Les voisins, seulement nommés.The neighbors, only named.

Variante plus simple : asserts v (sans is T) retire juste les valeurs falsy — l'équivalent typé d'un assert(x). Un asserts doit toujours être explicite : TypeScript ne l'infère jamais. Un prédicat, depuis TS 5.5, peut s'inférer d'un corps simple — (x: unknown) => typeof x === "string", ou le callback d'un .filter — mais seulement quand true signifie exactement « c'est ce type » ; pour un guard de forme comme isPoint, on garde la signature explicite.A simpler variant: asserts v (without is T) just removes falsy values — the typed equivalent of an assert(x). An asserts must always be explicit: TypeScript never infers one. A predicate, since TS 5.5, can be inferred from a simple body — (x: unknown) => typeof x === "string", or a .filter callback — but only when true means exactly "it's this type"; for a shape guard like isPoint, you keep the explicit signature.

№ 04Le piègeThe trap4 min

Le guard écrit à la main copie le type. Et les copies divergent.The hand-written guard copies the type. And copies drift apart.

Un type guard correct aujourd'hui ne le reste pas. Il duplique, en code impératif, la forme déjà décrite par le type — deux descriptions de la même chose, qu'aucun lien n'oblige à rester d'accord. Le jour où le type gagne un champ, le guard l'ignore, continue de dire « oui », et le mensonge du chapitre 2 revient par la petite porte.A type guard that's correct today won't stay so. It duplicates, in imperative code, the shape already described by the type — two descriptions of the same thing, with nothing forcing them to agree. The day the type gains a field, the guard ignores it, keeps saying 'yes', and chapter 2's lie sneaks back in.

Deux descriptions qui se désynchronisentTwo descriptions that fall out of sync
type User = { name: string; age: number };   // ← age ajouté l'an dernier

function isUser(x: unknown): x is User {
  // écrit quand User n'avait que name — jamais remis à jour
  return typeof x === "object" && x !== null && "name" in x;
}

if (isUser(data)) {
  data.age.toFixed();   // 💥 age peut être absent : le guard a menti en silence
}

Le type dit « name et age » ; le guard ne vérifie que name. Rien ne signale l'écart — le guard compile, ment, et le compilateur propage sa fausse preuve. Un guard à la main est une dette : il faut le maintenir à chaque évolution du type.The type says "name and age"; the guard only checks name. Nothing flags the gap — the guard compiles, lies, and the compiler propagates its false proof. A hand-written guard is a debt: it must be maintained on every change to the type.

Un guard à la main est un double du type. Deux vérités finissent toujours par diverger — fais-en dériver une de l'autre.A hand-written guard is a duplicate of the type. Two truths always end up diverging — derive one from the other.
№ 05Bilan & éditoWrap-up & editorial2 min

Une preuve qu'on emballe — et dont on répond.A proof you wrap — and answer for.

Le type guard est le dernier maillon du Vol 2 : il transforme une vérification runtime en une preuve transportable, nommée, réutilisable. Mais c'est un pont dont tu es le garant — le compilateur le traverse les yeux fermés, en se fiant à ta parole.The type guard is Vol 2's final link: it turns a runtime check into a portable, named, reusable proof. But it's a bridge you guarantee — the compiler crosses it blindfolded, trusting your word.

Tu veux…You want to…Le gesteThe moveCe que ça coûteWhat it costs
Faire voyager une preuveCarry a proof acrossUn prédicat : function isFoo(x): x is Foo, utilisé dans un if.A predicate: function isFoo(x): x is Foo, used in an if.Le compilateur ne vérifie pas le corps : tu réponds de sa justesse.The compiler doesn't check the body: you answer for its correctness.
Exiger une forme et continuerDemand a shape and move onUne assertion : function assertFoo(x): asserts x is Foo.An assertion: function assertFoo(x): asserts x is Foo.Lève à l'exécution si la forme manque — il faut gérer l'exception.Throws at runtime if the shape is missing — you must handle the exception.
Retirer juste les falsyRemove only the falsyasserts x (sans is T) — un assert typé.asserts x (without is T) — a typed assert.Signature explicite : TS 5.5+ n'infère que les prédicats simples, jamais un asserts.Explicit signature: TS 5.5+ only infers simple predicates, never an asserts.
Valider une donnée du dehorsValidate outside dataDériver le guard d'un schéma (z.infer + safeParse). Vol 7.Derive the guard from a schema (z.infer + safeParse). Vol 7.Une dépendance de plus — mais une seule vérité, qui ne dérive jamais.One more dependency — but a single truth, that never drifts.
Un prédicat de type ne dit pas au compilateur ce qui est vrai. Il dit ce dont tu te portes garant — à charge pour toi de ne pas mentir.A type predicate doesn't tell the compiler what is true. It says what you vouch for — and it's on you not to lie.
Mot de l'éditeurFrom the editor

« J'ai vérifié, fais-moi confiance. »"I checked, trust me."

Le type guard est l'endroit le plus honnête et le plus dangereux de TypeScript à la fois. Honnête, parce qu'il dit explicitement ce qu'un as fait en douce : « je prends sur moi de garantir cette forme ». Dangereux, pour la même raison — le compilateur s'incline devant la déclaration sans jamais relire le corps. Un guard juste est un cadeau qui se réutilise partout ; un guard faux, ou simplement périmé, est un mensonge qui se propage à tous les sites d'appel, avec l'autorité d'une vérification. D'où la règle qui clôt le volume : si la forme vient du dehors, ne l'écris pas à la main — fais-la dériver d'une seule source de vérité.The type guard is at once the most honest and most dangerous spot in TypeScript. Honest, because it states out loud what an as does on the sly: 'I take it on myself to guarantee this shape'. Dangerous, for the same reason — the compiler bows to the declaration without ever rereading the body. A correct guard is a gift reused everywhere; a wrong one, or merely stale, is a lie that spreads to every call site, with the authority of a check. Hence the rule that closes the volume: if the shape comes from outside, don't write it by hand — derive it from a single source of truth.

Fin du Vol 2 : effacement, narrowing, guards — on sait reconnecter, à l'exécution, le monde des valeurs et celui des types. Prochain volume : rendre l'état illégal irreprésentable. Plutôt que de vérifier après coup qu'une forme est valide, on va concevoir des types où la forme invalide ne se construit même pas — et le narrowing, devenu réflexe, en sera la clé de lecture.End of Vol 2: erasure, narrowing, guards — you can reconnect, at runtime, the world of values and that of types. Next volume: making illegal states unrepresentable. Rather than checking after the fact that a shape is valid, we'll design types where the invalid shape can't even be built — and narrowing, now a reflex, will be the key to reading them.