#Image Carousel#React

Image Carousel

Create an interactive image carousel in React with smooth slide transitions, navigation arrows, dot indicators, autoplay, and touch/swipe support for mobile devices.

By Pratik RaiMedium
Image Carousel

Build an image carousel that displays one image at a time with smooth navigation, infinite looping, and optimal performance. The key challenges are implementing circular navigation logic and using CSS transforms to prevent layout thrashing.

The task

Goal: implement a carousel for a fixed-size image collection.

Technology: HTML, CSS and JavaScript, or React.

Requirements

  1. Display a collection of images one at a time, with only the current image visible.
  2. Provide next and previous controls that move the carousel by one image.
  3. Loop in both directions: advancing past the last image returns to the first, and going back from the first lands on the last.
  4. Animate the transition between images rather than swapping them instantly.
  5. Show an indicator of how many images there are and which one is current.
  6. Accept any number of images without hardcoding the count.

Stretch goals

  • Autoplay with a control to pause it.
  • Keyboard support, so the arrow keys move between images.
  • Lazy loading, so off-screen images are not all fetched up front.

Hints

  1. Keep exactly one piece of state: the index of the current image. Everything visible can be derived from it.
  2. Move the whole strip rather than the images. Lay the images out in a row inside a container with overflow: hidden, and shift the row with a single transform.
  3. Get the looping right with modular arithmetic instead of if statements. Advancing is (index + 1) % total; going back is (index - 1 + total) % total, where the + total is what stops a negative index.
  4. Animate transform, not left. Transform is composited and does not force the browser to recalculate layout on every frame.

Architecture Overview

The carousel uses a viewport + track pattern:

┌─────────────────────────────────────┐
│  [← Prev]  ┌──────────┐  [Next →]   │
│            │ Viewport │             │
│            │ (400px)  │             │
│            └──────────┘             |
│                 │                   |
│            ┌────▼─────────────────┐ │
│            │ Track (flex container) |
│            │ [Img1][Img2][Img3]...  |
│            └──────────────────────┘ |
└─────────────────────────────────────┘

Image Carousel Architecture

  • Viewport: Fixed-width container with overflow: hidden that shows only one image
  • Track: Wide flex container that holds all images side-by-side
  • Transform: Moves the track left/right to reveal different images

Component Structure

The implementation follows a controlled component pattern:

TSXcomponent.tsx
1// Parent component manages state 2const [currentIndex, setCurrentIndex] = useState(0); 3 4// Child component receives props 5<ImageCarouselComponent 6 images={imageUrls} 7 onPrev={handlePrevClick} 8 onNext={handleNextClick} 9 currentIndex={currentIndex} 10/>

This separation allows:

  • Reusability: The carousel component is pure and doesn't manage its own state
  • Flexibility: Parent controls the navigation logic
  • No unnecessary re-renders: Only currentIndex changes trigger updates

Circular Navigation Logic

The infinite loop is achieved using the modulo operator (%). This creates a circular index that wraps around at the boundaries.

Next Button Logic

TSXcomponent.tsx
1function handleNextClick() { 2 setCurrentIndex((prevIndex) => (prevIndex + 1) % total); 3}

How it works:

  • prevIndex + 1: Move to next index
  • % total: Wrap around when reaching the end

Examples with 5 images (indices 0-4):

  • Index 0 → (0 + 1) % 5 = 1
  • Index 1 → (1 + 1) % 5 = 2
  • Index 2 → (2 + 1) % 5 = 3
  • Index 3 → (3 + 1) % 5 = 4
  • Index 4 → (4 + 1) % 5 = 0 (wraps to first)

Previous Button Logic

TSXcomponent.tsx
1function handlePrevClick() { 2 setCurrentIndex((prevIndex) => (prevIndex - 1 + total) % total); 3}

Why (prevIndex - 1 + total)?

  • prevIndex - 1: Move to previous index
  • + total: Add total to handle negative values
  • % total: Wrap around

Examples with 5 images:

  • Index 0 → (0 - 1 + 5) % 5 = 4 (wraps to last)
  • Index 1 → (1 - 1 + 5) % 5 = 0
  • Index 2 → (2 - 1 + 5) % 5 = 1
  • Index 3 → (3 - 1 + 5) % 5 = 2
  • Index 4 → (4 - 1 + 5) % 5 = 3

