Back

Traffic Light

Medium

Streak

0 days

Progress

0%

Submitted

0

Traffic Light

React30 minMediumFreeNew

Prompt

A Traffic Light component is a classic React question that tests how well you manage timed state transitions and side effects. Your task is to build a traffic light that automatically cycles through Red, Yellow, and Green states in sequence, with each state lasting a different amount of time — Red for 4 seconds, Green for 3 seconds, and Yellow for 1 second. The question sounds simple until you notice that detail: each light has a different duration. That single requirement is what separates the candidates who really understand React's effect system from those who have only memorised basic patterns. Your component should display three circular bulbs stacked vertically, highlight only the currently active light, and include a Start/Stop button to pause and resume the cycle.

Requirements

  • →Display three circular bulbs stacked vertically: Red (top), Yellow (middle), Green (bottom)
  • →Only the currently active bulb is lit up — the others are dimmed
  • →Automatically cycle through Red → Green → Yellow → Red
  • →Each light has its own duration: Red 4s, Yellow 1s, Green 3s
  • →A Start/Stop button pauses and resumes the cycle
  • →Smooth transition effect when switching lights
Example
Loading preview...
For the best coding experience, we recommend using a desktop device.
Preparing Sandbox...
Premium interview report

What interviewers score in this build

Use this before reading the code. It tells you what to say, what to test, and where machine-coding candidates usually lose points.

Interview signals

  • React: Uses a single activeIndex state. useEffect with setTimeout (not setInterval) re-runs when activeIndex or isRunning changes. Cleanup function returns clearTimeout to prevent stale transitions.
  • HTML/CSS: Circular bulbs use border-radius: 50%. Active bulb uses a bright color and glow (box-shadow). Inactive bulbs are dark/dimmed. Smooth transition on background and box-shadow.
  • Component Architecture: Lights defined as a config array with name, color, and duration. Cycle logic is a single line of modulo math — not a switch statement. Config-driven design makes it trivial to add a fourth light or change durations.
  • State Management: Only activeIndex and isRunning in state. The current light's name, color, and duration are all derived from the config array — no redundant state. The setTimeout duration also comes from the config.

Time checkpoints

  1. 1

    0:00: Read prompt. Notice the variable durations — this is the key complexity. Clarify: should it start automatically? Should the cycle be Red → Green → Yellow or Red → Yellow → Green?

  2. 2

    3:00: Define the LIGHTS config array with name, color, duration. Sketch state: activeIndex, isRunning.

  3. 3

    7:00: Render three bulbs. Apply active styling based on activeIndex. Test that the correct bulb is lit.

  4. 4

    12:00: Implement the useEffect with setTimeout. Verify the cycle runs automatically.

Edge-case checklist

Empty data and first-load state
Slow network, failed request, and retry path
Keyboard navigation and focus movement
Large input size, re-render pressure, and cleanup

Common mistakes

  • Starting with JSX before naming state and events.
  • Ignoring accessibility until the final minute.
  • Over-building abstractions instead of finishing the required behavior.
  • Failing to narrate trade-offs while coding.
SolutionRead-only · Live Preview

Technical Explanation

Problem Understanding

This question has a deliberate trap built into it: the variable duration requirement. Red lasts 4 seconds. Green lasts 3 seconds. Yellow lasts 1 second. If you reach for setInterval — which fires on a fixed schedule — you cannot satisfy this requirement without a lot of awkward extra logic. The correct tool is setTimeout, scheduled fresh on each state transition with the exact duration for that specific light.

Before touching code, clarify with the interviewer:

  • What is the correct cycle order? (Usually Red → Green → Yellow → Red — mimicking real traffic lights)
  • Should it start running automatically on mount?
  • Is a pause/resume button required?

Component Architecture

The key design decision is making the lights data-driven. Don't hard-code the light logic — define a config array:

const LIGHTS = [
  { name: 'Red',    color: '#ef4444', duration: 4000 },
  { name: 'Green',  color: '#22c55e', duration: 3000 },
  { name: 'Yellow', color: '#eab308', duration: 1000 },
];

Now all your cycle logic works generically from this array. Adding a fourth light, or changing a duration, requires zero changes to logic — just update the config. This is the kind of architecture that impresses interviewers because it shows you're thinking in terms of maintainability, not just "make it work."

💡 Interview Tip: Presenting a config-driven design instead of a hard-coded switch statement is a strong signal of senior-level thinking. Say explicitly: "I'm making this data-driven so that adding a new light or changing durations is a one-line change in the config, not a code change in the logic."

State Design

Two pieces of state. That's all.

