Low Level Design

Design a Traffic Signal Controller

A complete low-level design walkthrough for a traffic signal control system — State pattern for signal transitions, Singleton context as the FSM, and sensor-driven adaptive durations with emergency override.

August 11, 2026·12 min read

Problem Description#

Design a Traffic Signal Controller that manages signal transitions at an intersection — cycling through Red → Yellow → Green — with durations that adapt to real-time traffic levels and can respond to emergency vehicles.

At its core, a traffic light is a finite state machine:

  • The system is always in exactly one state (Red, Yellow, or Green)
  • Each state has its own duration and knows which state comes next
  • Duration is not fixed — it adjusts based on a sensor reading (LOW / MEDIUM / HIGH / EMERGENCY)
  • An emergency overrides the normal cycle and forces immediate Green

This problem is a textbook application of the State pattern: behavior changes based on current state, and adding a new state (e.g., Flashing Yellow) requires zero changes to existing code.


Clarify Requirements#

Before designing, ask these in an interview:

Functional

  • What states does the signal support? (Red, Yellow, Green — any others like flashing?)
  • Are durations fixed or traffic-adaptive?
  • How is traffic level detected — sensor, API, manual input?
  • How should emergency vehicles be handled — immediate green? Priority lane?
  • Should the system support multi-intersection coordination?

Non-functional

  • Does the controller run indefinitely or for a fixed number of cycles?
  • Is thread safety required (multiple controllers per intersection)?
  • Should state transitions be logged or auditable?

Final Requirements#

After clarification, here's what we'll build:

  • Three states: Red, Yellow, Green — each encapsulates its own duration and next-state logic
  • TrafficSensor simulates live traffic readings: LOW (2s), MEDIUM (3s), HIGH (4s), EMERGENCY (override)
  • On EMERGENCY: whichever state detects it transitions immediately to GreenLightState
  • TrafficSignalContext is a Singleton FSM: holds current state, delegates timing to it, then calls getNextState() for the transition
  • The signal loop runs indefinitely — each cycle: setDuration() → sleep() → transition

Core Entities#

EntityResponsibility
TrafficStateInterface — getDuration(), setDuration(), getNextState(), setContext()
RedLightStateStop phase; next → Yellow; duration 2–4s; EMERGENCY → immediate Green
YellowLightStateCaution phase; next → Green; duration 2–4s; EMERGENCY → immediate Green
GreenLightStateGo phase; next → Red; duration 2–5s; EMERGENCY extends to 5s
TrafficSensorSimulates a sensor reading — returns LOW / MEDIUM / HIGH / EMERGENCY randomly
TrafficSignalContextSingleton FSM — holds current state, drives the signal loop, exposes setState() for emergency override

Patterns Used#

1. State Pattern — TrafficState / RedLightState / YellowLightState / GreenLightState#

The State pattern is the core of this design. Instead of a single class with a giant if/else or switch on the current signal colour, each state is its own class. It knows:

  • How long it should last (setDuration reads the sensor and sets time)
  • What state follows it (getNextState)
  • What to do on EMERGENCY (override the context's state directly via context.setState())

Adding a FlashingYellowState or PedestrianCrossingState is a pure addition — no existing state class changes.

2. Singleton — TrafficSignalContext#

There is exactly one intersection controller. TrafficSignalContext.getInstance() uses a synchronized factory method for thread-safe lazy initialization. The single instance is the authority over which state is active and drives the infinite signal loop.

3. Context as Finite State Machine#

TrafficSignalContext.startTrafficSignal() is the FSM engine:

loop:
  currentState.setDuration()   ← sensor-driven, may override state
  sleep(currentState.getDuration())
  transition to currentState.getNextState()

Each state can short-circuit the normal transition by calling context.setState(new GreenLightState()) directly — this is the emergency-vehicle override path.


Code#

State Interface#

TrafficState defines the full contract. setContext lets states call back to the FSM for emergency overrides.

java
public interface TrafficState {
    int getDuration();
    void setDuration() throws InterruptedException;
    TrafficState getNextState();
    void setContext(TrafficSignalContext context);
}

Concrete States#

Each state reads the sensor in setDuration() and adjusts its timer. EMERGENCY triggers an immediate context transition to Green.

java
class RedLightState implements TrafficState {
    private int time = 3;
    private TrafficSignalContext context;

    @Override public TrafficState getNextState()            { return new YellowLightState(); }
    @Override public void setContext(TrafficSignalContext c){ this.context = c; }
    @Override public int getDuration()                      { return time; }

    @Override
    public void setDuration() {
        String level = new TrafficSensor().getTrafficLevel();
        switch (level) {
            case "LOW"       -> this.time = 2;
            case "MEDIUM"    -> this.time = 3;
            case "HIGH"      -> this.time = 4;
            case "EMERGENCY" -> {
                System.out.println("🚨 Emergency detected at Red — switching to Green!");
                context.setState(new GreenLightState());
            }
            default -> System.out.println("No sensor data — using default duration.");
        }
    }
}

Sensor & Context#

TrafficSensor simulates a live reading. TrafficSignalContext is the Singleton FSM — it owns the state and drives the loop.

java
class TrafficSensor {
    public String getTrafficLevel() {
        return switch ((int)(Math.random() * 4)) {
            case 0  -> "LOW";
            case 1  -> "MEDIUM";
            case 2  -> "HIGH";
            default -> { System.out.println("🚨 Emergency vehicle detected!"); yield "EMERGENCY"; }
        };
    }
}

Demo#

java
public class TrafficSignalControl {
    public static void main(String[] args) throws InterruptedException {
        TrafficSignalContext context = TrafficSignalContext.getInstance();
        context.startTrafficSignal();
        // Output (example):
        // 🚦 RedLightState | 3s
        // 🚦 YellowLightState | 2s
        // 🚨 Emergency vehicle detected!
        // 🚨 Emergency during Yellow — switching to Green!
        // 🚦 GreenLightState | 4s
        // 🚦 RedLightState | 2s  ← LOW traffic
    }
}

Class Diagram#


Extendible — Follow Ups#

1. Add a Flashing Yellow (night mode) state#

Create FlashingYellowState implements TrafficState — its getNextState() returns itself and its setDuration() ignores the sensor. Wire it in by calling context.setState(new FlashingYellowState()) at night. Zero changes to existing states.

2. Multi-intersection coordination#

Replace the single TrafficSignalContext with an IntersectionController that owns four TrafficSignalContext instances (one per road). It enforces the constraint that at most one road is green at a time using a shared lock or a coordinating state machine.

3. Observer for state-change events#

Add a TrafficStateObserver interface with onStateChange(TrafficState state). TrafficSignalContext.setState() notifies all registered observers — useful for display boards, logging services, or an emergency-vehicle dispatch system.

4. Configurable durations via strategy#

Extract duration logic out of each state into a DurationStrategy interface (int computeDuration(String trafficLevel)). Inject it per state — swap FixedDurationStrategy in tests and SensorDurationStrategy in production without changing any state class.

5. Real sensor integration#

Replace TrafficSensor's random number with a call to an HTTP sensor API or a message queue consumer. Since TrafficSensor is already isolated behind its own class boundary, the change is confined to one class.

6. Audit trail / event sourcing#

Log every state transition — timestamp, previous state, new state, trigger (normal or emergency) — to an append-only TransitionLog. Replay the log to reconstruct intersection history for traffic engineering analysis.