Back

Performant Image Slider

medium

Streak

0 days

Progress

0%

Submitted

0

Performant Image Slider

React40 minmediumFreeNew

Prompt

An image slider (also called a carousel) is one of the most common UI patterns on the web — you see it on e-commerce product pages, portfolio sites, and marketing landing pages everywhere. Your task is to build a fully functional image slider in React that displays one image at a time from a provided array of images, with Previous and Next navigation buttons. But don't let the familiarity fool you. This question tests several things at once: how you manage cyclic navigation (wrapping from the last image back to the first), how you implement timed auto-play correctly without memory leaks, and whether you can deliver a polished, accessible UI under time pressure. Your component will receive an array of image objects (each with a src and alt), and must handle navigation, dot indicators for position tracking, auto-play that pauses on hover, and graceful handling of edge cases like a single image or an empty array.

Requirements

  • →Display one image at a time from a provided images array
  • →Previous and Next buttons navigate between images
  • →Wrap around — clicking Next on the last image goes to the first, and vice versa
  • →Show dot indicators at the bottom representing each image; the active dot is highlighted
  • →Clicking a dot navigates directly to that image
  • →Auto-play cycles through images every 3 seconds
  • →Auto-play pauses when the user hovers over the slider
  • →Hide navigation buttons and dots when only one image is provided
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 currentIndex state. useEffect with setInterval for auto-play includes a cleanup function. onMouseEnter/onMouseLeave on the container manages the isPaused flag. Images are accessed via images[currentIndex] — pure derivation, no redundant state.
  • HTML/CSS: Images have descriptive alt text for screen readers. The slider container has a defined aspect ratio. object-fit:cover ensures images fill the frame without distortion. Dot buttons use aria-label='Go to slide N' for accessibility.
  • Component Architecture: The slider is self-contained — no external state management needed. Navigation logic (prev/next) is clean and reusable. Images, autoPlayInterval, and onSlideChange are configurable via props.
  • State Management: Only currentIndex and isPaused live in state. The current image, total count, and whether nav buttons should show are all derived. This is a good example of keeping state minimal.

Time checkpoints

  1. 1

    0:00: Interview starts. Read the prompt. Ask clarifying questions: What is the images prop shape? Is auto-play required? Should it pause on hover?

  2. 2

    3:00: Plan state: just currentIndex. Sketch out the component structure mentally before touching code.

  3. 3

    7:00: Render the current image, Previous/Next buttons, and wire up click handlers with wrapping logic.

  4. 4

    13:00: Add dot indicators. Clicking a dot sets currentIndex directly.

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

Let me walk you through this properly. An image slider sounds simple, but there are several subtle things that separate a junior implementation from a senior one: wrapping navigation, auto-play without memory leaks, hover pause, and accessibility. Before writing any code, ask the interviewer these questions:

  • What does the images prop look like — an array of URLs, or objects with src and alt?
  • Is auto-play required, and should it pause on hover?
  • Should clicking a dot navigate to that slide?
  • What should happen if images is empty or has only one item?

Spending two minutes on clarification shows the interviewer you think before you code. It's one of the biggest signals of seniority.

Component Architecture

For a slider, one component is usually enough. You don't need to split this into sub-components unless the interviewer asks you to, or the complexity grows. Keep it simple and focused. The component accepts these props:

// Usage
<ImageSlider
  images={[
    { src: '/img/photo1.jpg', alt: 'Mountain landscape' },
    { src: '/img/photo2.jpg', alt: 'Ocean sunset' },
  ]}
  autoPlayInterval={3000}
/>
💡 Interview Tip: Defining your component's props API before implementing it is a senior move. It forces you to think about the interface before the implementation — and it gives you something concrete to discuss with the interviewer before you start typing.

State Design

Here is the key insight: you only need two pieces of state.

const [currentIndex, setCurrentIndex] = useState(0);
const [isPaused, setIsPaused] = useState(false);

Everything else is derived from these:

  • images[currentIndex] gives you the current image — no separate currentImage state
  • images.length gives you the total count
  • images.length <= 1 tells you whether to hide navigation
⚠️ Common Mistake: Some candidates create a currentImage state and sync it with currentIndex in a useEffect. This is redundant — you now have two sources of truth that can go out of sync. Derive it directly: const currentImage = images[currentIndex].

Step-by-Step Implementation

Navigation Logic

This is the most important part to get right. The wrapping formula is:

const prev = () => setCurrentIndex(i => (i - 1 + images.length) % images.length);
const next = () => setCurrentIndex(i => (i + 1) % images.length);
⚠️ Common Mistake: Many candidates write (currentIndex - 1) % images.length for previous. In JavaScript, -1 % 5 returns -1, not 4. Python handles this correctly but JavaScript does not. You must add images.length before taking the modulo when going backwards: (i - 1 + images.length) % images.length. This is a gotcha that trips up many experienced developers — calling it out shows you know JavaScript deeply.
💡 Interview Tip: Using the functional form of setState (i => ...) instead of currentIndex directly is best practice here. It avoids stale closure issues, especially since this same function will be called from inside a setInterval callback.

Auto-play with useEffect

useEffect(() => {
  if (isPaused || images.length <= 1) return;

  const timer = setInterval(next, autoPlayInterval);
  return () => clearInterval(timer); // This line is critical
}, [isPaused, images.length, autoPlayInterval]);

Let me explain every line here:

  • The guard clause: If paused or only one image, don't start a timer at all. Return immediately.
  • setInterval(next, ...): Call our next function every autoPlayInterval milliseconds.
  • return () => clearInterval(timer): This cleanup function runs when the component unmounts OR before the effect re-runs (because a dependency changed). Without this, the interval keeps running after the component is gone, causing "Can't perform state update on unmounted component" warnings — and eventually memory leaks in long-running apps.
⚠️ Common Mistake: Forgetting the cleanup function entirely. The interval fires forever, even after the user navigates away. In a large app with many carousels being mounted and unmounted, this causes serious performance degradation. Always clean up your intervals.

Hover Pause

<div
  className="slider"
  onMouseEnter={() => setIsPaused(true)}
  onMouseLeave={() => setIsPaused(false)}
>

When isPaused becomes true, the useEffect cleanup runs and clears the current interval. When isPaused becomes false, the effect re-runs and starts a fresh interval. This is reactive state management working exactly as designed.

Full Component

export default function ImageSlider({ images = [], autoPlayInterval = 3000 }) {
  const [currentIndex, setCurrentIndex] = useState(0);
  const [isPaused, setIsPaused] = useState(false);

  const prev = () => setCurrentIndex(i => (i - 1 + images.length) % images.length);
  const next = () => setCurrentIndex(i => (i + 1) % images.length);

  useEffect(() => {
    if (isPaused || images.length <= 1) return;
    const timer = setInterval(next, autoPlayInterval);
    return () => clearInterval(timer);
  }, [isPaused, images.length, autoPlayInterval]);

  if (images.length === 0) {
    return <div className="slider-empty">No images provided.</div>;
  }

  return (
    <div
      className="slider"
      onMouseEnter={() => setIsPaused(true)}
      onMouseLeave={() => setIsPaused(false)}
    >
      <img
        src={images[currentIndex].src}
        alt={images[currentIndex].alt}
        className="slider-image"
      />

      {images.length > 1 && (
        <>
          <button className="slider-btn slider-btn--prev" onClick={prev} aria-label="Previous image">
            ‹
          </button>
          <button className="slider-btn slider-btn--next" onClick={next} aria-label="Next image">
            ›
          </button>

          <div className="slider-dots" role="tablist">
            {images.map((_, i) => (
              <button
                key={i}
                role="tab"
                aria-label={`Go to slide ${i + 1}`}
                aria-selected={i === currentIndex}
                className={i === currentIndex ? 'dot dot--active' : 'dot'}
                onClick={() => setCurrentIndex(i)}
              />
            ))}
          </div>
        </>
      )}
    </div>
  );
}

Core Logic — The Wrapping Formula Explained

Let's trace through the math with 5 images (indices 0–4):

  • Next from index 4: (4 + 1) % 5 = 0 ✓ wraps to start
  • Prev from index 0: (0 - 1 + 5) % 5 = 4 ✓ wraps to end
  • Prev from index 0 without fix: (0 - 1) % 5 = -1 ✗ broken

This is the kind of mathematical reasoning interviewers love to see explained out loud while you code.

Edge Cases

  • Empty array: Return a fallback UI early. Don't let the component crash trying to access images[0] when images is empty.
  • Single image: The images.length > 1 conditional hides both the nav buttons and the dots. The auto-play guard also returns early. Clean and correct.
  • Memory leak: Returning clearInterval from useEffect handles this completely.
  • Rapid clicking: Because we use the functional form of setState, there are no stale closure issues even if the user clicks very fast.

Styling Approach

The key CSS decisions for a slider:

.slider {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9; /* maintains shape regardless of container width */
  overflow: hidden;
  border-radius: 12px;
}

.slider-image {
  width: 100%;
  height: 100%;
  object-fit: cover; /* fills the frame without stretching */
  display: block; /* removes inline-block gap below image */
}

.slider-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  padding: 8px 14px;
  cursor: pointer;
  font-size: 24px;
  border-radius: 4px;
}
.slider-btn--prev { left: 12px; }
.slider-btn--next { right: 12px; }

.slider-dots { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; }
.dot { width: 8px; height: 8px; border-radius: 50%; border: none; background: rgba(255,255,255,0.5); cursor: pointer; padding: 0; }
.dot--active { background: white; }
💡 Interview Tip: Using aspect-ratio: 16/9 instead of a hardcoded height is a great modern CSS choice. It means the slider scales proportionally with the container width — responsive by default, no media queries needed. Mentioning this shows you're up to date with modern CSS.
⚠️ Common Mistake: Forgetting display: block on the image. By default, images are inline elements, which adds a small whitespace gap below them inside a container. display: block removes this. It's a tiny detail but one that interviewers with a sharp eye for CSS will notice.

Common Pitfalls Summary

  • The negative modulo bug: (i - 1) % n returns -1 in JavaScript when i is 0. Always add n before modulo for backwards navigation.
  • Missing cleanup function: setInterval without clearInterval is a memory leak that builds up over time in large applications.
  • Redundant currentImage state: Derive it from images[currentIndex], don't store it separately.
  • Using currentIndex directly in the interval callback: Inside setInterval, currentIndex is captured in a stale closure. Always use the functional update form: setCurrentIndex(i => ...).
  • No alt text on images: Every image needs meaningful alt text. This is a basic accessibility requirement and an immediate red flag for interviewers.

How to Communicate During the Interview

Talk through your reasoning out loud. Specifically mention:

  • "I'm only storing currentIndex in state — the current image is derived directly as images[currentIndex]."
  • "For the wrapping logic, I'm using modulo arithmetic. For backwards navigation, I add images.length before taking modulo because JavaScript's modulo operator returns negative numbers for negative inputs — unlike Python."
  • "I'm using the functional form of setCurrentIndex inside the setInterval callback to avoid stale closure issues."
  • "The cleanup function in my useEffect clears the interval when the component unmounts or when isPaused changes — this prevents memory leaks."
  • "I'm using aspect-ratio: 16/9 so the slider is responsive without needing a fixed height or media queries."

These are the kinds of statements that make an interviewer's eyes light up. You're not just showing that your code works — you're showing that you understand why it works and where it could go wrong.

Interview Criteria

React

Uses a single currentIndex state. useEffect with setInterval for auto-play includes a cleanup function. onMouseEnter/onMouseLeave on the container manages the isPaused flag. Images are accessed via images[currentIndex] — pure derivation, no redundant state.

HTML/CSS

Images have descriptive alt text for screen readers. The slider container has a defined aspect ratio. object-fit:cover ensures images fill the frame without distortion. Dot buttons use aria-label='Go to slide N' for accessibility.

Component Architecture

The slider is self-contained — no external state management needed. Navigation logic (prev/next) is clean and reusable. Images, autoPlayInterval, and onSlideChange are configurable via props.

State Management

Only currentIndex and isPaused live in state. The current image, total count, and whether nav buttons should show are all derived. This is a good example of keeping state minimal.

Edge Cases

Handles empty images array with a graceful fallback message. Hides navigation and dots for a single image. Interval cleanup on unmount prevents memory leaks. Negative modulo bug in Previous navigation is handled correctly.

Time Checkpoints

0:00

0:00: Interview starts. Read the prompt. Ask clarifying questions: What is the images prop shape? Is auto-play required? Should it pause on hover?

3:00

3:00: Plan state: just currentIndex. Sketch out the component structure mentally before touching code.

7:00

7:00: Render the current image, Previous/Next buttons, and wire up click handlers with wrapping logic.

13:00

13:00: Add dot indicators. Clicking a dot sets currentIndex directly.

18:00

18:00: Implement auto-play with useEffect and setInterval. Add pause on hover.

24:00

24:00: Handle edge cases: empty array, single image. Polish styling.

28:00

28:00: Walk the interviewer through your solution, explaining key decisions.

Streak

0 days

Last active: Sign in to track

Progress

0%

0/0 solved

Submitted

0

Solutions pushed to review history.