const [activeIndex, setActiveIndex] = useState(0); // 0=Red, 1=Green, 2=Yellow
const [isRunning, setIsRunning] = useState(true);

Everything else is derived:

  • LIGHTS[activeIndex].color — what color is the active light?
  • LIGHTS[activeIndex].duration — how long does it stay on?
  • LIGHTS[activeIndex].name — what label to show?
⚠️ Common Mistake: Some candidates store the current light's name or color as separate state variables. This is an anti-pattern — you now have multiple sources of truth that must be kept in sync. If activeIndex is 0, the color is always LIGHTS[0].color. No separate state needed.

Why setTimeout, Not setInterval

This is the most important thing to explain in the interview. Let's compare:

// ❌ WRONG — setInterval fires at a fixed interval
// You cannot give Red 4s and Yellow 1s with a single setInterval
useEffect(() => {
  const timer = setInterval(next, 1000); // all lights get 1s — wrong!
  return () => clearInterval(timer);
}, []);
// ✅ CORRECT — setTimeout with the current light's specific duration
useEffect(() => {
  if (!isRunning) return;
  const timer = setTimeout(() => {
    setActiveIndex(i => (i + 1) % LIGHTS.length);
  }, LIGHTS[activeIndex].duration); // reads the right duration each time
  return () => clearTimeout(timer);
}, [activeIndex, isRunning]);

The setTimeout approach is elegant because of how it integrates with React's effect system. Every time activeIndex changes, the effect re-runs. It reads the duration for the new active light and schedules the next transition accordingly. No external counter, no switch statement — the state machine drives itself.

💡 Interview Tip: Say this out loud: "I'm using setTimeout rather than setInterval because each light has a different duration. Each time activeIndex changes, the effect re-runs and schedules the next transition with the correct duration for that specific light. The staggered durations come naturally from the config array."

Step-by-Step Implementation

The Effect

useEffect(() => {
  if (!isRunning) return; // guard: do nothing when paused

  const timer = setTimeout(() => {
    setActiveIndex(i => (i + 1) % LIGHTS.length); // advance to next light
  }, LIGHTS[activeIndex].duration);               // use THIS light's duration

  return () => clearTimeout(timer); // cancel if re-rendered before timer fires
}, [activeIndex, isRunning]); // re-run when either of these changes

Let's trace through this step by step:

  1. Component mounts. activeIndex=0 (Red), isRunning=true. Effect runs. Sets a 4000ms timer.
  2. After 4 seconds, the timer fires. setActiveIndex(i => (0+1)%3 = 1). State updates to Green.
  3. State change triggers a re-render. useEffect cleanup runs — clearTimeout on the old timer (already fired, so harmless). Effect runs again with activeIndex=1. Sets a 3000ms timer.
  4. After 3 seconds, transitions to Yellow (index 2). Sets a 1000ms timer.
  5. After 1 second, wraps back to Red (index 0). Cycle repeats.

The Cleanup Function — Why It Matters

Imagine the user clicks Stop 2 seconds into the Red phase. What happens?

  1. User clicks Stop → setIsRunning(false).
  2. Re-render triggers. useEffect cleanup runs → clearTimeout(timer). The 4s timer is cancelled. Red will not transition to Green.
  3. Effect runs again. isRunning is false → early return. No new timer is set.

Without the cleanup function, the 4s timer would still fire after Stop is pressed, causing an unwanted state transition. The user pressed Stop but the light keeps changing — a bug that's easy to miss in testing but breaks the user experience.

⚠️ Common Mistake: Not returning a cleanup function from the effect. The timer fires even after Stop is pressed, even after the component unmounts. This causes the dreaded "Can't perform a React state update on an unmounted component" warning and can create subtle bugs in navigation-heavy apps.

Full Component

const LIGHTS = [
  { name: 'Red',    color: '#ef4444', duration: 4000 },
  { name: 'Green',  color: '#22c55e', duration: 3000 },
  { name: 'Yellow', color: '#eab308', duration: 1000 },
];

