00 — Le code qui marche en dev et meurt en prod
Ce que tu vas apprendre
- Pourquoi un code qui passe les tests peut quand même être un problème
- La différence entre « lent » et « ne passe pas à l'échelle »
- Ce que la complexité algorithmique mesure réellement
- Le plan de cette série de 7 articles
Prérequis
- Savoir écrire une boucle et une fonction dans un langage (TypeScript ou Python conviennent)
- Avoir déjà manipulé un tableau et un objet/dictionnaire
- Aucune base mathématique avancée n'est nécessaire
Il y a trois ans, j'ai écrit une fonction de déduplication pour une liste de contacts. Sur mon poste, avec les 200 contacts du jeu de test, elle répondait instantanément. En production, six mois plus tard, un client avait importé 90 000 contacts. La page de déduplication mettait 40 secondes à répondre, puis le navigateur affichait un écran blanc. Le code n'avait pas changé. Les données, si.
Le bug n'était pas une faute de frappe ni une mauvaise logique. La fonction faisait exactement ce qu'on lui demandait. Le problème : pour chaque contact, elle parcourait toute la liste pour chercher un doublon. 200 contacts, ça fait 40 000 comparaisons, invisible. 90 000 contacts, ça fait 8 milliards de comparaisons. Le code était une bombe à retardement dont la mèche était la quantité de données.
C'est ça, la complexité algorithmique : la façon dont le coût d'un programme évolue quand la quantité de données augmente. Et c'est une forme de dette technique particulièrement vicieuse, parce qu'elle ne se voit pas tant que les volumes restent petits.
« Lent » n'est pas le vrai problème
Quand on parle de performance, le réflexe est de penser « mon code est lent, je vais l'optimiser ». Mais la lenteur absolue n'est pas le sujet. Une fonction qui prend 2 millisecondes au lieu de 1 n'a aucune importance dans 99 % des cas.
Le vrai sujet, c'est la pente. Si tu doubles la quantité de données, est-ce que ton code prend deux fois plus de temps ? Quatre fois ? Mille fois ? Le même temps ? Cette pente décide si ton code tiendra l'année prochaine, quand le volume aura été multiplié par dix sans que personne ne touche au code.
Voici deux fonctions qui résolvent le même problème : trouver si un élément est présent dans une collection.
typescript// Version A : on parcourt tout le tableau
function existsInArray(items: number[], target: number): boolean {
for (const item of items) {
if (item === target) return true;
}
return false;
}
// Version B : on utilise un Set
function existsInSet(items: Set<number>, target: number): boolean {
return items.has(target);
}
Sur 10 éléments, les deux répondent en une fraction de microseconde. Tu ne verras aucune différence. Sur 10 millions d'éléments cherchés des milliers de fois, la version A devient inutilisable et la version B ne bouge pas. La différence n'est pas dans le langage ni dans la machine. Elle est dans la pente.
Ce que la complexité mesure
La complexité algorithmique répond à une question précise : quand la taille de l'entrée grandit, comment grandit le coût ?
On note la taille de l'entrée n. Le « coût » peut être le temps (combien d'opérations) ou la mémoire (combien d'espace). On exprime ce coût avec la notation Big-O, qui sera le sujet de l'article suivant. Quelques exemples pour fixer les idées :
- Lire le premier élément d'un tableau coûte toujours pareil, peu importe la taille : c'est O(1).
- Parcourir un tableau coûte proportionnellement à sa taille : c'est O(n).
- Comparer chaque élément à tous les autres coûte le carré de la taille : c'est O(n²). C'était mon bug de déduplication.
La complexité ne te donne pas un temps en secondes. Elle te donne une forme de croissance. C'est ce qui permet de raisonner sur un code sans le lancer, et de prédire son comportement sur des volumes que tu n'as pas encore testés.
Pourquoi c'est une dette technique
Une dette technique classique, c'est du code mal structuré : tu sais qu'il est moche, tu le vois, tu peux décider de le rembourser. La dette de complexité est différente : elle est invisible tant que les volumes restent bas. Le code passe les tests, passe la revue, part en production. Tout va bien.
Puis l'entreprise grandit. Les volumes montent. Un jour, une page met 40 secondes à charger et personne ne comprend pourquoi, parce que « ça a toujours marché ». La dette était là depuis le premier jour, elle attendait juste assez de données pour se réveiller.
Comprendre la complexité, c'est apprendre à repérer cette dette au moment où on écrit le code, pas trois ans plus tard sous la pression d'un incident.
Le plan de la série
Cette série couvre la complexité de façon pragmatique, orientée code réel et pas démonstration mathématique.
| Article | Contenu |
|---|---|
| 00 — Introduction | Pourquoi la complexité est une dette qui dort (cet article) |
| 01 — La notation Big-O | Lire O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ) |
| 02 — Compter les opérations | Cas pire/moyen/meilleur, pourquoi on ignore les constantes |
| 03 — Le coût des structures de données | Array, liste chaînée, hashmap, Set, arbre comparés |
| 04 — La complexité spatiale | La mémoire a un coût, le trade-off temps/espace |
| 05 — Les pièges classiques | Boucles imbriquées cachées, includes dans une boucle |
| 06 — Choisir la bonne structure | Arbres de décision selon l'usage réel |
À la fin, tu sauras regarder une fonction et estimer sa complexité de tête, repérer les pièges qui transforment du code propre en bombe à retardement, et choisir la structure de données adaptée à ton problème plutôt que celle qui te vient en premier.
L'article suivant pose les fondations : la notation Big-O, comment la lire et ce qu'elle veut dire.
Sources
- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms (3rd ed.), Chapitre 3 "Growth of Functions". MIT Press.
- Skiena, S. S. (2008). The Algorithm Design Manual (2nd ed.), Chapitre 2 "Algorithm Analysis". Springer.
- Bentley, J. (2000). Programming Pearls (2nd ed.), Colonne 8 "Algorithm Design Techniques". Addison-Wesley.