Back to Machine Coding
Machine Coding
easy
mid

How would you build an image carousel from scratch?

State = current index. Prev/next wrap or clamp; dots/thumbnails jump. Add: autoplay with pause-on-hover, keyboard arrows, swipe on touch, lazy-loading images, and accessibility (aria-roledescription, live region, focus management). Use transform for the slide animation.

4 min read·~25 min to think through

A carousel is simple state — currentIndex — but interviewers grade autoplay hygiene, accessibility, and the interaction surface.

Core implementation

jsx
function Carousel({ images, autoPlay = true, interval = 4000 }) {
  const [index, setIndex] = useState(0);
  const [paused, setPaused] = useState(false);

  const go = (i) => setIndex((i + images.length) % images.length); // wrap
  const next = () => go(index + 1);
  const prev = () => go(index - 1);

  // autoplay — pause on hover/focus, clean up the timer
  useEffect(() => {
    if (!autoPlay || paused) return;
    const id = setInterval(next, interval);
    return () => clearInterval(id);
  }, [autoPlay, paused, index, interval]);

  return (
    <div
      role="region" aria-roledescription="carousel" aria-label="Gallery"
      onMouseEnter={() => setPaused(true)} onMouseLeave={() => setPaused(false)}
      onKeyDown={(e) => {
        if (e.key === "ArrowRight") next();
        if (e.key === "ArrowLeft") prev();
      }}
    >
      <div style={{ transform: `translateX(-${index * 100}%)`, transition: "transform .3s" }}>
        {images.map((src, i) => (
          <img key={src} src={src} loading={i === index ? "eager" : "lazy"}
               aria-hidden={i !== index} alt={`Slide ${i + 1}`} />
        ))}
      </div>
      <button onClick={prev} aria-label="Previous slide">‹</button>
      <button onClick={next} aria-label="Next slide">›</button>
      <div role="tablist">
        {images.map((_, i) => (
          <button key={i} aria-label={`Go to slide ${i + 1}`}
                  aria-selected={i === index} onClick={() => go(i)} />
        ))}
      </div>
    </div>
  );
}

What's being graded

  • Index management — wrap (% length) or clamp; dots jump directly.
  • Autoplay hygienesetInterval in an effect with cleanup; pause on hover and focus (and ideally when the tab is hidden / prefers-reduced-motion). An autoplay carousel you can't pause is an accessibility failure.
  • Slide animation — animate transform: translateX (composite-only, GPU, smooth), not left.
  • Keyboard support — arrow keys.
  • Touch / swipe — pointer events tracking drag distance on touch devices.
  • Lazy-loading — only eager-load the visible (and maybe adjacent) images; loading="lazy" the rest.
  • Accessibilityaria-roledescription="carousel", label the region, label the prev/next/dot buttons, aria-hidden non-visible slides, and an aria-live region announcing "slide X of N." Respect prefers-reduced-motion.

The framing

"State is just currentIndex; prev/next wrap with modulo, dots jump. The grading is in the interaction and a11y: autoplay must be a cleaned-up setInterval in an effect that pauses on hover and focus — an unpausable autoplay carousel fails accessibility. Animate transform: translateX for smooth GPU-composited sliding. Add arrow-key support and touch swipe via pointer events. Lazy-load all but the visible image. And the ARIA: aria-roledescription=carousel, labeled controls, aria-hidden on offscreen slides, an aria-live 'slide X of N', and respect prefers-reduced-motion."

Follow-up questions

  • Why must autoplay pause on hover/focus?
  • Why animate transform instead of left?
  • How do you make a carousel accessible?
  • How would you add touch/swipe support?

Common mistakes

  • Autoplay with no pause-on-hover/focus, or no timer cleanup.
  • Animating left/margin instead of transform.
  • Eager-loading every image upfront.
  • No keyboard support, no ARIA, ignoring prefers-reduced-motion.
  • Off-by-one in wrap-around index math.

Performance considerations

  • transform-based sliding is composite-only (GPU, 60fps). Lazy-loading offscreen images cuts initial bytes. Clean up the autoplay timer to avoid leaks and setState-after-unmount.

Edge cases

  • Single image (hide controls).
  • Wrapping from last to first.
  • Tab backgrounded during autoplay.
  • Very wide/tall images, varying aspect ratios.
  • Rapid prev/next clicks mid-animation.

Real-world examples

  • Product image galleries, hero banners, onboarding slides.
  • Embla/Swiper libraries implementing exactly these concerns.

Senior engineer discussion

Seniors handle autoplay hygiene (cleanup, pause on hover/focus, reduced-motion), use transform for animation, add keyboard + touch, lazy-load images, and implement the full carousel ARIA pattern.

Related questions