Lesson 2/10 10 min

Types and inference

string, number, boolean… and inference: TypeScript guesses most types by itself. Annotate little, but well.

« Je vais devoir annoter chaque ligne ?! »

C'est la peur numéro un quand on découvre TypeScript. On imagine quelque chose comme ça :

const age: number = 30;
const prenom: string = 'Sam';
const actif: boolean = true;
const notes: number[] = [12, 15, 18];
const i: number = 0;

Un type partout, sur chaque déclaration, sur chaque boucle, sur chaque variable évidente. Ça ressemble à du Java des années 2000 : verbeux, répétitif, et franchement rébarbatif.

Bonne nouvelle : tout ce code est du TS valide, mais presque toutes ces annotations sont inutiles. TypeScript les déduit tout seul depuis la valeur initiale. Tu peux écrire exactement ça :

const age = 30;
const prenom = 'Sam';
const actif = true;
const notes = [12, 15, 18];
const i = 0;

Et le compilateur sait que age est un number, prenom une string, notes un number[], sans que tu n'écrives quoi que ce soit. C'est l'inférence de types.

L'inférence : TypeScript joue au détective

L'inférence, c'est le fait que TypeScript déduit le type d'une variable depuis sa valeur initiale, et tient ce contrat pour toujours ensuite. Dès la ligne de déclaration, le compilateur a tranché :

let score = 0;         // TS infère : number
let message = 'ok';    // TS infère : string
let actif = true;      // TS infère : boolean
let notes = [12, 15];  // TS infère : number[]

score = score + 10;    // ✓ number + number → number
score = 'dix';         // ✗ Type 'string' is not assignable to type 'number'

Un détail pour les const : avec let, TS infère le type large (number). Avec const, comme la valeur ne peut plus changer, TS infère le type littéral : const nom = 'Sam' a le type 'Sam' (pas juste string). Ce n'est pas critique pour l'instant, mais ça explique certains messages d'erreur en cas de réassignation.

Les types de base que TS manipule sont peu nombreux : string, number, boolean, null, undefined, les tableaux (number[], string[]…), et les objets. C'est tout ce dont tu as besoin pour 80 % des cas.

La règle d'or : laisse l'inférence travailler pour les variables ; annote les frontières. Les frontières, ce sont les paramètres de fonctions et les retours des API publiques, là où TS ne peut pas deviner ce que l'appelant va passer. C'est là que le contrat compte vraiment. À l'intérieur d'une fonction, laisse TS déduire.

Prédis avant de lire

let score = 0; : aucune annotation nulle part. Plus loin dans le code : score = "dix";. À ton avis : que dit TypeScript, et d'où sort-il cette information ?

Voir la réponse

TypeScript répond : « Type 'string' is not assignable to type 'number' ». Il n'a pas eu besoin d'une annotation : dès = 0, il a inféré que score est un number, et tient ce contrat pour toutes les lignes qui suivent. La string "dix" viole ce contrat : refus immédiat, sans exécuter une ligne.

À toi : l'inférence qui veille

Clique sur Vérifier les types : TS n'a vu aucune annotation, et pourtant il refuse. Lis le message, corrige la ligne fautive (score = "dix";score = 10;), et revérifie jusqu'au ✓ 0 erreur.

🧐 Labo TypeScript · l'inférence en action (mode strict)
Bloqué sur la correction ? Voir le fix

Remplace score = "dix"; par score = 10;. L'inférence depuis = 0 a figé le contrat number sans que tu écrives quoi que ce soit : c'est exactement ce que le compilateur défendait.

Quand annoter explicitement ?

L'inférence est puissante, mais elle a ses limites. Trois situations où tu dois annoter :

1. Les paramètres de fonctions. TS ne peut pas deviner ce que l'appelant va passer : tu dois l'écrire.

function doubler(n: number): number {
    return n * 2;
}
// Sans annotation de paramètre → n est implicitement 'any' → interdit en strict