Why not just (prevIndex - 1) % total? In JavaScript, -1 % 5 equals -1, not 4. Adding total first ensures we get a positive number before applying modulo:

  • (-1 + 5) % 5 = 4 % 5 = 4

Transform-Based Sliding

The carousel uses CSS transform: translateX() to slide images. This is crucial for performance.

The Math Behind the Transform

TSXcomponent.tsx
1transform: `translateX(-${(currentIndex * 100) / images.length}%)`

Breaking it down:

  1. Each image takes 100 / images.length% of the track width
  2. To show image at index i, we need to shift the track left by i image widths
  3. currentIndex * (100 / images.length) = percentage to shift

Example with 5 images:

  • Each image width: 100 / 5 = 20% of track
  • Index 0: translateX(-0%) → shows first image
  • Index 1: translateX(-20%) → shows second image
  • Index 2: translateX(-40%) → shows third image
  • Index 3: translateX(-60%) → shows fourth image
  • Index 4: translateX(-80%) → shows fifth image

General formula:

translateX(-(currentIndex × 100) / images.length %)

Track and Image Sizing

Track width:

TSXcomponent.tsx
1width: `${images.length * 100}%`
  • If we have 5 images, track is 500% of viewport width
  • Each image takes 100% of viewport (which is 20% of track)

Individual image width:

TSXcomponent.tsx
1width: `${100 / images.length}%`
  • For 5 images: 100 / 5 = 20% of track width
  • This ensures all images fit side-by-side in the track

Visual representation (5 images):

Viewport (400px):
┌────────────────────┐
│                    │
└────────────────────┘
         │
         ▼
Track (2000px = 500%):
┌────┬────┬────┬────┬────┐
│Img1│Img2│Img3│Img4│Img5│  Each: 400px (20% of 2000px)
└────┴────┴────┴────┴────┘
     │
     └─ translateX(-20%) shows Img2

Performance Optimizations

1. Using Transform Instead of Position

Why transform?

  • transform and opacity are GPU-accelerated properties
  • They don't trigger reflow (layout recalculation)
  • They only trigger repaint (visual update)
  • Much faster than changing left, top, or margin

What happens with position changes:

CSSstyles.css
1/* BAD: Triggers reflow */ 2.track { 3 left: -400px; /* Browser recalculates layout */ 4}

What happens with transform:

CSSstyles.css
1/* GOOD: Only triggers repaint */ 2.track { 3 transform: translateX(-400px); /* Browser only updates visual */ 4}

2. Keeping All Images in DOM

All images are rendered in the DOM simultaneously:

TSXcomponent.tsx
1{images.map((image, index) => ( 2 <img key={index} src={image} /> 3))}

Benefits:

  • No layout thrashing: Images don't get added/removed, preventing reflow
  • Smooth transitions: CSS transitions work seamlessly
  • Preloading: Images can start loading before they're visible

Trade-off:

  • Higher initial memory usage
  • All images load at once (can be optimized with lazy loading)

3. CSS Transitions

CSSstyles.css
1.track { 2 transition: transform 0.5s ease-in-out; 3}

This provides smooth animation when currentIndex changes. The browser automatically animates the transform property between values.

Component Implementation Details

Viewport Container

TSXcomponent.tsx
1<div className="viewport">

CSS:

CSSstyles.css
1.viewport { 2 width: 400px; /* Fixed width */ 3 overflow: hidden; /* Hides images outside viewport */ 4}

The viewport acts as a "window" that shows only one image at a time.

Track Container

TSXcomponent.tsx
1<div className="track" style={{ transform: `translateX(...)` }}>

CSS:

CSSstyles.css
1.track { 2 display: flex; /* Images side-by-side */ 3 transition: transform 0.5s ease-in-out; /* Smooth animation */ 4}

The track holds all images and slides horizontally based on currentIndex.

Image Rendering

TSXcomponent.tsx
1{images.map((image, index) => ( 2 <img 3 key={index} 4 src={image} 5 style={{ 6 width: `${100 / images.length}%`, 7 height: '300px', 8 objectFit: 'contain' 9 }} 10 /> 11))}

Each image:

  • Takes 100 / images.length% of track width
  • Has fixed height for consistency
  • Uses objectFit: contain to maintain aspect ratio

Advanced Optimizations

Lazy Loading Images

