CASE STUDY

Adaptive Traffic Light System (LLD)

3 min read·451 words·Intermediate

Asked at

1 candidate report in Oct 2025

How to use this case study

SDE-2 / Mid

Model the intersection (roads, signals, phases), a state machine that cycles green, yellow and red safely, and how timings are chosen.

SDE-3 / Senior

Adapt green times to real-time vehicle counts with minimum and maximum limits, handle pedestrians and emergency vehicles, and design for extensibility (strategy pattern).

Staff / Principal

Discuss safety guarantees (never conflicting greens), sensor failures and fallbacks, and coordinating multiple intersections ("green waves").


0) Problem Restatement

Design a traffic light controller for a four-way intersection that adjusts green times based on real-time traffic (sensor counts of waiting cars). Goldman Sachs asked this as low-level design. The most important rule: never give green to conflicting directions at the same time. It should also be fair (no road waits forever), handle pedestrians and emergency vehicles, and fall back safely when sensors fail.


1) Requirements

  • Signals for each approach (N, S, E, W), grouped into phases, e.g., Phase A = North-South straight green; Phase B = East-West straight green (plus optional turn phases).
  • Each phase goes green → yellow (fixed, e.g., 3s) → all-red clearance (e.g., 1–2s) → the next phase.
  • Green duration adapts to traffic: between min_green (e.g., 10s) and max_green (e.g., 60s).
  • Pedestrian button requests, and emergency vehicle preemption.
  • If sensors fail → fixed-time plan.


2) Class Design

Architecture Diagram

classDiagram
    class IntersectionController {
        -List phases
        -Phase current
        -SignalState state
        -TimingStrategy strategy
        +tick(now) void
        +onSensorUpdate(approach, count) void
        +requestPedestrian(phase) void
        +preempt(phase) void
    }
    class Phase {
        +String name
        +List greenApproaches
        +int minGreen
        +int maxGreen
    }
    class TimingStrategy {
        <<interface>>
        +greenDuration(phase, counts) int
        +nextPhase(phases, current, counts, waits) Phase
    }
    class FixedTimeStrategy
    class AdaptiveStrategy
    class SafetyMonitor {
        +validate(signalStates) bool
    }
    TimingStrategy <|.. FixedTimeStrategy
    TimingStrategy <|.. AdaptiveStrategy
    IntersectionController --> Phase
    IntersectionController --> TimingStrategy
    IntersectionController --> SafetyMonitor
Signal state machine for the current phase: GREEN → YELLOW → ALL_RED → (switch phase) → GREEN.

3) Core Logic (Python sketch)

from enum import Enum

class State(Enum):
    GREEN = 1; YELLOW = 2; ALL_RED = 3

YELLOW_S, ALL_RED_S = 3, 2

class AdaptiveStrategy:
    def __init__(self, sec_per_car=2):
        self.sec_per_car = sec_per_car
    def green_duration(self, phase, counts):
        cars = sum(counts.get(a, 0) for a in phase["approaches"])
        return max(phase["min_green"], min(phase["max_green"], cars * self.sec_per_car))
    def next_phase(self, phases, current, counts, waited):
        # Most waiting cars wins, but a phase waiting too long (> 120 s) is served first (fairness).
        others = [p for p in phases if p is not current]
        starving = [p for p in others if waited[p["name"]] > 120]
        pool = starving or others
        return max(pool, key=lambda p: sum(counts.get(a, 0) for a in p["approaches"]))

class Controller:
    def __init__(self, phases, strategy):
        self.phases, self.strategy = phases, strategy
        self.current, self.state = phases[0], State.GREEN
        self.counts, self.waited = {}, {p["name"]: 0 for p in phases}
        self.remaining = strategy.green_duration(self.current, self.counts)

    def tick(self, dt=1):
        for p in self.phases:
            if p is not self.current: self.waited[p["name"]] += dt
        self.remaining -= dt
        if self.remaining > 0: return
        if self.state == State.GREEN:
            self.state, self.remaining = State.YELLOW, YELLOW_S
        elif self.state == State.YELLOW:
            self.state, self.remaining = State.ALL_RED, ALL_RED_S      # everyone red: clears the box
        else:
            self.current = self.strategy.next_phase(self.phases, self.current, self.counts, self.waited)
            self.waited[self.current["name"]] = 0
            self.state = State.GREEN
            self.remaining = self.strategy.green_duration(self.current, self.counts)

    def is_green(self, approach):
        return self.state == State.GREEN and approach in self.current["approaches"]

4) Safety and Special Cases

  • No conflicting greens by design: only one phase can be green, and phases are defined so their approaches never conflict. A separate SafetyMonitor (in real systems, a hardware "conflict monitor") double-checks outputs and forces all-red flashing if it ever sees a conflict.
  • Yellow and all-red are fixed, not adaptive, because they're safety timings.
  • Fairness: min and max green, plus the "waited too long" rule, so a quiet road still gets its turn.
  • Pedestrians: a button press marks the phase as requested, and its green is extended to at least the crossing time.
  • Emergency vehicle preemption: finish the current yellow and all-red safely, then give green to the emergency vehicle's approach, then resume.
  • Sensor failure: if counts are missing or stale, switch the strategy to FixedTimeStrategy (the strategy pattern makes this a one-line swap).


5) Beyond One Intersection

  • A central system can coordinate neighboring intersections to create green waves (offsets so cars moving at the speed limit hit consecutive greens).
  • Each controller must still work alone if the network is down (local fixed or adaptive plan).


6) Wrap-Up

Model the intersection as non-conflicting phases driven by a state machine (green → fixed yellow → all-red → next phase), with a pluggable timing strategy. The adaptive strategy sizes green time from waiting-car counts within min and max bounds and picks the next phase by demand, with an anti-starvation rule. Add pedestrian requests, emergency preemption, a safety monitor that forces all-red on conflicts, and a fixed-time fallback when sensors fail.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →