Case Studies

Traffic Signal Control

Design a traffic signal controller — phases, timings, intersections, emergency override.

4 min readDesigns · LLD & Patterns

Interview practice: try designing this yourself before reading. Go to the exercise ↓

Problem Statement

Design a traffic signal control system for an intersection:

  • Cycles through phases (N-S green, all red, E-W green, ...)
  • Configurable phase durations
  • Pedestrian crossing requests
  • Emergency vehicle override
  • Multi-intersection coordination ("green wave")

Requirements

Functional

  • Phases per intersection: NS_GREEN, NS_YELLOW, EW_GREEN, EW_YELLOW, all-red transition
  • Configurable durations
  • Pedestrian button
  • Emergency override (turn target direction green immediately)
  • Multi-intersection coordination

Non-Functional

  • Real-time predictable cycles
  • Fail-safe: on controller failure, fall back to flashing red
  • Auditable timing log

State Machine (single intersection)

This is a textbook State pattern.


Class Diagram


Phase Configuration (Builder)

public class PhaseConfig {
    public final SignalState state;
    public final Duration duration;

    public PhaseConfig(SignalState s, Duration d) {
        this.state = s; this.duration = d;
    }

    public static List<PhaseConfig> defaultCycle() {
        return List.of(
            new PhaseConfig(SignalState.NS_GREEN,  Duration.ofSeconds(30)),
            new PhaseConfig(SignalState.NS_YELLOW, Duration.ofSeconds(4)),
            new PhaseConfig(SignalState.ALL_RED,   Duration.ofSeconds(2)),
            new PhaseConfig(SignalState.EW_GREEN,  Duration.ofSeconds(30)),
            new PhaseConfig(SignalState.EW_YELLOW, Duration.ofSeconds(4)),
            new PhaseConfig(SignalState.ALL_RED,   Duration.ofSeconds(2))
        );
    }
}

Intersection (Core Class)

public class Intersection {
    private final String id;
    private final List<PhaseConfig> phases;
    private int currentPhaseIdx = 0;
    private Instant phaseStartedAt = Instant.now();

    private boolean pedestrianRequested = false;
    private SignalState forcedState = null;     // emergency override

    private final List<TimingEvent> auditLog = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Intersection(String id, List<PhaseConfig> phases) {
        this.id = id;
        this.phases = List.copyOf(phases);
    }

    /** Called every second by the controller's ticker. */
    public void tick() {
        lock.lock();
        try {
            if (forcedState != null) {
                // Emergency: hold the forced state
                return;
            }

            PhaseConfig current = phases.get(currentPhaseIdx);
            Instant now = Instant.now();
            if (Duration.between(phaseStartedAt, now).compareTo(current.duration) >= 0) {
                advancePhase();
            }
        } finally {
            lock.unlock();
        }
    }

    private void advancePhase() {
        // If pedestrian requested and we're entering ALL_RED, extend it
        currentPhaseIdx = (currentPhaseIdx + 1) % phases.size();
        phaseStartedAt = Instant.now();

        PhaseConfig p = phases.get(currentPhaseIdx);
        auditLog.add(new TimingEvent(p.state, phaseStartedAt));

        if (p.state == SignalState.ALL_RED && pedestrianRequested) {
            // Extend by an extra few seconds — handled by adjusting effective duration
            pedestrianRequested = false;
            // (in real impl, swap in a longer ALL_RED variant)
        }
    }

    public void requestPedestrian() {
        lock.lock();
        try { pedestrianRequested = true; }
        finally { lock.unlock(); }
    }

    public void emergencyOverride(Direction dir) {
        lock.lock();
        try {
            forcedState = (dir == Direction.NORTH_SOUTH)
                ? SignalState.NS_GREEN
                : SignalState.EW_GREEN;
            auditLog.add(new TimingEvent(forcedState, Instant.now(), "EMERGENCY"));
        } finally { lock.unlock(); }
    }

    public void clearOverride() {
        lock.lock();
        try {
            forcedState = null;
            phaseStartedAt = Instant.now();
        } finally { lock.unlock(); }
    }

    public SignalState currentState() {
        lock.lock();
        try {
            if (forcedState != null) return forcedState;
            return phases.get(currentPhaseIdx).state;
        } finally { lock.unlock(); }
    }
}

Signal Lights (Mapping State → Lights)

public class IntersectionLights {
    public Map<Direction, Light> lightsFor(SignalState s) {
        return switch (s) {
            case NS_GREEN     -> Map.of(NS, GREEN, EW, RED);
            case NS_YELLOW    -> Map.of(NS, YELLOW, EW, RED);
            case ALL_RED      -> Map.of(NS, RED, EW, RED);
            case EW_GREEN     -> Map.of(NS, RED, EW, GREEN);
            case EW_YELLOW    -> Map.of(NS, RED, EW, YELLOW);
            case FLASHING_RED -> Map.of(NS, RED, EW, RED);
        };
    }
}

Controller (Ticker)

public class TrafficController {
    private final Intersection intersection;
    private final ScheduledExecutorService ticker = Executors.newSingleThreadScheduledExecutor();

    public TrafficController(Intersection intersection) {
        this.intersection = intersection;
    }

    public void start() {
        ticker.scheduleAtFixedRate(intersection::tick, 0, 1, TimeUnit.SECONDS);
    }

    public void stop() {
        ticker.shutdown();
    }
}

A 1-second tick is the controller's heartbeat. State transitions happen on tick.


Multi-Intersection Coordination ("Green Wave")

public class IntersectionCoordinator {
    private final List<Intersection> intersections;
    // Distance between consecutive intersections
    private final List<Duration> travelTimes;

    public IntersectionCoordinator(List<Intersection> intersections, List<Duration> travelTimes) {
        if (travelTimes.size() != intersections.size() - 1) throw new IllegalArgumentException();
        this.intersections = intersections;
        this.travelTimes = travelTimes;
    }

    /** Stagger NS_GREEN starts so a car at speed limit hits all greens. */
    public void scheduleGreenWave() {
        Instant now = Instant.now();
        for (int i = 0; i < intersections.size(); i++) {
            Duration cumulative = Duration.ZERO;
            for (int j = 0; j < i; j++) cumulative = cumulative.plus(travelTimes.get(j));
            // Configure intersection to start NS_GREEN at now + cumulative
            // (omitted: requires intersection to expose a "scheduledStart" field)
        }
    }
}

Sequence: Emergency Vehicle


Edge Cases

CaseHandling
Controller failsHardware fallback to FLASHING_RED
Pedestrian button heldDebounce; one request per cycle
Emergency in opposite direction during transitionForced state interrupts current phase
Power outageBattery backup; flash red on resume
Phase duration 0Validate at config time
Concurrent emergency + pedestrianEmergency wins

Design Patterns Used

PatternWhere
StateSignalState machine
StrategyPhase timings (default / rush-hour / off-peak)
ObserverLights subscribe to state changes
CommandEmergency override as command
MediatorCoordinator orchestrates multiple intersections
SingletonOne controller per intersection

Interview Tips

  • The state machine is the spine — sketch it first.
  • Don't forget the all-red transition between green-yellow and the next green — it's a real safety requirement, not a bug.
  • Bring up emergency override and pedestrian request — they're the "edge" interviewers expect.
  • For multi-intersection, mention green-wave timing: stagger starts by distance / speed.
  • Mention fail-safe behavior (flashing red on power loss) — shows real-world awareness.

Practice

Try it yourself

Design a traffic signal controller for an intersection.

Your design should cover

  • Phases that give green to compatible directions, with timings
  • Lights never show conflicting greens
  • Configurable phase plans per intersection
  • Emergency vehicle override
  • Pedestrian crossing requests
0 / 8,000 characters · saved in this browser

Finished reading?

Mark it done to track your progress in Designs.