To prevent loading all images at once, use native lazy loading:

TSXcomponent.tsx
1<img 2 src={image} 3 loading={index === currentIndex || 4 index === currentIndex + 1 || 5 index === currentIndex - 1 ? "eager" : "lazy"} 6/>

This loads:

  • Current image immediately
  • Next and previous images (for smooth transitions)
  • Other images lazily

Preventing Layout Shift

Set explicit dimensions to prevent Cumulative Layout Shift (CLS):

TSXcomponent.tsx
1<img 2 src={image} 3 width={400} 4 height={300} 5 style={{ aspectRatio: '4/3' }} 6/>

Key Takeaways

  1. Circular Navigation: Use (index + 1) % total for next and (index - 1 + total) % total for previous
  2. Transform Formula: translateX(-(currentIndex × 100) / images.length %)
  3. Track Width: images.length × 100% to fit all images
  4. Image Width: 100 / images.length% so images fit side-by-side
  5. Performance: Use transform instead of position changes to avoid reflow
  6. Architecture: Controlled component pattern for reusability and no unnecessary re-renders

The carousel is efficient, smooth, and works with any number of images because it's based on mathematical relationships rather than hardcoded values.

What interviewers look for

Most of the marks are in the boundaries rather than the happy path.

  • Does it wrap in both directions? Clicking previous on the first image is where naive implementations produce an index of -1 and a blank screen. It is the first thing many interviewers click.
  • Does the track move, or the images? Re-rendering the list on every step works but shows you have not thought about how the browser paints. One transform on a container is the answer they are listening for.
  • Are the controls real buttons? A div with a click handler cannot be reached by keyboard and has no accessible name. This is a quick way to demonstrate that you build for more than a mouse.
  • Does it survive a different number of images? Hardcoding percentages for five images works for exactly five images. Deriving the widths from the array length shows the component is reusable.
  • If it autoplays, can it be stopped? Moving content with no pause control is an accessibility failure, and mentioning it unprompted lands well.

Finishing a simple version cleanly reads better than leaving an ambitious one half-built. Get one image moving in both directions, then reach for indicators, autoplay and lazy loading with whatever time is left.

Goal: Implement a image carousel component that can be used to display a list of images.

Frequently asked questions

Is the image carousel a common frontend interview question?
It is one of the most frequently set UI-building tasks in front-end interviews, and it appears in both machine-coding rounds and take-home briefs. It is popular because it is small enough to finish in under an hour but still forces decisions about state, layout, and edge cases — which is exactly what an interviewer wants to watch you make.
What do interviewers look for in a carousel implementation?
Mostly the things that are easy to skip. Does the index wrap correctly at both ends, or does it break on the first and last slide? Does the track slide with a CSS transform rather than re-rendering the list? Are the controls real buttons that work with a keyboard? Interviewers are rarely impressed by animation polish and almost always probe the boundaries.
How long should an image carousel take in an interview?
A working version with next and previous controls is usually expected in 30 to 45 minutes. Treat indicators, autoplay, and looping as extensions to reach for once the core is solid — a finished simple carousel reads far better than an unfinished ambitious one.
Should I use a carousel library in an interview?
No. The question exists to see how you build the behaviour, so reaching for a library answers a question nobody asked. Build it with a transform on a track of equal-width slides and plain state for the active index; mention the library you would use in production if you want to show you know the landscape.
How do you make an image carousel accessible?
Use real button elements for the controls so they are focusable and operable by keyboard, give each one a label that says what it does, and make sure images carry meaningful alt text. If the carousel advances on its own, it needs a way to pause, because moving content that cannot be stopped is a genuine accessibility failure rather than a nitpick.

Related Challenges

Continue learning with these related challenges

View All
React

Dynamic Tic Tac Toe

Build a dynamic Tic Tac Toe game in React with customizable grid sizes, win detection algorithms, player turn management, and game reset functionality. Great for interviews.

React · JavaScriptPratik Rai ·

React

Dice Roller

Create an animated dice roller component in React with realistic rolling animations, random number generation, multiple dice support, and roll history tracking.

React · JavaScriptPratik Rai ·

React

Whack A Mole

Build a fun Whack-a-Mole game in React with random mole spawning, click detection, score tracking, countdown timer, and increasing difficulty levels. Perfect for React practice.

React · JavaScriptPratik Rai ·