export default function TrafficLight() {
  const [activeIndex, setActiveIndex] = useState(0);
  const [isRunning, setIsRunning] = useState(true);

  useEffect(() => {
    if (!isRunning) return;
    const timer = setTimeout(() => {
      setActiveIndex(i => (i + 1) % LIGHTS.length);
    }, LIGHTS[activeIndex].duration);
    return () => clearTimeout(timer);
  }, [activeIndex, isRunning]);

  return (
    <div className="tl-page">
      <div className="tl-housing">
        {LIGHTS.map((light, i) => (
          <div
            key={light.name}
            className="tl-bulb"
            style={{
              background: i === activeIndex ? light.color : '#1e293b',
              boxShadow: i === activeIndex
                ? \`0 0 24px 6px \${light.color}60\`
                : 'none',
            }}
          />
        ))}
      </div>
      <p className="tl-label">{LIGHTS[activeIndex].name}</p>
      <button onClick={() => setIsRunning(r => !r)}>
        {isRunning ? 'Stop' : 'Start'}
      </button>
    </div>
  );
}

Edge Cases

  • Component unmounts mid-cycle: The cleanup function cancels the pending timer. No state update fires on a dead component.
  • Rapid Stop/Start: Each click of Stop/Start toggles isRunning. The effect cleans up the old timer and either sets a new one (if running) or returns early (if stopped). No race condition.
  • activeIndex out of range: Using % LIGHTS.length wraps correctly. Index can never exceed the array bounds.

Styling Notes

The visual key for this component is the glow effect on the active bulb. Using box-shadow with a semi-transparent version of the bulb color creates a realistic lamp glow:

// Active bulb — lit up with glow
boxShadow: \`0 0 24px 6px \${light.color}60\` // 60 = 38% opacity in hex

// Inactive bulb — dark background
background: '#1e293b'
💡 Interview Tip: Adding a transition: background 0.3s, box-shadow 0.3s on the bulb div creates a smooth fade between states — the light appears to turn on and off gradually rather than snapping. This level of polish, delivered in 28 minutes, tells the interviewer you have strong front-end instincts.

Common Pitfalls Summary

  • Using setInterval: Cannot give different durations per light without complex extra logic. Always use setTimeout for variable-duration state machines.
  • Missing cleanup function: Timers fire after the component unmounts or after Stop is pressed — causes bugs and React warnings.
  • Hard-coding the cycle in a switch statement: Not extensible. Config-driven is always better for repeating patterns.
  • Not using the functional form of setState: Inside a setTimeout callback, setActiveIndex(activeIndex + 1) captures a stale closure value. Always use setActiveIndex(i => (i + 1) % LIGHTS.length) to get the latest state.
  • Wrong cycle order: Real traffic lights go Red → Green → Yellow → Red (not Red → Yellow → Green). This is a small detail but shows attention to real-world accuracy.

How to Communicate During the Interview

  • "I noticed each light has a different duration — that means setInterval won't work here. I'm using setTimeout instead, which I re-schedule on each state change with the correct duration for that specific light."
  • "My lights are defined as a config array, not hard-coded in the JSX. This makes the component completely generic — you could change the durations or add a fourth light by just editing the config."
  • "The cleanup function in my useEffect is critical. When isRunning changes to false, the cleanup cancels the pending timer so the light doesn't keep advancing even after the user presses Stop."
  • "I'm using the functional form of setActiveIndex to avoid stale closure issues inside the setTimeout callback."

Interview Criteria

React

Uses a single activeIndex state. useEffect with setTimeout (not setInterval) re-runs when activeIndex or isRunning changes. Cleanup function returns clearTimeout to prevent stale transitions.

HTML/CSS

Circular bulbs use border-radius: 50%. Active bulb uses a bright color and glow (box-shadow). Inactive bulbs are dark/dimmed. Smooth transition on background and box-shadow.

Component Architecture

Lights defined as a config array with name, color, and duration. Cycle logic is a single line of modulo math — not a switch statement. Config-driven design makes it trivial to add a fourth light or change durations.

State Management

Only activeIndex and isRunning in state. The current light's name, color, and duration are all derived from the config array — no redundant state. The setTimeout duration also comes from the config.

Edge Cases

isRunning=false causes the effect to return early, setting no timer. Interval cleanup prevents transitions firing after Stop is pressed. Correctly handles the component unmounting mid-cycle.

Time Checkpoints

0:00

0:00: Read prompt. Notice the variable durations — this is the key complexity. Clarify: should it start automatically? Should the cycle be Red → Green → Yellow or Red → Yellow → Green?

3:00

3:00: Define the LIGHTS config array with name, color, duration. Sketch state: activeIndex, isRunning.

7:00

7:00: Render three bulbs. Apply active styling based on activeIndex. Test that the correct bulb is lit.

12:00

12:00: Implement the useEffect with setTimeout. Verify the cycle runs automatically.

18:00

18:00: Add isRunning state and Start/Stop button. Guard the effect with if (!isRunning) return.

24:00

24:00: Add transition CSS, glow effect on active bulb, polish the housing design.

28:00

28:00: Walk the interviewer through your solution. Explain why setTimeout over setInterval.

Streak

0 days

Last active: Sign in to track

Progress

0%

0/0 solved

Submitted

0

Solutions pushed to review history.