The image carousel machine-coding round is one of the more deceptive frontend interview tests. Surface-simple — show images, navigate between them — the round actually tests touch handling, animation, autoplay management, accessibility for a notoriously a11y-hostile component, and image performance. Senior interviewers grade many dimensions; junior candidates often nail the visual but miss the rest.
This piece walks through the implementation patterns and what interviewers grade.
The typical prompt
- “Build an image carousel that cycles through 5 images. The user can advance with arrow buttons.”
- “Build a swipe-able image gallery for mobile.”
- “Implement an autoplay carousel that pauses on hover and respects prefers-reduced-motion.”
What interviewers grade
- Working navigation. Previous and next buttons advance the carousel.
- Touch / swipe. On touch devices, swipe gestures advance.
- Animation. Smooth slide transitions, not abrupt swaps.
- Indicators. Dots or pagination showing current position.
- Autoplay. Auto-advance after N seconds, pause on hover and focus.
- Accessibility. Keyboard support, ARIA live region for slide changes, reduced-motion support.
- Image loading. Lazy-load off-screen images, handle loading states.
- Edge cases. Wrap-around (last → first), single-image case, empty state.
Implementation
Step 1: state and structure
function Carousel({ images }) {
const [current, setCurrent] = useState(0);
function next() {
setCurrent(c => (c + 1) % images.length);
}
function prev() {
setCurrent(c => (c - 1 + images.length) % images.length);
}
return (
<div className="carousel" role="region" aria-roledescription="carousel">
<div className="slides" style={{ transform: `translateX(-${current * 100}%)` }}>
{images.map((src, i) => (
<img key={src} src={src} alt={`Slide ${i + 1}`} loading={Math.abs(i - current) <= 1 ? 'eager' : 'lazy'} />
))}
</div>
<button onClick={prev} aria-label="Previous slide">←</button>
<button onClick={next} aria-label="Next slide">→</button>
<div className="indicators">
{images.map((_, i) => (
<button
key={i}
onClick={() => setCurrent(i)}
aria-label={`Go to slide ${i + 1}`}
aria-current={i === current}
/>
))}
</div>
</div>
);
}
The transform-based positioning is performance-friendly (composite-only). The lazy-loading hints reduce initial network load.
Step 2: keyboard support
useEffect(() => {
function handleKeyDown(e) {
if (e.key === 'ArrowLeft') prev();
else if (e.key === 'ArrowRight') next();
}
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, []);
For senior+ rounds, scope this to when the carousel is focused, not globally.
Step 3: touch / swipe support
const touchStartRef = useRef<number>(0);
function onTouchStart(e) {
touchStartRef.current = e.touches[0].clientX;
}
function onTouchEnd(e) {
const delta = e.changedTouches[0].clientX - touchStartRef.current;
if (Math.abs(delta) > 50) {
if (delta > 0) prev();
else next();
}
}
// On the carousel:
<div className="carousel" onTouchStart={onTouchStart} onTouchEnd={onTouchEnd}>
The 50px threshold prevents accidental swipes from minor finger movement.
Step 4: autoplay with pause
const [isPaused, setIsPaused] = useState(false);
useEffect(() => {
if (isPaused) return;
const id = setInterval(next, 5000);
return () => clearInterval(id);
}, [isPaused]);
// Pause handlers
<div
className="carousel"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
onFocus={() => setIsPaused(true)}
onBlur={() => setIsPaused(false)}
>
Step 5: reduced motion support
const prefersReducedMotion = useMemo(() => {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}, []);
// In useEffect, disable autoplay if reduced motion is preferred:
useEffect(() => {
if (isPaused || prefersReducedMotion) return;
const id = setInterval(next, 5000);
return () => clearInterval(id);
}, [isPaused, prefersReducedMotion]);
Step 6: ARIA live region for slide changes
<div className="visually-hidden" aria-live="polite">
Slide {current + 1} of {images.length}
</div>
Screen readers announce the current slide as it changes. The visually-hidden class hides it from sight but keeps it accessible.
Common pitfalls
- No keyboard support. Arrow keys should advance the carousel.
- No autoplay pause. Aggressive autoplay without pause is hostile to reading.
- No reduced-motion check. Users with vestibular disorders should not have animations forced on them.
- No aria-roledescription. Screen readers should know it’s a carousel.
- All images load eagerly. A 20-image carousel forcing all 20 to load at once hurts LCP.
- Buttons without aria-label. “←” alone tells screen readers nothing.
- No wrap-around or wrap-around-only. Some prompts want clamping (no wrap); read the requirements.
- No animation. Abrupt swaps feel cheap.
Stretch goals
- Lazy-load images further than just the next/prev (e.g., next 2).
- Pre-cache the next image in JS to avoid first-show delay.
- Multi-item display (show 3 slides at once on desktop).
- Vertical carousel variant.
- Image zoom on click.
- Fullscreen / lightbox mode.
Common a11y mistakes carousel-specific
Carousels have a long history of accessibility failures. The W3C ARIA Authoring Practices Guide has a specific carousel pattern document. Senior candidates aware of:
- Carousels are notoriously hostile to screen readers if implemented naively.
- “role=region” with “aria-roledescription=carousel” is the right framing.
- Each slide should have an accessible label.
- Tab order should make sense.
- Auto-advance should pause when any element inside has keyboard focus.
Time budget for a 45-minute round
- 0-5 min: clarify requirements (autoplay? touch? wrap-around?).
- 5-15 min: basic structure with prev/next.
- 15-25 min: animation, indicators, keyboard support.
- 25-35 min: touch/swipe, autoplay with pause.
- 35-40 min: accessibility (ARIA, reduced motion).
- 40-45 min: edge cases, stretch goals.
Frequently Asked Questions
Should I use a library?
For the round, no. In production, libraries like Embla Carousel and Swiper are excellent choices.
How important is touch support?
For senior+ rounds, expected. The carousel needs to work on mobile.
Is reduced-motion always required?
For senior+ a11y-conscious companies yes. The pattern is small and worth knowing.
How does this differ from infinite scroll?
Carousel: discrete navigation through a fixed set. Infinite scroll: continuous loading of items.
Are carousels still common in 2026?
Less than a decade ago. Many design teams have moved away from auto-rotating carousels because they distract users. The interview round still tests them as a canonical complex-component exercise.