1279. Traffic Light Controlled Intersection 🔒
Problem Description
This problem asks you to design a traffic light controller for an intersection where two roads meet. Road A runs North-South (directions 1 and 2), and Road B runs East-West (directions 3 and 4).
The intersection has the following rules:
- Each road has a traffic light that can be either green or red
- When a light is green, cars from both directions on that road can cross
- When a light is red, cars must wait
- Only one road can have a green light at a time - when Road A is green, Road B must be red, and vice versa
- Initially, Road A has a green light and Road B has a red light
You need to implement the carArrived
method that gets called whenever a car arrives at the intersection. This method receives:
carId
: The unique identifier of the arriving carroadId
: Which road the car is on (1 for Road A, 2 for Road B)direction
: The direction the car is traveling (1-4)turnGreen
: A function to change the traffic light to green on the current roadcrossCar
: A function to let the current car cross the intersection
The key requirements are:
- Avoid deadlock - cars should not get stuck waiting forever
- Prevent collisions - cars from different roads should never cross at the same time
- Correct light management - only call
turnGreen()
when actually changing the light from red to green (calling it when already green is wrong)
The solution uses a lock to ensure thread safety and tracks which road currently has the green light. When a car arrives, it acquires the lock, checks if its road needs the light changed, crosses the intersection, then releases the lock. This ensures cars cross one at a time and the light changes only when necessary.
Intuition
The core challenge here is coordinating multiple cars arriving from different roads while ensuring they don't collide and don't deadlock. Let's think through what could go wrong:
- Race conditions: If multiple cars try to cross simultaneously without coordination, cars from different roads might collide
- Deadlock: If we're not careful with our synchronization, cars could end up waiting for each other indefinitely
- Incorrect light changes: We need to track which road currently has the green light to avoid unnecessary
turnGreen()
calls
The key insight is that the intersection itself is a shared resource that only one car can use at a time. This immediately suggests using a mutual exclusion mechanism - a lock.
Why does a simple lock work here? Consider what happens:
- When a car arrives, it must wait for exclusive access to the intersection
- Once it has access, it can safely check if the light needs changing
- No other car can interfere during this critical section
- After crossing, it releases the lock for the next car
The beauty of this approach is that it naturally handles the traffic light state. We only need to track which road currently has green (self.road
). When a car from a different road arrives:
- It compares its
roadId
with the current green road - If different, it calls
turnGreen()
and updates the tracked state - If same, the light is already green, so just cross
This serialization through the lock ensures:
- No collisions: Only one car crosses at a time
- No deadlock: The lock is always released after crossing, so the next car can proceed
- Correct light management: We only change lights when switching roads
The solution is elegant because it transforms a complex traffic coordination problem into a simple critical section problem - whoever holds the lock gets to make decisions about the intersection state and cross safely.
Solution Approach
The implementation uses a mutex lock pattern to ensure thread-safe access to the intersection. Let's walk through the solution step by step:
Data Structures Used
- Lock (Mutex):
self.lock = Lock()
- A threading lock that ensures mutual exclusion - State Variable:
self.road = 1
- Tracks which road currently has the green light (initialized to Road A)
Implementation Details
The __init__
method sets up our traffic light controller:
def __init__(self):
self.lock = Lock() # Create a mutex for synchronization
self.road = 1 # Road A (1) initially has green light
The carArrived
method implements the core logic:
def carArrived(self, carId, roadId, direction, turnGreen, crossCar):
self.lock.acquire() # Step 1: Acquire exclusive access
if self.road != roadId: # Step 2: Check if light change needed
self.road = roadId # Step 3: Update state
turnGreen() # Step 4: Change the light
crossCar() # Step 5: Let car cross
self.lock.release() # Step 6: Release lock for next car
Algorithm Flow
-
Acquire Lock: When a car arrives, it first acquires the lock. If another car is currently crossing, this car will block and wait.
-
Check Light State: Once the lock is acquired, check if the car's road (
roadId
) matches the road that currently has green (self.road
). -
Change Light if Needed:
- If
self.road != roadId
, the car is on a different road than the one with green light - Update
self.road
to the new road - Call
turnGreen()
to change the traffic light
- If
-
Cross Intersection: Call
crossCar()
to let the current car cross safely -
Release Lock: Release the lock so the next waiting car can proceed
Why This Works
- Prevents Collisions: The lock ensures only one car can be in the critical section (checking lights and crossing) at a time
- Avoids Deadlock: The lock is always released after crossing, guaranteeing progress
- Correct Light Management: We only call
turnGreen()
when actually switching roads, never when the light is already green for the current road - Thread Safety: All state changes happen within the locked section, preventing race conditions
The pattern used here is essentially a monitor pattern where the lock protects the shared state (self.road
) and ensures atomic execution of the traffic control logic.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a scenario with 4 cars arriving at the intersection to see how the solution handles traffic coordination:
Initial State: Road A has green light (self.road = 1
), Road B has red light
Car Sequence:
- Car #1 arrives on Road A (roadId=1)
- Car #2 arrives on Road B (roadId=2)
- Car #3 arrives on Road B (roadId=2)
- Car #4 arrives on Road A (roadId=1)
Step-by-Step Execution:
Car #1 (Road A):
- Acquires lock (no competition)
- Checks:
self.road (1) == roadId (1)
→ Already green, no light change needed - Calls
crossCar()
to cross intersection - Releases lock
- State remains:
self.road = 1
Car #2 (Road B):
- Acquires lock (might wait if Car #1 still crossing)
- Checks:
self.road (1) != roadId (2)
→ Different road, needs light change - Updates:
self.road = 2
- Calls
turnGreen()
to change light to Road B - Calls
crossCar()
to cross intersection - Releases lock
- New state:
self.road = 2
Car #3 (Road B):
- Acquires lock (waits for Car #2 to finish)
- Checks:
self.road (2) == roadId (2)
→ Already green, no light change needed - Calls
crossCar()
to cross intersection - Releases lock
- State remains:
self.road = 2
Car #4 (Road A):
- Acquires lock (waits for Car #3 to finish)
- Checks:
self.road (2) != roadId (1)
→ Different road, needs light change - Updates:
self.road = 1
- Calls
turnGreen()
to change light back to Road A - Calls
crossCar()
to cross intersection - Releases lock
- New state:
self.road = 1
Key Observations:
- The light only changes twice: when Car #2 switches from Road A to B, and when Car #4 switches back from Road B to A
- Cars #1 and #3 don't trigger light changes since they're on roads that already have green
- The lock ensures cars cross one at a time, preventing collisions
- Each car releases the lock after crossing, preventing deadlock
This example demonstrates how the solution efficiently manages traffic flow with minimal light changes while maintaining safety.
Solution Implementation
1from threading import Lock
2from typing import Callable
3
4
5class TrafficLight:
6 """
7 Traffic light controller for managing intersection between two roads.
8 Ensures that cars from the same road can pass continuously without switching lights,
9 and switches the light only when a car from a different road arrives.
10 """
11
12 def __init__(self) -> None:
13 """Initialize the traffic light with a lock for thread safety and default road."""
14 # Lock to ensure thread-safe access to the traffic light state
15 self.lock: Lock = Lock()
16 # Current road that has the green light (1 for road A, 2 for road B)
17 self.road: int = 1
18
19 def carArrived(
20 self,
21 carId: int, # Unique identifier of the car
22 roadId: int, # Road the car is on (1 for road A, 2 for road B)
23 direction: int, # Direction the car is traveling
24 turnGreen: Callable[[], None], # Function to turn the traffic light green for current road
25 crossCar: Callable[[], None] # Function to allow the car to cross the intersection
26 ) -> None:
27 """
28 Handle a car arriving at the intersection.
29
30 Args:
31 carId: Unique identifier of the arriving car
32 roadId: ID of the road (1 or 2) the car is traveling on
33 direction: Direction of travel for the car
34 turnGreen: Callback function to switch the light to green for the current road
35 crossCar: Callback function to let the car cross the intersection
36 """
37 # Acquire lock to ensure exclusive access to the traffic light state
38 self.lock.acquire()
39
40 # Check if the car is on a different road than the current green light
41 if self.road != roadId:
42 # Switch the green light to the new road
43 self.road = roadId
44 turnGreen()
45
46 # Allow the car to cross the intersection
47 crossCar()
48
49 # Release the lock for other waiting cars
50 self.lock.release()
51
1/**
2 * Traffic Light Controller for managing intersection crossing
3 * Ensures cars from different roads don't cross simultaneously
4 */
5class TrafficLight {
6
7 // Tracks which road currently has the green light (1 for road A, 2 for road B)
8 private int currentGreenRoad = 1;
9
10 /**
11 * Constructor initializes the traffic light system
12 * Default: Road 1 (road A) has the green light initially
13 */
14 public TrafficLight() {
15 }
16
17 /**
18 * Handles car arrival at the intersection
19 * Ensures thread-safe operation using synchronized keyword
20 *
21 * @param carId Unique identifier of the arriving car
22 * @param roadId ID of the road the car is on (1 for road A, 2 for road B)
23 * @param direction Direction the car is traveling
24 * @param turnGreen Runnable to switch the traffic light to green for current road
25 * @param crossCar Runnable to allow the car to cross the intersection
26 */
27 public synchronized void carArrived(
28 int carId, // ID of the car
29 int roadId, // ID of the road the car travels on. Can be 1 (road A) or 2 (road B)
30 int direction, // Direction of the car
31 Runnable turnGreen, // Use turnGreen.run() to turn light to green on current road
32 Runnable crossCar // Use crossCar.run() to make car cross the intersection
33 ) {
34 // If car's road doesn't have green light, switch the light
35 if (roadId != currentGreenRoad) {
36 turnGreen.run();
37 currentGreenRoad = roadId;
38 }
39
40 // Allow the car to cross the intersection
41 crossCar.run();
42 }
43}
44
1/**
2 * Traffic Light Controller for managing intersection crossing
3 * Ensures cars from different roads don't cross simultaneously
4 */
5class TrafficLight {
6private:
7 // Tracks which road currently has the green light (1 for road A, 2 for road B)
8 int currentGreenRoad;
9 // Mutex for thread-safe synchronization
10 std::mutex mtx;
11
12public:
13 /**
14 * Constructor initializes the traffic light system
15 * Default: Road 1 (road A) has the green light initially
16 */
17 TrafficLight() {
18 currentGreenRoad = 1;
19 }
20
21 /**
22 * Handles car arrival at the intersection
23 * Ensures thread-safe operation using mutex lock
24 *
25 * @param carId Unique identifier of the arriving car
26 * @param roadId ID of the road the car is on (1 for road A, 2 for road B)
27 * @param direction Direction the car is traveling
28 * @param turnGreen Function to switch the traffic light to green for current road
29 * @param crossCar Function to allow the car to cross the intersection
30 */
31 void carArrived(
32 int carId, // ID of the car
33 int roadId, // ID of the road the car travels on. Can be 1 (road A) or 2 (road B)
34 int direction, // Direction of the car
35 std::function<void()> turnGreen, // Use turnGreen() to turn light to green on current road
36 std::function<void()> crossCar // Use crossCar() to make car cross the intersection
37 ) {
38 // Lock the mutex for thread-safe access
39 std::lock_guard<std::mutex> lock(mtx);
40
41 // If car's road doesn't have green light, switch the light
42 if (roadId != currentGreenRoad) {
43 turnGreen();
44 currentGreenRoad = roadId;
45 }
46
47 // Allow the car to cross the intersection
48 crossCar();
49 }
50};
51
1/**
2 * Traffic Light Controller for managing intersection crossing
3 * Ensures cars from different roads don't cross simultaneously
4 */
5
6// Tracks which road currently has the green light (1 for road A, 2 for road B)
7let currentGreenRoad: number = 1;
8
9// Mutex lock for synchronization
10let isLocked: boolean = false;
11const lockQueue: (() => void)[] = [];
12
13/**
14 * Acquires a lock for thread-safe operation
15 * Implements a simple mutex using async/await
16 */
17async function acquireLock(): Promise<void> {
18 while (isLocked) {
19 await new Promise<void>((resolve) => {
20 lockQueue.push(resolve);
21 });
22 }
23 isLocked = true;
24}
25
26/**
27 * Releases the lock and notifies waiting threads
28 */
29function releaseLock(): void {
30 isLocked = false;
31 const nextInQueue = lockQueue.shift();
32 if (nextInQueue) {
33 nextInQueue();
34 }
35}
36
37/**
38 * Handles car arrival at the intersection
39 * Ensures thread-safe operation using lock mechanism
40 *
41 * @param carId Unique identifier of the arriving car
42 * @param roadId ID of the road the car is on (1 for road A, 2 for road B)
43 * @param direction Direction the car is traveling
44 * @param turnGreen Callback to switch the traffic light to green for current road
45 * @param crossCar Callback to allow the car to cross the intersection
46 */
47async function carArrived(
48 carId: number, // ID of the car
49 roadId: number, // ID of the road the car travels on. Can be 1 (road A) or 2 (road B)
50 direction: number, // Direction of the car
51 turnGreen: () => void, // Use turnGreen() to turn light to green on current road
52 crossCar: () => void // Use crossCar() to make car cross the intersection
53): Promise<void> {
54 // Acquire lock for thread-safe operation
55 await acquireLock();
56
57 try {
58 // If car's road doesn't have green light, switch the light
59 if (roadId !== currentGreenRoad) {
60 turnGreen();
61 currentGreenRoad = roadId;
62 }
63
64 // Allow the car to cross the intersection
65 crossCar();
66 } finally {
67 // Always release the lock
68 releaseLock();
69 }
70}
71
Time and Space Complexity
Time Complexity: O(1)
- The
carArrived
method performs a fixed number of operations regardless of input size lock.acquire()
-O(1)
in the best case (no contention), but can beO(n)
in worst case wheren
is the number of waiting threads- Comparison
self.road != roadId
-O(1)
- Assignment
self.road = roadId
-O(1)
- Function calls
turnGreen()
andcrossCar()
-O(1)
(assuming these are simple callback operations) lock.release()
-O(1)
- Overall amortized time complexity per operation is
O(1)
Space Complexity: O(1)
- The class maintains only two instance variables:
self.lock
- a Lock object which takes constant spaceself.road
- an integer variable storing the current road ID
- No additional data structures are created that grow with input
- The method parameters use constant space regardless of the number of cars
- No recursive calls that would increase the call stack
- Total space usage remains constant:
O(1)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Release the Lock on Exception
The current implementation has a critical flaw: if turnGreen()
or crossCar()
throws an exception, the lock will never be released, causing a permanent deadlock where no other car can ever cross the intersection.
Problem Code:
def carArrived(self, carId, roadId, direction, turnGreen, crossCar):
self.lock.acquire()
if self.road != roadId:
self.road = roadId
turnGreen() # If this throws an exception...
crossCar() # Or if this throws an exception...
self.lock.release() # This line never executes!
Solution: Use a context manager or try-finally block:
def carArrived(self, carId, roadId, direction, turnGreen, crossCar):
with self.lock: # Automatically releases lock even on exception
if self.road != roadId:
self.road = roadId
turnGreen()
crossCar()
Or with try-finally:
def carArrived(self, carId, roadId, direction, turnGreen, crossCar):
self.lock.acquire()
try:
if self.road != roadId:
self.road = roadId
turnGreen()
crossCar()
finally:
self.lock.release() # Always executes
2. Using Road ID Instead of Proper Road Grouping
A subtle pitfall is confusing the road ID with direction grouping. The problem states:
- Road A: directions 1 and 2 (North-South)
- Road B: directions 3 and 4 (East-West)
Some might incorrectly map roadId
directly to direction or assume roadId
equals 1 or 2 based on direction numbers.
Incorrect Implementation:
def carArrived(self, carId, roadId, direction, turnGreen, crossCar):
with self.lock:
# Wrong: using direction to determine road
current_road = 1 if direction <= 2 else 2
if self.road != current_road:
self.road = current_road
turnGreen()
crossCar()
Correct: Trust the roadId
parameter provided - it already indicates which road (1 for Road A, 2 for Road B).
3. Calling turnGreen() Unnecessarily
A common mistake is calling turnGreen()
every time a car arrives, even when the light is already green for that road:
Incorrect:
def carArrived(self, carId, roadId, direction, turnGreen, crossCar):
with self.lock:
self.road = roadId
turnGreen() # Wrong: calls even when light is already green
crossCar()
This violates the requirement that turnGreen()
should only be called when actually changing the light from red to green.
4. Over-complicating with Multiple Locks or Conditions
Some implementations try to use separate locks for each road or complex condition variables:
Over-complicated:
def __init__(self):
self.lock_a = Lock()
self.lock_b = Lock()
self.condition = Condition()
# Unnecessarily complex!
Better: A single lock is sufficient and cleaner since only one road can be green at a time anyway.
5. Not Initializing the Road State Properly
Forgetting to initialize self.road
or setting it to an invalid value:
Incorrect:
def __init__(self):
self.lock = Lock()
# Missing: self.road initialization
# Or wrong: self.road = 0 or self.road = None
This would cause the first car from Road A to unnecessarily call turnGreen()
even though Road A already has the green light initially.
Which data structure is used in a depth first search?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!