TypeScript, c’est bien plus encore que des types de base comme string ou number, ou même des interfaces. En fait, pour vraiment tirer parti de la puissance de TypeScript, il faut maîtriser ses types avancés : unions, intersections, et types utilitaires. Et c'est ce que je vous propose de voir aujourd'hui !
Dans cet article, nous verrons les unions (|
), les intersections (&
), et les types utilitaires comme Partial
, Pick
, et Omit
, pour résoudre des problèmes concrets en TypeScript. Comme toujours, dans l'idée de typer des cas complexes avec précision, tout en gardant un code clair et maintenable. Ça vous dit ? Alors en avant !
Remarque : pour voir ces types avancés en pratique, n'hésitez pas à faire un saut dans la rubrique "Projets", où vous trouverez tout un tas d'exemples de codes TS fortement typés, notamment avec ces types utilitaires !
Salut ! Bienvenue sur LeCoinTS, où je partage des tutos TypeScript gratuits et sans pub. Envie de me soutenir dans cette aventure, avec un café ? ☕

Les UNIONS TypeScript (opérateur |), pour autoriser tel "OU" tel type
Les unions, notées avec |
, permettent à une variable ou un paramètre de fonction d’accepter plusieurs types. C’est ce qui nous permet à nous, développeur TypeScript, d'indiquer la "liste de tous les types" qu'une variable ou paramètre de fonction peut prendre.
Dit comme ça, c'est peut-être pas très clair ! Alors prenons un exemple, pour illustrer cela :
const afficherId = (id: string | number): void => {
if(typeof id === "number") console.log(`ID numérique : ${id}`);
if(typeof id === "string") console.log(`ID texte : ${id}`);
}
afficherId("ABC123"); // Retour : "ID texte : ABC123"
afficherId(123); // Retour : "ID numérique : 123"
afficherId(true); // ERREUR : 'boolean' n’est pas assignable à 'string | number'
Ici, nous avons créé une fonction nommée afficherId
, qui comme son nom l'indique, permet d'afficher un identifiant (avec console.log). Cette fonction a un paramètre, nommé id
. Ce paramètre devra être une chaîne de caractère (string) ou une valeur numérique (number), comme il a été définit dans la fonction (avec id: string | number
).
Dans cet exemple, on voit que si l'on appelle cette fonction avec une chaîne de caractères ("ABC123") ou un nombre (123) en argument, alors la fonction retourne le bon texte en conséquence. Par contre, si on met un booléen (ou tout autre type que string ou number), alors notre éditeur nous remonte une erreur.
Bonne pratique : utilisez typeof
, instanceof
, ou ce qu'on appelle des "type guards" en anglais, pour déterminer quel est le type de telle ou telle variable.
Remarque générale, sur les Unions : Il peut être déroutant qu'une union de types semble avoir l'intersection des propriétés de ces types. En fait, il n'y a pas d'erreur ici, car tout est une question de point de vue. Pour une union, TypeScript combine les types afin qu'on puisse en choisir un parmi tous (d'où l'opérateur |, qui est un "OU" logique). Si ce n'est pas clair pour vous, ce n'est pas grave. C'était juste pour ceux qui se poseraient la question, en se disant qu'une union serait plus logique avec un "ET" plutôt qu'un "OU" 😉
À présent, passons aux intersections !
Juste une parenthèse, pour ceux qui ne sauraient pas ce qu'est un "type guard" : c'est en fait un vérificateur de type, pour ainsi dire. Pour que ce soit plus explicite, voici un exemple de type guard, qui teste si une valeur donnée est de type string ou non :
const valeur1: string = "texte";
const valeur2: number = 123546;
const estString = (valeur: string | number): valeur is string => {
return typeof valeur === "string";
}
console.log(estString(valeur1) ? "Valeur1 est un string" : "Valeur1 n'est pas de type string");
// Retour : "Valeur1 est un string"
console.log(estString(valeur2) ? "Valeur2 est un string" : "Valeur2 n'est pas de type string");
// Retour : "Valeur2 n'est pas de type string"
Remarquez bien le "is string" en sortie de fonction, car c'est ce qui fait qu'un type guard est différent des fonctions habituelles 😉
Les INTERSECTIONS TypeScript (opérateur &), pour combiner tel "ET" tel type
Les intersections, notées avec &, combinent plusieurs types en un seul. En fait, c’est utile pour fusionner des interfaces ou créer des objets, avec des propriétés multiples. Là encore, je vais immédiatement vous mettre un exemple de code, afin que ce soit plus parlant !
interface Animal {
nombre_de_pattes: number;
couleurs: string[];
}
interface ComplementPourChien {
type_de_poils: string;
forme_des_oreilles: string;
}
interface ComplementPourOiseau {
type_de_plumes: string;
forme_du_bec: string;
}
type Chien = Animal & ComplementPourChien;
type Oiseau = Animal & ComplementPourOiseau;
const monChien: Chien = {
nombre_de_pattes: 4,
couleurs: ["blanc", "noir"],
type_de_poils: "court",
forme_des_oreilles: "pointues"
}
const monOiseau: Oiseau = {
nombre_de_pattes: 2,
couleurs: ["jaune", "bleu", "rouge"],
type_de_plumes: "duvet",
forme_du_bec: "pointu"
}
console.log(monChien)
console.log(monOiseau)
Ici, j'ai écris un exemple avec des animaux, et plus précisément, 2 familles d'animaux : les chiens, et les oiseaux (désolé pour mon manque d'imagination !). En fait, ces animaux ont des choses en commun (ce qui est décrit dans l'interface "Animal") et des choses qui sont spécifiques à leur famille (décrit dans les interfaces "ComplementPourChien" et "ComplementPourOiseau"). Du coup, pour définir ce qu'est un type Chien ou un type Oiseau, on réalise une intersection (opérateur &
) de l'interface Animal avec leur interface complémentaire, afin d'obtenir un type résultant de l'ensemble des propriétés de ces deux interfaces.
Voici d'ailleurs ce qu'on trouve en sortie, avec ces console.log :
====> console.log de "monChien"
{
"nombre_de_pattes": 4,
"couleurs": [
"blanc",
"noir"
],
"type_de_poils": "court",
"forme_des_oreilles": "pointues"
} ====> console.log de "monOiseau"
{
"nombre_de_pattes": 2,
"couleurs": [
"jaune",
"bleu",
"rouge"
],
"type_de_plumes": "duvet",
"forme_du_bec": "pointu"
}
Ici, on voit bien qu'au final l'opérateur "&" combine plusieurs types, pour faire quelque chose de "plus grand" !
Les types utilitaires Partial, Pick, Omit, Record, et ReturnType
Pour notre plus grand bonheur, TypeScript fournit des types utilitaires intégrés pour manipuler les types existants ! Vous ne savez-pas de quoi il s'agit ? Alors laissez moi vous faire découvrir tout ça !
Remarque : dans ce qui suit, vous verrez des types utilitaires avec des <T>, <K,T>, <T,K> etc. à leur niveau. Déjà, n'ayez pas peur ! Car c'est juste un façon de noter des types quelconques ou particuliers dedans. En fait, ce sont des placeholder (comme on dit en anglais), c'est à dire des éléments génériques provisoires, à remplacer par les types que vous souhaitez y mettre. Par exemple, si je vous parle du type Partial<T>
, voici un exemple d'utilisation dans votre programme : ProfilPartiel = Partial<ProfilComplet>
. Tout ça pour dire que les "T", "K", ou toute autre lettre pourront être remplacé par vos types, dans votre code. Du reste, sachez que les lettres T, U, V, …, K, … sont souvent utilisées par convention en TypeScript ("T" signifiant Type, d'où cette lettre à l'origine, "K" pour keys signifiant un jeu de clés, etc) ; c'est pourquoi je l'utilise ici !
Type Partial<T> : pour rendre toutes les propriétés optionnelles
Alors, pour commencer, voyons le type Partial ! En fait, le type Partial permet de rendre optionnelles toutes les propriétés du type qu'il vise. Car sinon, comme vous le savez, toutes les propriétés d'un type donné doivent obligatoirement être renseignées (à l'exception de celles notées avec un ?
à la fin de leur nom). Du coup, en quelque sorte, utiliser Partial
revient à placer virtuellement un "?" au niveau de chaque propriété, pour qu'elles deviennent toutes optionnelles (mais juste en écrivant Partial, ça va plus vite !).
Pour être plus clair, voici un exemple d'utilisation, avec et sans Partial :
interface Produit {
id: number;
nom: string;
categorie: string;
origine: string;
fournisseur: string;
prix: number;
tvaApplicable: number;
}
const nouveauProduit: Produit = {
id: 1,
nom: "monNouveauProduit"
};
// Sans Partial, TOUS les paramètres de l'interface Produit sont requis (sauf ceux qui auraient un ? la fin de leur nom)
// Du coup, TypeScript nous remonte l'erreur suivante : Type '{ id: number; nom: string; }' is missing the
// following properties from type 'Produit': categorie,
// origine, fournisseur, prix, tvaApplicable
const miseAJourProduit: Partial<Produit> = {
id: 1,
fournisseur: "monNouveauFournisseur"
};
// Ici, il n'y a aucune erreur, car les propriétés de l'interface Produit sont devenus optionnelles ; du coup, on renseigne
// uniquement les propriétés qui nous intéresse, en oubliant volontairement les autres
Ici, comme vous l'aurez compris :
- Sans Partial, TypeScript nous remonte une erreur comme quoi nous n'avons pas caractérisé toutes les propriétés de l'interface Produit
- Avec Partial, TypeScript nous permet de renseigner uniquement les propriétés donc nous avons besoin, faisant fi des autres
Mais concrètement parlant, me direz-vous, dans quels cas utiliser Partial ? Eh bien … ce peut être par exemple le cas avec une API REST, où l'on voudrait utiliser la méthode PATCH
pour mettre à jour certains champs uniquement, sans toucher aux autres. Ce peut être également le cas pour modifier un ou plusieurs des paramètres par défaut d'un élément, sans toucher au reste. Ou plus globalement, chaque fois qu'on voudra toucher que partiellement (d'où le terme "Partial") à quelque chose !
Voici un autre exemple, utilisant Partial
dans une fonction (qui devrait être plus parlant pour vous !) :
interface Config {
theme: string;
langue: string;
debug: boolean;
}
const configParDefaut: Config = { theme: "clair", langue: "fr", debug: false };
console.log("Config avant modif :", configParDefaut)
const configurerApplication = (options: Partial<Config> = {}) => {
return { ...configParDefaut, ...options };
}
const nouvelleConfig = configurerApplication({ theme: "sombre" }); // Seulement le thème est spécifié
console.log("Config après modif :", nouvelleConfig)
Ici, on ne va modifier que la propriété "theme" de l'interface "Config" ; et grâce à Partial, nous n'aurons pas besoin de repréciser la valeur de toutes les autres propriétés ("langue" et "debug", dans ce cas). On peut d'ailleurs vérifier cela, en jetant un coup d'œil au retour console :
[LOG]: "Config avant modif :", {
"theme": "clair",
"langue": "fr",
"debug": false
}
[LOG]: "Config après modif :", {
"theme": "sombre",
"langue": "fr",
"debug": false
}
Avec ce dernier exemple, mettant en œuvre Partial dans une fonction, je pense que vous comprenez mieux à présent son utilité (pour partiellement mettre à jour des données, donc !). Alors passons au suivant, le bien nommé : Pick !
Vous aimez cet article ? LeCoinTS reste gratuit et sans pub grâce à vos dons.
Motivez-moi à en faire plus ! ☕

Type Pick<T, K> : pour sélectionner uniquement les propriétés qui nous intéresse
Deuxième type avancé que nous allons voir ici : Pick<T, K>. À noter ici que T et K sont deux placeholder, pour rappel, c'est à dire des lettres arbitraires signifiant des types quelconques ou particuliers ; mais par convention, on utilise la lettre "T" pour dire qu'on va utiliser un "Type" donné, et "K" pour dire qu'on va utiliser des "Keys" dessus (des clés de filtrage, par exemple, pour obtenir un sous-ensemble). Et tout ça, comme d'habitude, pour rendre le code plus intuitif !
Alors, que fait Pick
exactement ? Pour faire simple, utiliser Pick sur un type permet de sélectionner certaines propriétés uniquement, parmi toutes celles disponibles. Voici un exemple l'illustrant :
interface Utilisateur {
id: number;
nom: string;
email: string;
}
type UtilisateurSansEmail = Pick<Utilisateur, "id" | "nom">;
const user: UtilisateurSansEmail = {
id: 1,
nom: "toto"
// email non requis, car on a "pick" uniquement les champs id et nom
};
Comme vous l'aurez compris, Pick permet de ne garder que les propriétés qui nous intéresse, afin de définir un nouveau type, plus concis (avec un nombre de propriété moindre, donc). Cette sélection de propriétés se voit bien dans l'exemple précédent, avec le filtre de retenue "id" | "nom"
s'appliquant sur l'interface Utilisateur
.
Voici un autre exemple, mettant en œuvre Pick dans une fonction, si ça peut être plus parlant pour vous !
interface Utilisateur {
id: number;
nom: string;
email: string;
motDePasse: string;
dateInscription: Date;
}
function afficherNomEtEmailUtilisateur(data: Pick<Utilisateur, "nom" | "email">) {
console.log(`Nom : ${data.nom}, Email: ${data.email}`);
}
afficherNomEtEmailUtilisateur({ nom: "Toto", email: "toto@example.com" });
// OK
afficherNomEtEmailUtilisateur({ nom: "Toto", motDePasse: "123" });
// Génère une erreur TypeScript, car 'motDePasse' n'est pas attendu : Object literal may only specify known properties,
// and 'motDePasse' does not exist in type
// 'Pick<Utilisateur, "nom" | "email">'
Ici, on voit bien le garde-fou que nous procure Pick en TypeScript, en cadrant les choses attendues ! Bon passons au mot clef suivant !
Type Omit<T, K> : pour exclure des propriétés spécifiques
Le type Omit
est le pendant de Pick
, pour ainsi dire ! Car autant Pick nous permettait de garder que les propriétés souhaitées, autant Omit nous permet de retirer des propriétés non souhaitées.
Voici un exemple de code, mettant en œuvre Omit :
interface Utilisateur {
id: number;
nom: string;
motDePasse: string;
}
type UtilisateurPublic = Omit<Utilisateur, "motDePasse">;
const user1: UtilisateurPublic = { id: 1, nom: "Toto" };
// OK ! La propriété "motDePasse" a été exclue, donc tout va bien
const user2: UtilisateurPublic = { id: 2, nom: "Titi", motDePasse: "123456" };
// Là, ça génère une erreur TypeScript : Object literal may only specify known properties,
// and 'motDePasse' does not exist in type 'UtilisateurPublic'.
Ici, on voit bien comment Omit nous permet d'exclure telle ou telle propriété d'un type donné, toujours dans l'idée de cadrer un maximum les choses (ou d'apporter plus de sécurité, en omettant, d'où le terme Omit, des champs sensibles tels que "motDePasse").
Mais pour que ce soit plus parlant encore, voici un autre exemple avec Omit dans une fonction :
interface Utilisateur {
id: number;
nom: string;
motDePasse: string;
}
const utilisateur: Utilisateur = {
id: 1,
nom: "Toto",
motDePasse: "123456"
}
const retirerMotDePasse = (user: Utilisateur): Omit<Utilisateur, "motDePasse"> => {
const { motDePasse: _, ...restant } = user
return { ...restant }
}
console.log(retirerMotDePasse(utilisateur))
Ici, la fonction "retirerMotDePasse" permet de passer d'un type Utilisateur à un type "Utilisateur sans mot de passe", pour ainsi dire. Car cette fonction prend en argument notre utilisateur, en fait une copie en excluant la propriété "motDePasse", et nous retourne cela. Ceci est très utile, par exemple, lorsqu'on interroge une base de données pour récupérer les infos d'un utilisateur, en excluant le mot de passe présent dedans, pour des raisons de sécurité (ce qui est une bonne pratique, au passage, car cela permet de rajouter un garde-fou, sait on jamais !).
En retour console, on obtient :
[LOG]: {
"id": 1,
"nom": "Toto"
}
En bonus, je ne sais pas si vous l'avez remarqué dans l'exemple précédent, mais vous avez là un exemple de code TypeScript vous permettant de supprimer une propriété à un objet (ce qui est un poil plus complexe qu'en Javascript, où on aurait simplement pu écrire delete utilisateur.motDePasse
pour ce faire, par exemple). Cela se fait grâce à la destructuration, en séparant le "motDePasse" du reste.
Type Record<K, T> : pour créer un objet de type clé-valeur bien typé !
Le Record<K, T>
est une autre type également très pratique en TypeScript. Avec lui, on pourra créer un type où les clés (ou propriétés) sont de type K, et les valeurs associées sont de type T. Pour rappel, K et T sont des placeholder, c'est à dire des types désignant des quelconques (comme T) ou particuliers (comme K).
Ce type Record est particulièrement utile pour définir des objets avec une structure clé-valeur dynamique, ou prédéfinie. Voici quelques exemples concrets d’utilisation , dans des cas courants.
Exemple de dictionnaire avec Record
type NomUtilisateur = string;
type Score = number;
const scores: Record<NomUtilisateur, Score> = {
Aline: 95,
Bruno: 87,
Charlotte: 100,
};
Ici, Record nous permet de créer un nouveau type, ayant un nom utilisateur de type string (clef), et un score de type number (valeur) ; et bien entendu, TypeScript nous retournera un message d'erreur, si jamais on n'envoie pas les bons types ici !). À noter qu'on peut bien entendu ajouter autant de clef/valeur dans un objet typé avec Record ; c'est à dire que, si j'ajoute l'instruction scores.didier = 75
à la suite/fin du code précédent, cela ajoutera la clef "didier" avec un score de 75, à cet objet.
Exemple de configuration, avec des clés dynamiques
type Parametre = "timeout" | "retries" | "verbose";
type ValeurConfig = number | boolean;
const config: Record<Parametre, ValeurConfig> = {
timeout: 5000,
retries: 3,
verbose: true,
};
Même idée que l'exemple précédent, mais avec une union de clefs, et des valeurs clairement définies.
Et un dernier exemple pour la route, en ajoutant le type Pick que nous avons vu plus haut !
interface Personne {
nom: string;
age: number;
email: string;
profession: string;
}
type ChampsQuiNousInterressent = Pick<Personne, "nom" | "age">;
type Registre = Record<string, ChampsQuiNousInterressent>;
const registre: Registre = {
id1: { nom: "Alicia", age: 25 },
id2: { nom: "Bertrand", age: 30 },
};
Là on prend les "nom" et "age" de l'interface "Personne", et on s'en sert pour définir un registre sur cette base. Sympa, non ?
En résumé, Record permet de typer clairement un objet de type clé-valeur ! Du reste, voyons le dernier type que j'ai à vous présenter aujourd'hui, sans plus attendre !
Type ReturnType<T> : pour extraire le type de retour d’une fonction
Le dernier type que nous allons voir ici est ReturnType
. Comme les autres, c'est ce qu'on appelle un "utility type" (type utilitaire), en TypeScript. Ici, nous verrons comment ReturnType permet d’extraire le type de retour d’une fonction. C’est d'ailleurs super utile, quand on souhaite typer quelque chose selon ce que renvoi une fonction, sans avoir à le redéfinir manuellement (mais c'est à double tranchant, attention). Du reste, voici des exemples concrets d’utilisation de ReturnType, en usage courant.
Exemple de typage basé sur le retour d'une fonction existante
const obtenirPremierUtilisateur = () => {
return { id: 1, nom: "André", age: 25 };
}
type Utilisateur = ReturnType<typeof obtenirPremierUtilisateur >;
const user2: Utilisateur = { id: 2, nom: "Baptiste", age: 30 }; // OK
const user3: Utilisateur = { id: 3, nom: "Carole" }; // ERREUR : propriété "age" manquante
Ici, grâce à ReturnType, on extrait le type de l'objet { id: 1, nom: "André", age: 25 }
retourné par la fonction obtenirPremierUtilisateur
, ce qui nous donne un type : { id: number; nom: string; age: number }
. Ensuite, nous créons des utilisateurs sur la base de ce type, avec au passage une erreur volontaire à la fin, pour bien mettre en évidence l'efficacité de TypeScript 😉
Exemple de typage avec un objet donné (et fonction asynchrone)
const fetchData = async() => {
return { status: "success", data: ["item1", "item2"] };
}
type ResultatFetch = ReturnType<typeof fetchData>;
// Type : Promise<{ status: string; data: string[] }>
const traiterDonnees = async() => {
const resultat: Awaited<ResultatFetch> = await fetchData();
// Type, après Awaited : { status: string; data: string[] }
console.log(resultat.data);
// Et on affiche la propriété "data" de l'objet "resultat", histoire de !
}
traiterDonnees()
Dans cet exemple, on récupère le type de l'objet { status: "success", data: ["item1", "item2"] }
grâce à ReturnType, comme précédemment. Puis on se sert de ce type pour caractériser la réponse qu'on recevra de la fonction asynchrone fetchData (bon là c'est un exemple qui se mord la queue, mais bon, l'important est que vous compreniez le principe !). Pour info, si vous lancez ce code, il retournera : ["item1", "item2"]
.
Et un dernier exemple pour la route, pour voir si vous suivez toujours !
type Divisible = (valeur1: number, valeur2: number) => { resultat: boolean };
type Reponse = ReturnType<Divisible>;
// Résultat : { resultat: boolean }
const estDivisible: Divisible = (valeur1, valeur2) => ({resultat: valeur2 === 0 ? false : true});
const reponse_1: Reponse = estDivisible(2, 4);
console.log(reponse_1); // Retourne : { "resultat": true }
const reponse_2: Reponse = estDivisible(7, 0);
console.log(reponse_2); // Retourne : { "resultat": false }
Bon là, je pense que vous avez compris le principe ! Ou tout du moins, comment ReturnType permet de récupérer le type d'une fonction ou objet donné 🙂
Remarque : perso, je ne suis pas très fan de ReturnType ; car selon moi, ça enlève de la rigueur à TypeScript. Cela dit, tout dépend des usages qu'on en fait, donc à chacun de voir !
Conclusion
Comme nous venons de le voir ici, les unions, intersections, et types utilitaires comme Partial ou Pick sont des outils puissants pour maîtriser les typages avancés en TypeScript. Ils vous permettront de gérer des cas complexes, tout en gardant un code propre et sûr. Du reste, n’abusez pas non plus des unions ou intersections, si un type simple suffit !
Sur ce, je vous dis à bientôt, sur un prochain article. Et pour ceux qui voudraient soutenir le site LeCoinTS, merci par avance pour votre don !
Jérôme.
Merci d’avoir lu cet article ! LeCoinTS reste gratuit et sans pub grâce à vous.
Un petit don pour soutenir le site ? ☕


JEROME
Passionné par tout ce qui touche à la programmation informatique en TypeScript, sans toutefois en être expert, j'ai à coeur de vous partager ici, peu à peu, tout ce que j'ai appris, découvert, réalisé, et testé jusqu'à présent ! En espérant que tout cela puisse vous servir, ainsi qu'au plus grand nombre de francophones possible !
(*) Mis à jour le 14/04/2025