2. Les retours des fonctions exportées / API publiques. Même si TS peut souvent les inférer, l'annoter explicitement documente le contrat pour l'appelant (et pour l'IA qui génère du code).

3. Les variables déclarées avant d'être remplies. TS ne peut pas inférer depuis… rien :

let resultat: number;   // déclarée sans valeur → annotation obligatoire
if (condition) {
    resultat = 42;
} else {
    resultat = 0;
}
console.log(resultat * 2);

Sur-annoter ne protège pas mieux. const age: number = 30 ajoute du bruit redondant : l'inférence donne exactement le même contrat. Pire : noyer le code d'annotations évidentes rend moins visible les annotations qui comptent vraiment (celles des frontières). Le bruit cache le signal utile.

Schéma de l'inférence : à gauche « let score = 0 » (aucune annotation) ; au centre une boîte verte « Inférence : je déduis number » avec une loupe ; à droite deux usages : score + 10 avec une coche verte (OK), score = "dix" avec une croix rouge (erreur de type). let score = 0 aucune annotation Inférence je déduis : number pour toujours ✓ score + 10 number + number → OK ✗ score = "dix" string ≠ number → refus
L'inférence déduit number depuis = 0 et défend ce contrat sur tous les usages suivants.

Et la suite ? Tu sais typer des variables et des fonctions simples. La vraie question arrive : comment décrire la forme d'un objet, ses propriétés, leurs types, ce qui est obligatoire ou optionnel ? C'est le terrain des interfaces et des type aliases : leçon 3.

"Do I have to annotate every single line?!"

That's the number-one fear when first discovering TypeScript. You picture something like this:

const age: number = 30;
const firstName: string = 'Sam';
const active: boolean = true;
const scores: number[] = [12, 15, 18];
const i: number = 0;

A type on everything — every declaration, every loop counter, every obvious variable. It looks like Java from the early 2000s: verbose, repetitive, and honestly tedious.

Good news: all of that is valid TS, but almost every one of those annotations is unnecessary. TypeScript deduces them automatically from the initial value. You can write this instead:

const age = 30;
const firstName = 'Sam';
const active = true;
const scores = [12, 15, 18];
const i = 0;

And the compiler knows that age is a number, firstName a string, scores a number[]without you writing anything. That's type inference.

Inference: TypeScript plays detective

Inference means TypeScript deduces the type of a variable from its initial value — and enforces that contract from then on. The moment you write the declaration, the compiler has decided:

let score = 0;         // TS infers: number
let message = 'ok';    // TS infers: string
let active = true;     // TS infers: boolean
let scores = [12, 15]; // TS infers: number[]

score = score + 10;    // ✓ number + number → number
score = 'ten';         // ✗ Type 'string' is not assignable to type 'number'

A detail about const: with let, TS infers the wide type (number). With const, since the value can't change, TS infers the literal typeconst name = 'Sam' has type 'Sam' (not just string). Not critical for now, but it explains some error messages if you try to reassign.

The base types TS works with are few: string, number, boolean, null, undefined, arrays (number[], string[]…), and objects. That covers 80% of all cases.

The golden rule: let inference work for variables; annotate the boundaries. Boundaries are function parameters and return types of public APIs — that's where TS can't guess what the caller will pass. That's where the contract matters most. Inside a function body, let TS figure it out.

Predict before reading on

let score = 0; — no annotation anywhere. Later: score = "ten";. Your guess: what does TypeScript say, and where does that information come from?

Show the answer

TypeScript says: "Type 'string' is not assignable to type 'number'". It didn't need any annotation: from = 0, it inferred that score is a number, and holds that contract for every subsequent line. The string "ten" breaks it — rejected immediately, no execution needed.

Your turn: inference on guard

Click Check the types — TS has seen no annotation at all, and it still refuses. Read the message, fix the offending line (score = "ten";score = 10;), and re-check until ✓ 0 errors.

🧐 TypeScript lab · inference in action (strict mode)
Stuck on the fix? Show it

Replace score = "ten"; with score = 10;. The inference from = 0 locked in the number contract without you writing a single type annotation — that's exactly what the compiler was enforcing.

When to annotate explicitly?

Inference is powerful, but it has limits. Three situations where you must annotate:

1. Function parameters. TS can't guess what the caller will pass — you have to say it.

function double(n: number): number {
    return n * 2;
}
// Without a parameter annotation → n is implicitly 'any' → forbidden in strict mode

2. Return types of exported functions / public APIs. Even when TS can often infer them, annotating explicitly documents the contract for the caller (and for the AI generating code).

3. Variables declared before being filled. TS can't infer from… nothing:

let result: number;   // declared without a value → annotation required
if (condition) {
    result = 42;
} else {
    result = 0;
}
console.log(result * 2);

Over-annotating doesn't add protection. const age: number = 30 is redundant noise — inference gives you the exact same contract. Worse: burying code in obvious annotations makes it harder to see the annotations that actually matter (the boundary ones). Noise hides the useful signal.

Inference diagram: on the left "let score = 0" (no annotation); in the centre a green box "Inference — I deduce: number" with a magnifying glass; on the right two usages — score + 10 with a green tick (OK), score = "ten" with a red cross (type error). let score = 0 no annotation Inference I deduce: number for ever after ✓ score + 10 number + number → OK ✗ score = "ten" string ≠ number → rejected
Inference deduces number from = 0 and enforces that contract on every subsequent usage.

What's next? You can now type variables and simple functions. The real question is coming: how do you describe the shape of an object — its properties, their types, what's required or optional? That's the territory of interfaces and type aliases — lesson 3.

🎯 Pratique

S'entraîner (clique pour ouvrir) :

💬 Ré-explique sans regarder
Ré-explique sans regarder

Explique l'inférence et la règle « annoter les frontières ». Pourquoi laisse-t-on l'inférence travailler sur les variables, et où annote-t-on vraiment ?

Une bonne explication dit : l'inférence = TS déduit le type depuis la valeur initiale (= 0number) et tient ce contrat sur toutes les lignes suivantes. On annote les frontières : paramètres de fonctions et retours des API publiques, parce que c'est là que TS ne peut pas deviner ce que l'appelant va passer. À l'intérieur d'une fonction, on laisse l'inférence ; sur-annoter est du bruit qui noie les annotations utiles.
🧠 Rappel libre
Rappel libre

De mémoire : cite les types de base de TypeScript. Que déduit TS pour let actif = true et const notes = [12, 15] ?

Les types de base : string, number, boolean, null, undefined, les tableaux (number[], string[]…), les objets. Pour let actif = true : TS déduit boolean. Pour const notes = [12, 15] : TS déduit number[] (un tableau de nombres).
⚖️ Juge le code de l'IA
Accepter ou rejeter le code de l'IA

L'IA génère une boucle : for (const i: number = 0; i < 10; i++). Elle annote i partout « pour être plus sûre ». Tu acceptes, ou tu rejettes ?

À rejeter (ou faire nettoyer). L'annotation : number sur i n'ajoute aucune sécurité : l'inférence depuis = 0 donne exactement le même contrat. Ce que ça fait en revanche : polluer la lisibilité et noyer les annotations qui comptent vraiment (celles des paramètres de fonctions). Demande à l'IA de supprimer les annotations redondantes : la rigueur utile est aux frontières, pas sur chaque compteur de boucle.
Que fait l'inférence de types ?
Où faut-il annoter explicitement en priorité ?
const fruits = ['pomme', 'poire'] : quel type TypeScript infère-t-il ?
Pourquoi écrire const age: number = 30 est-il déconseillé ?
Next step

You know TypeScript guesses variable types. But how do you describe the shape of an object — its properties, their types, what's required or optional? That's the territory of interfaces and type aliases — and typos on property names become a thing of the past.

Lesson 3: Interfaces and type aliases →
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement