MFormations
Modern Backend Engineering

Chapitre 22

22 - Simulations d'Entretiens Backend

22 - Simulations d'Entretiens Backend

Chapitre 22 : Simulations d'Entretiens Backend

Introduction

Ce chapitre propose 60 questions d'entretien back-end (30 comportementales, 30 techniques) ainsi que des coding challenges et des études de cas complètes de system design.


Partie 1 : Questions Comportementales (30)

1.1 Parle-moi de toi

Question : "Parle-moi de ton parcours et de ce qui t'a amené au développement backend."

Structure de réponse :

  1. Présent (30%) : Rôle actuel, responsabilités
  2. Passé (50%) : Progression de carrière, projets marquants
  3. Futur (20%) : Objectifs, pourquoi ce poste

Exemple :

"Je suis ingénieur backend avec 5 ans d'expérience spécialisé en Node.js. J'ai commencé en full-stack, mais j'ai rapidement été attiré par les défis de conception d'API et de systèmes distribués. Chez [entreprise], j'ai mené la migration d'un monolithe vers 12 microservices, réduisant le temps de déploiement de 2 heures à 5 minutes. Je cherche aujourd'hui un poste où je pourrai approfondir l'architecture événementielle et le cloud native."

1.2 Projet le plus complexe

Question : "Quel est le projet le plus complexe sur lequel tu as travaillé ?"

Structure STAR :

  • Situation : Contexte
  • Task : Objectif
  • Action : Ce que vous avez fait
  • Result : Résultat mesurable

Exemple :

"Situation : Notre plateforme e-commerce avait des pannes récurrentes pendant les soldes, le monolithe ne tenait pas la charge. Tâche : Repenser l'architecture pour supporter 5x le trafic normal sans downtime. Action : J'ai conçu une architecture microservices avec Kafka pour le découplage, Redis pour le cache, et Kubernetes pour l'orchestration. J'ai implémenté le circuit breaker et le graceful degradation. Résultat : Zéro downtime pendant le Black Friday suivant, temps de réponse -60%, et l'équipe peut déployer indépendamment chaque service."

1.3 Conflit technique

Question : "Raconte un conflit technique que tu as résolu avec un collègue."

Clé : Montrer respect et recherche de la meilleure solution, pas de "gagner".

"Un collègue voulait utiliser MongoDB pour son flexibilité, je préférais PostgreSQL pour la cohérence. Nous avons fait un proof-of-concept de 2 jours chacun, puis benchmarké. MongoDB était 2x plus rapide en écriture, mais PostgreSQL gérait mieux les requêtes complexes. Nous avons choisi PostgreSQL pour le service principal avec une couche de cache Redis. Le collègue a apprécié l'approche data-driven."

1.4 Échec et apprentissage

Question : "Parle-moi d'un échec professionnel et ce que tu en as appris."

"J'ai déployé une migration de base de données sans rollback planifié. La migration a corrompu des données en production. J'ai dû restaurer depuis un backup de 6 heures, perdant les transactions récentes. J'ai appris à toujours avoir un rollback planifié, tester les migrations sur staging, et les exécuter en multiples petites étapes plutôt qu'un gros changement."

1.5 Feedback difficile

Question : "Donne-moi un exemple de feedback difficile que tu as reçu et comment tu y as réagi."

"Un senior m'a dit que mon code était trop 'clever' et pas assez lisible. J'ai réalisé que je privilégiais les solutions élégantes aux solutions maintenables. J'ai adopté le principe KISS et demande systématiquement des code reviews sur la lisibilité. Aujourd'hui, je fais des revues en me demandant 'un junior pourrait-il comprendre ce code ?'"

1.6 Priorisation

Question : "Comment gères-tu des priorités conflictuelles ?"

"Je catégorise par impact et urgence. Quand deux features importantes conflictent, je présente les options au product manager avec les trade-offs techniques : la feature A débloque 3 autres fonctionnalités mais prend 2 semaines, la B rapporte immédiatement mais est un cul-de-sac technique. Je documente aussi la dette technique pour qu'elle soit visible."

1.7 Mentorat

Question : "As-tu déjà mentored des développeurs juniors ?"

"Oui, j'ai encadré 3 juniors. Mon approche est de les laisser coder d'abord, puis de faire une code review détaillée. Je préfère expliquer le 'pourquoi' plus que le 'comment'. Je les encourage à poser des questions et à casser des choses dans leur environnement de dev. Résultat : un junior était autonome sur son premier microservice après 3 mois."

1.8 Technologie préférée

Question : "Quelle est ta technologie préférée et pourquoi ?"

"Redis. C'est incroyablement polyvalent : cache, session store, rate limiter, message broker, geospatial. Sa simplicité combinée à sa puissance en fait l'outil le plus impactant que j'utilise. Les structures de données (sorted sets, hyperloglog, streams) permettent des solutions élégantes à des problèmes complexes."

1.9 Veille technologique

Question : "Comment restes-tu à jour technologiquement ?"

"Je suis plusieurs newsletters (Node Weekly, DB Weekly), des blogs engineering (Netflix, Uber), et des conférences KubeCon. J'expérimente sur des side projects. Actuellement, j'explore WebAssembly côté serveur et les bases de données orientées colonnes."

1.10 Code review

Question : "À quoi ressemble une bonne code review pour toi ?"

"Une bonne review est constructive et bienveillante. Je vérifie : la logique métier, les edge cases, la testabilité, la performance, et l'architecture. Je commente moins sur le style (lint automatisé). Je pose des questions plutôt que de donner des ordres. 'As-tu considéré...' plutôt que 'Tu devrais...'"

1.11 Dette technique

Question : "Comment gères-tu la dette technique ?"

"Je la documente avec ticket Jira et étiquette 'tech-debt'. Je propose des 'fix-it fridays' ou des sprints dédiés. Je montre l'impact : 'cette dette ajoute 2 jours par mois de maintenance'. Le ratio idéal : 80% features, 20% réduction de dette."

1.12 Travail en équipe

Question : "Décris ton équipe idéale."

"Une équipe de 4-6 personnes avec des compétences complémentaires, qui pratique le pair programming et les reviews. Transparente sur les difficultés, avec un PM qui protège l'équipe. Des daily standups courts et des rétros honnêtes."

1.13 Décision impopulaire

Question : "Quand as-tu dû prendre une décision impopulaire ?"

"J'ai désactivé une fonctionnalité populaire car elle causait des pannes récurrentes. Les utilisateurs étaient mécontents, mais la stabilité était prioritaire. Nous l'avons réécrite proprement et redéployée 2 semaines plus tard. L'équipe a compris que parfois il faut reculer pour mieux avancer."

1.14 Apprentissage rapide

Question : "Comment apprends-tu une nouvelle technologie rapidement ?"

"1. Je construis un petit projet pour avoir un contexte. 2. Je lis la documentation officielle. 3. Je regarde des conférences sur le sujet. 4. Je contribue à un projet open-source pour valider ma compréhension. Par exemple, j'ai appris Kafka en 2 semaines en construisant un pipeline de traitement d'événements."

1.15 Amélioration processus

Question : "Quel processus as-tu amélioré dans ton équipe ?"

"Notre déploiement manuel prenait 30 minutes et était sujet aux erreurs. J'ai automatisé avec GitHub Actions + Blue/Green deployment : passage de 30 min à 2 min, zéro erreur, et les déploiements peuvent être faits par n'importe qui."

1.16-1.30 Questions supplémentaires

16. Gestion du stress : "Comment gères-tu une production qui tombe à 3h du matin ?" → Rester calme, suivre le runbook, communiquer, post-mortem.

17. Autonomie : "Préfères-tu des spécifications détaillées ou de l'autonomie ?" → Autonomie cadrée : objectifs clairs, liberté d'exécution.

18. Feedback : "Comment donnes-tu du feedback négatif ?" → SBI model : Situation, Behavior, Impact.

19. Changement de scope : "Comment gères-tu un changement de scope en cours de sprint ?" → Évaluer l'impact, discuter des trade-offs, renégocier.

20. Culture d'entreprise : "Quelle culture d'entreprise recherches-tu ?" → Learning culture, psychological safety, engineering excellence.

21. Fail fast : "Que penses-tu de l'approche 'fail fast' ?" → Bonne pour l'exploration, mauvaise pour la production. Échouer rapidement sur des idées, pas sur la fiabilité.

22. Documentation : "Comment documentes-tu ton code ?" → README pour le projet, JSDoc pour les API, ADR pour les décisions. Pas de commentaires qui répètent le code.

23. Testing : "Quel est ton approach du testing ?" → Pyramid testing : unitaires (70%), intégration (20%), e2e (10%). TDD pour la logique complexe.

24. Open source : "Contribues-tu à l'open source ?" → Oui, je contribue à [projet]. Cela m'aide à lire du code de qualité et à comprendre des architectures matures.

25. Remote work : "Comment restes-tu productif en remote ?" → Routine, espace dédié, async-first communication, over-communication.

26. Cross-team : "Comment travailles-tu avec d'autres équipes ?" → APIs claires, documentation, sync hebdomadaire, contrat d'interface.

27. Legacy code : "Comment abordes-tu une codebase legacy ?" → Strangler Fig pattern : ajouter des tests d'abord, extraire progressivement.

28. Performance : "Comment optimises-tu une API lente ?" → Mesurer (profiling), identifier le bottleneck, optimiser (cache, index, requêtes).

29. Innovation : "Comment introduis-tu une nouvelle techno dans l'équipe ?" → POC → présentation → adoption graduelle. Montrer, pas imposer.

30. Motivation : "Qu'est-ce qui te motive dans le développement backend ?" → Construire des systèmes fiables, scalables et élégants. L'impact de l'infrastructure invisible.


Partie 2 : Questions Techniques (30)

2.1 System Design (15 questions)

Q1 : Conception d'un URL Shortener

Problème : Concevoir un service comme bit.ly ou TinyURL.

Points clés :

  • Génération d'IDs uniques (Base62, Snowflake)
  • Redirections 301/302
  • Cache Redis + CDN
  • Analytics (clics, géo, referrer)
  • Scalabilité : 100M URLs/mois, 10K req/s redirections

Discussion :

  • Stockage : PostgreSQL (sharding par hash) ou Cassandra
  • Cache : Redis, Write-through pour les URLs populaires
  • CDN : CloudFront/CloudFlare pour les redirections
  • Analytics : Kafka → Hadoop/ClickHouse en batch

Q2 : Conception d'un Chat en Temps Réel

Problème : Concevoir WhatsApp/Messenger.

Points clés :

  • WebSocket persistent
  • Gestion de présence
  • Messages hors-ligne
  • Chiffrement de bout en bout
  • Groupes (jusqu'à 256)

Discussion :

  • WebSocket avec heartbeat
  • Cassandra pour le stockage des messages (partition par conversation_id)
  • Redis pour les sessions et la présence
  • APNS/FCM pour les notifications push
  • Message sync à la reconnexion

Q3 : Conception d'un Système de Paiement

Problème : Concevoir Stripe ou un système de paiement.

Points clés :

  • Idempotence (clé d'idempotence)
  • Double-entry accounting
  • Gestion des échecs et retours
  • Conformité PCI DSS
  • Anti-fraud

Discussion :

  • État machine : initié → autorisé → capturé → complété
  • Saga pattern pour les transactions distribuées
  • Idempotency key dans Redis avec TTL
  • Event sourcing pour le ledger
  • Rate limiting par merchant

Q4 : Conception de News Feed

Problème : Concevoir le feed Facebook/LinkedIn.

Points clés :

  • Fan-out on write vs read
  • Ranking algorithm
  • Content diversity
  • Real-time updates
  • Scroll infini

Discussion :

  • Fan-out on write pour les amis (pré-calculé)
  • Fan-out on read pour les célébrités
  • ML ranking (affinity, recency, content type)
  • Feed cache Redis (sharded par user_id)
  • Kafka pour le fan-out asynchrone

Q5 : Conception d'un CDN

Problème : Concevoir un CDN comme CloudFront/CloudFlare.

Points clés :

  • Edge servers géo-distribués
  • Cache invalidation
  • DDoS protection
  • SSL termination
  • Origin pull vs push

Discussion :

  • DNS-based routing (Anycast)
  • Reverse proxy cache (Varnish, Nginx, Squid)
  • Cache invalidation : purge API, TTL, versioning
  • DDoS : rate limiting, WAF, challenge (CloudFlare)
  • Origin shielding pour réduire la charge

Q6 : Conception d'un Rate Limiter

Problème : Concevoir un rate limiter distribué (comme celui de Stripe/GitHub).

Points clés :

  • Algorithme : Token bucket, Leaky bucket, Sliding window
  • Distribution : Redis cluster
  • Headers : X-RateLimit-*
  • Différenciation par plan/API key

Discussion :

  • Sliding window log vs fixed window
  • Redis sorted set pour sliding window
  • LUA scripting pour atomicité
  • Local rate limiting + sync périodique
  • Graceful degradation : passer en mode degrading si Redis est down

Q7 : Conception d'un Service de Recherche

Problème : Concevoir un moteur de recherche full-text.

Points clés :

  • Index inversé
  • Ranking (TF-IDF, BM25, PageRank)
  • Facettes et filtres
  • Suggestions et autocomplete
  • Pagination par curseur

Discussion :

  • Index inversé stocké (Elasticsearch, tant que service dédié)
  • Sharding par document
  • Réplication pour HA
  • Autocomplete avec Trie / Finite State Transducer
  • Real-time vs batch indexing

Q8 : Conception d'un Système d'Authentification

Problème : Concevoir un système d'auth SSO/OAuth2.

Points clés :

  • OAuth2 flow (Authorization Code, PKCE)
  • JWT access + refresh tokens
  • Session management
  • MFA / TOTP
  • Social login

Discussion :

  • JWT signé avec RS256 (public/private key)
  • Refresh token rotation avec family
  • Blacklist de tokens (Redis)
  • OAuth2 provider (Keycloak, Auth0, custom)
  • Rate limiting sur les endpoints d'auth

Q9 : Conception de Video Streaming

Problème : Concevoir YouTube/Netflix.

Points clés :

  • Upload et transcodage
  • Adaptive bitrate streaming (HLS/DASH)
  • CDN delivery
  • Recommendations
  • Comments et likes

Discussion :

  • Upload → queue de transcodage → CDN
  • Transcodage avec FFmpeg (resolution ladder)
  • HLS segments (10s each) sur CDN
  • Pre-processing pipeline (thumbnail, analyse)
  • ML recommendations (collaborative filtering + content-based)

Q10 : Conception d'un Système de Réservation

Problème : Concevoir un système de réservation (avion, hôtel).

Points clés :

  • Concurrence (2 personnes réservent la même place)
  • Gestion d'inventaire
  • Overbooking contrôlé
  • Paiement et confirmation

Discussion :

  • Pessimistic locking (SELECT FOR UPDATE)
  • Optimistic locking (version column)
  • Queue pour les réservations (Kafka)
  • Timeout de réservation (TTL Redis)
  • Saga pattern pour le flow booking + payment

Q11 : Conception d'un Système de Logs

Problème : Concevoir un système centralisé de logs (ELK-like).

Points clés :

  • Ingestion haute vitesse
  • Indexation temps réel
  • Search et aggregation
  • Retention policies
  • Alerting

Discussion :

  • Kafka comme buffer d'ingestion
  • Logstash/Fluentd pour parsing
  • Elasticsearch cluster (hot/warm/cold nodes)
  • ILM (Index Lifecycle Management)
  • Grafana pour les dashboards

Q12 : Conception d'une API Gateway

Problème : Concevoir une API Gateway (Kong/Apigee).

Points clés :

  • Routing et load balancing
  • Rate limiting, auth
  • Request/response transformation
  • Circuit breaker
  • Observabilité

Discussion :

  • Reverse proxy distribué
  • Plugins chaînés (auth → rate limit → transform → proxy)
  • Service discovery (Consul, K8s)
  • Canary routing par header
  • Cache des réponses (Redis)

Q13 : Conception de Distributed Lock

Problème : Concevoir un distributed locking service.

Points clés :

  • Redlock algorithm
  • Lease avec TTL
  • Fencing tokens
  • Deadlock detection
  • Semaphore distribué

Discussion :

  • Redis Redlock : lock sur N/2+1 nœuds
  • ZooKeeper : sequential ephemeral nodes
  • Fencing token pour prévenir les delayed locks
  • Watchdog pour prolonger les leases
  • Connection loss handling

Q14 : Conception d'un Message Queue

Problème : Concevoir un message queue (Kafka/RabbitMQ).

Points clés :

  • Publishing et consuming
  • Durabilité et réplication
  • Partitionnement
  • Consumer groups
  • Exactly-once semantics

Discussion :

  • Log-based storage (append-only)
  • Partition par clé de message
  • Consumer group avec rebalance
  • ISR (In-Sync Replicas) pour la durabilité
  • Idempotent producer + transactional consumer

Q15 : Conception d'un Financial Ledger

Problème : Concevoir un système de comptabilité financière.

Points clés :

  • Double-entry accounting
  • Immutabilité (append-only)
  • Reconciliation
  • Audit trail
  • Reporting

Discussion :

  • Event sourcing : tous les changements sont des événements
  • Ledger comme source of truth
  • Projections pour les balances
  • Sharding par account_id
  • Batch reconciliation quotidienne

2.2 Architecture et Design (15 questions)

Q16 : Monolithe vs Microservices

Question : Quand choisir monolithe vs microservices ?

Réponse :

  • Monolithe : Petite équipe, MVP, startup, transactions complexes
  • Microservices : Équipes multiples, scaling indépendant, cycles de release différents
  • Stratégie : Commencer monolithe, extraire progressivement (Strangler Fig)

Q17 : SQL vs NoSQL

Question : Comment choisis-tu entre SQL et NoSQL ?

Réponse :

  • SQL : Données relationnelles, ACID, requêtes complexes, reporting
  • NoSQL document : Schéma flexible, dénormalisation, scalabilité horizontale
  • NoSQL key-value : Cache, sessions, compteurs
  • NoSQL graph : Relations complexes, recommandations, traversées

Q18 : REST vs GraphQL vs gRPC

Question : Quand utiliser REST, GraphQL, ou gRPC ?

Réponse :

  • REST : API publiques, CRUD standard, cache HTTP, maturité
  • GraphQL : UI complexes, multiple ressources, bande passante limitée, subscriptions
  • gRPC : Inter-service, streaming, performance, polyglot

Q19 : Cache Strategy

Question : Quelle stratégie de cache choisis-tu selon le cas d'usage ?

Réponse :

  • Cache-aside : Lazy loading, bon pour les lectures majoritaires
  • Write-through : Cache toujours synchrone, écritures plus lentes
  • Write-behind : Performant, risque de perte de données
  • Refresh-ahead : Proactif, prédit les accès
  • CDN : Statique, assets, contenu public

Q20 : Load Balancing

Question : Comment distribues-tu le trafic entre plusieurs instances ?

Réponse :

  • DNS round-robin : Simple, pas de health check
  • Reverse proxy (Nginx, HAProxy) : Health check, SSL termination, sticky sessions
  • Cloud LB (ALB, GCLB) : Auto-scaling, multi-region
  • Service mesh (Istio, Linkerd) : Traffic management, mTLS

Q21 : Database Indexing

Question : Comment indexer une table de 10M lignes avec des requêtes variées ?

Réponse :

  • Analyser les requêtes (slow query log, pg_stat_statements)
  • B-tree pour les colonnes de tri/where
  • Composite index sur les colonnes filtrées ensemble
  • Partial index pour les sous-ensembles
  • Covering index pour éviter le table scan
  • Éviter le sur-indexing (coût en écriture)

Q22 : Idempotence

Question : Comment garantir l'idempotence dans une API ?

Réponse :

  • Clé d'idempotence dans le header (Idempotency-Key)
  • Stockage en Redis avec TTL
  • Vérifier si l'ID existe avant de traiter
  • Réponse mise en cache pour la même clé
  • Base de données unique constraint

Q23 : Transactions Distribuées

Question : Comment gères-tu les transactions qui traversent plusieurs services ?

Réponse :

  • 2PC : Simple mais bloquant et lent
  • Saga choreography : Chaque service publie des événements
  • Saga orchestration : Orchestrateur central
  • Outbox pattern : Événements stockés dans la même transaction DB
  • Eventual consistency avec compensation si échec

Q24 : Cap Theorem

Question : Comment appliques-tu le théorème CAP en pratique ?

Réponse :

  • Identifier le besoin : quelle propriété sacrifier en cas de partition ?
  • AP : Préférer la disponibilité (DynamoDB, Cassandra, DNS)
  • CP : Préférer la cohérence (ZooKeeper, etcd, base relationnelle)
  • En pratique : trade-off pas binaire, spectre entre strong et eventual consistency

Q25 : Graceful Degradation

Question : Comment conçois-tu un système qui dégrade gracieusement ?

Réponse :

  • Circuit breaker pour les dépendances externes
  • Fallback values (cache stale, defaults)
  • Bulkhead (thread pools séparés)
  • Feature flags pour désactiver des fonctionnalités
  • Cache toujours disponible même si obsolète

Q26 : Async Processing

Question : Quand utiliser du processing asynchrone vs synchrone ?

Réponse :

  • Synchrone : Opérations en temps réel, feedback immédiat (< 100ms)
  • Asynchrone : Traitements longs, batch, emails, notifications, découplage
  • Pattern : API synchrone pour la validation, enqueue pour le processing

Q27 : Database Migration

Question : Comment gères-tu les migrations de base de données sans downtime ?

Réponse :

  • Expand-Migrate-Contract pattern
  • Les anciennes et nouvelles colonnes coexistent
  • Déploiement en phases : add column → backfill → read new → drop old
  • Versioned migrations avec rollback
  • Feature flag pour basculer en lecture/écriture

Q28 : Data Consistency

Question : Comment maintiens-tu la cohérence des données entre services ?

Réponse :

  • Event-driven avec outbox pattern
  • Idempotent consumers
  • Saga pour transactions longues
  • Reconciliation jobs (batch fix)
  • Data contracts (Avro/Protobuf schema registry)

Q29 : Observabilité

Question : Comment rends-tu un système observable ?

Réponse :

  • Logs : Structurés, corrélés (trace ID), centralisés (ELK/Loki)
  • Métriques : RED (Rate, Errors, Duration) pour chaque service
  • Traces : OpenTelemetry, Distributed tracing (Jaeger/Zipkin)
  • Dashboards : Grafana pour les tendances
  • Alerting : Basé sur SLO, pas sur symptômes

Q30 : Security

Question : Quelles sont les pratiques de sécurité essentielles pour une API backend ?

Réponse :

  • HTTPS everywhere (HSTS, TLS 1.3)
  • Auth : JWT + refresh token rotation
  • Rate limiting par API key et par IP
  • Input validation (Zod/Joi)
  • SQL injection prevention (parameterized queries)
  • CORS configuré restrictivement
  • Security headers (CSP, X-Frame-Options)
  • Dépendances à jour (npm audit, Dependabot)
  • Secrets management (Vault, environment variables)

Partie 3 : Coding Challenges (10 problèmes)

Challenge 1 : Rate Limiter

Problème : Implémenter un Token Bucket rate limiter.

class TokenBucket {
  private tokens: number;
  private lastRefill: number;

  constructor(
    private capacity: number,
    private refillRate: number, // tokens per second
  ) {
    this.tokens = capacity;
    this.lastRefill = Date.now();
  }

  allow(): boolean {
    this.refill();
    if (this.tokens > 0) {
      this.tokens--;
      return true;
    }
    return false;
  }

  private refill(): void {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
    this.lastRefill = now;
  }
}

Challenge 2 : LRU Cache

Problème : Implémenter un Least Recently Used cache.

class LRUCache<K, V> {
  private capacity: number;
  private cache = new Map<K, V>();

  constructor(capacity: number) {
    this.capacity = capacity;
  }

  get(key: K): V | undefined {
    if (!this.cache.has(key)) return undefined;
    const value = this.cache.get(key)!;
    this.cache.delete(key);
    this.cache.set(key, value);
    return value;
  }

  put(key: K, value: V): void {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    } else if (this.cache.size >= this.capacity) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(key, value);
  }
}

Challenge 3 : Distributed Counter

Problème : Implémenter un compteur distribué avec Redis.

async function incrementCounter(key: string, redis: Redis): Promise<number> {
  const script = `
    local current = redis.call('GET', KEYS[1])
    if not current then
      redis.call('SET', KEYS[1], 1, 'EX', ARGV[1])
      return 1
    end
    return redis.call('INCR', KEYS[1])
  `;
  return redis.eval(script, 1, key, 3600);
}

Challenge 4 : JWT Middleware

Problème : Implémenter un middleware d'authentification JWT.

function authenticate(req: Request, res: Response, next: NextFunction): void {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    throw new UnauthorizedError('Missing or invalid token');
  }

  try {
    const token = authHeader.slice(7);
    const payload = jwt.verify(token, process.env.JWT_SECRET!, {
      algorithms: ['RS256'],
      issuer: 'auth.example.com',
    }) as JwtPayload;
    
    req.user = { id: payload.sub!, role: payload.role };
    next();
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      throw new UnauthorizedError('Token expired');
    }
    throw new UnauthorizedError('Invalid token');
  }
}

Challenge 5 : Pagination avec Cursor

Problème : Implémenter une pagination par curseur.

async function paginateWithCursor(
  query: string,
  params: any[],
  cursor?: string,
  limit: number = 20,
): Promise<{ data: any[]; nextCursor?: string }> {
  const actualLimit = limit + 1;
  
  let results;
  if (cursor) {
    const decoded = Buffer.from(cursor, 'base64').toString();
    results = await pool.query(
      `${query} AND created_at < $${params.length + 1} ORDER BY created_at DESC LIMIT $${params.length + 2}`,
      [...params, decoded, actualLimit],
    );
  } else {
    results = await pool.query(
      `${query} ORDER BY created_at DESC LIMIT $${params.length + 1}`,
      [...params, actualLimit],
    );
  }

  const hasMore = results.rows.length > limit;
  const data = hasMore ? results.rows.slice(0, limit) : results.rows;
  const nextCursor = hasMore
    ? Buffer.from(data[data.length - 1].created_at.toISOString()).toString('base64')
    : undefined;

  return { data, nextCursor };
}

Challenge 6 : Circuit Breaker

Problème : Implémenter un circuit breaker.

class CircuitBreaker {
  private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED';
  private failureCount = 0;
  private lastFailureTime = 0;

  constructor(
    private threshold: number = 5,
    private cooldownMs: number = 30000,
  ) {}

  async call<T>(fn: () => Promise<T>, fallback: T): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime >= this.cooldownMs) {
        this.state = 'HALF_OPEN';
      } else {
        return fallback;
      }
    }

    try {
      const result = await fn();
      this.reset();
      return result;
    } catch (error) {
      this.recordFailure();
      throw error;
    }
  }

  private recordFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
    }
  }

  private reset(): void {
    this.state = 'CLOSED';
    this.failureCount = 0;
  }
}

Challenge 7 : Debounce

Problème : Implémenter debounce pour éviter les appels répétés.

function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number,
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout>;

  return (...args: Parameters<T>) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

Challenge 8 : Throttle

Problème : Implémenter throttle pour limiter le taux d'appels.

function throttle<T extends (...args: any[]) => any>(
  fn: T,
  limitMs: number,
): (...args: Parameters<T>) => void {
  let lastCall = 0;
  let timeoutId: ReturnType<typeof setTimeout> | null = null;

  return (...args: Parameters<T>) => {
    const now = Date.now();
    const remaining = limitMs - (now - lastCall);

    if (remaining <= 0) {
      if (timeoutId) {
        clearTimeout(timeoutId);
        timeoutId = null;
      }
      lastCall = now;
      fn(...args);
    } else if (!timeoutId) {
      timeoutId = setTimeout(() => {
        lastCall = Date.now();
        timeoutId = null;
        fn(...args);
      }, remaining);
    }
  };
}

Challenge 9 : Saga Orchestrator

Problème : Implémenter un orchestrateur de saga.

interface SagaStep<T> {
  name: string;
  execute: (ctx: T) => Promise<void>;
  compensate: (ctx: T) => Promise<void>;
}

class SagaOrchestrator<T> {
  private steps: SagaStep<T>[] = [];
  private executedSteps: SagaStep<T>[] = [];

  addStep(step: SagaStep<T>): this {
    this.steps.push(step);
    return this;
  }

  async execute(ctx: T): Promise<void> {
    for (const step of this.steps) {
      try {
        await step.execute(ctx);
        this.executedSteps.push(step);
      } catch (error) {
        await this.compensate();
        throw new Error(`Saga failed at step ${step.name}: ${error}`);
      }
    }
  }

  private async compensate(): Promise<void> {
    for (const step of [...this.executedSteps].reverse()) {
      try {
        await step.compensate(step as unknown as T);
      } catch (error) {
        console.error(`Compensation failed for ${step.name}:`, error);
      }
    }
  }
}

Challenge 10 : Consistent Hashing

Problème : Implémenter un consistent hash ring.

class ConsistentHashRing {
  private ring: Map<number, string> = new Map();
  private sortedKeys: number[] = [];
  private virtualNodes = 100;

  constructor(private nodes: string[] = []) {
    for (const node of nodes) {
      this.addNode(node);
    }
  }

  addNode(node: string): void {
    for (let i = 0; i < this.virtualNodes; i++) {
      const hash = this.hash(`${node}:${i}`);
      this.ring.set(hash, node);
      this.sortedKeys.push(hash);
    }
    this.sortedKeys.sort((a, b) => a - b);
  }

  removeNode(node: string): void {
    for (let i = 0; i < this.virtualNodes; i++) {
      const hash = this.hash(`${node}:${i}`);
      this.ring.delete(hash);
    }
    this.sortedKeys = this.sortedKeys.filter(k => this.ring.has(k));
  }

  getNode(key: string): string {
    if (this.ring.size === 0) throw new Error('No nodes available');
    const hash = this.hash(key);
    for (const ringKey of this.sortedKeys) {
      if (hash <= ringKey) {
        return this.ring.get(ringKey)!;
      }
    }
    return this.ring.get(this.sortedKeys[0])!;
  }

  private hash(key: string): number {
    let hash = 0;
    for (let i = 0; i < key.length; i++) {
      hash = (hash * 31 + key.charCodeAt(i)) | 0;
    }
    return hash >>> 0;
  }
}

Partie 4 : Cas Complets de System Design

Cas 1 : Design d'Uber

Exigences :

  • Passagers et conducteurs
  • Matching en temps réel
  • Pricing dynamique (surge)
  • Navigation et tracking
  • Paiement

Architecture :

Passenger App → API Gateway → Ride Service → Matching Engine
Conductor App → API Gateway → Driver Service → GPS Service
                                    │
                              Kafka / WebSocket
                                    │
                              ETA Service (ML)

Database :

  • PostgreSQL : Comptes, rides, payments
  • Redis : GPS locations, driver availability
  • Cassandra : Ride history (time-series)

Key challenges :

  • Geospatial index (PostGIS, Google S2)
  • Driver matching dans un rayon donné
  • Surge pricing (supply/demand par zone)
  • Reliable real-time GPS with WebSocket

Cas 2 : Design de Netflix

Exigences :

  • Streaming vidéo
  • Recommandations personnalisées
  • Multi-device
  • Offline download
  • CDN global

Architecture :

Upload → Transcode Pipeline → CDN
   │
   ▼
Content DB → Recommendation Engine
   │
   ▼
User Service → API Gateway → Client

Storage :

  • S3 pour les vidéos originales
  • CloudFront/CDN pour la diffusion
  • Cassandra pour l'historique de visionnage
  • Elasticsearch pour le catalogue

Key challenges :

  • Transcodage (resolution ladder : 360p → 4K)
  • Adaptive bitrate (HLS/DASH)
  • Personalized recommendations (collaborative filtering + DL)
  • CDN edge caching

Cas 3 : Design de Twitter/X

Exigences :

  • Tweets, retweets, likes
  • Timeline (home + trending)
  • Search
  • Notifications
  • 500M MAU

Architecture :

Tweet Service → Fan-out (Kafka) → Timeline Service → Timeline Cache (Redis)
                                              │
                                        API Gateway ← Client

Fan-out :

  • Célébrités : Pull (on read)
  • Amis : Push (on write)

Storage :

  • PostgreSQL/MySQL : Users, tweets
  • Redis : Timeline cache
  • Elasticsearch : Search index
  • Cassandra : Social graph (followers)

Cas 4 : Design de Dropbox/Google Drive

Exigences :

  • Upload/download fichiers
  • Sync multi-device
  • Partage et permissions
  • Versioning
  • Collaboration

Architecture :

Client → API Gateway → File Service → Metadata DB (PostgreSQL)
                  │
            Block Store (S3)
                  │
            Sync Engine (WebSocket)

Key challenges :

  • Block-level sync (diviser en chunks de 4MB)
  • Delta sync (ne transférer que les changements)
  • Conflict resolution (last-writer-wins ou merge)
  • Deduplication (content-addressable storage)

Cas 5 : Design de Slack/Discord

Exigences :

  • Channels et DMs
  • Real-time messaging
  • File sharing
  • Search
  • 10M+ concurrent users

Architecture :

Client → WebSocket Gateway → Message Service → Message Store (Cassandra)
                                      │
                                Search Index (Elasticsearch)
                                      │
                                File Service (S3 + CDN)

Key challenges :

  • WebSocket connection management (gateway)
  • Message ordering per channel (sequence numbers)
  • Read state tracking (last read message per user per channel)
  • Full-text search with sharding
  • Rate limiting per channel