Skip to the solution
MediumMachine Coding13 min read

Build a Lazy-loaded Image

1.1k views

The problem

Question presented to candidate: "Our product feed shows dozens of large photos and the page is slow on phones. Build an image component that only downloads its photo when it is about to scroll into view. It should show a placeholder first, fade the photo in when it arrives, and show a fallback with a retry button if the file fails."

Requirements:

  • A reusable image component taking src, alt, width, height and a placeholder colour or gradient.
  • The real src is not set until the image is within about 200px of the viewport.
  • The box reserves its final size before loading, so nothing jumps when the photo arrives.
  • Fade in on load; on error show a message and a Retry button.
  • Demo: a feed of 8 photos with a "Requested X of 8" counter; one photo is deliberately broken.

What a strong answer should cover:

  • IntersectionObserver with a rootMargin to start loading slightly early, one observer per image or one shared, and disconnecting it once the image is requested and on unmount.
  • An explicit status model (idle, loading, loaded, error) instead of several loose booleans.
  • Reserving space with width / height or aspect-ratio to avoid layout shift (CLS), plus a low-quality placeholder.
  • Handling load and error, and a retry that really requests the file again.
  • A fallback when IntersectionObserver does not exist, and knowing that native loading="lazy" covers the simple case.
  • Accessibility: a real alt, aria-busy while loading, and an announced error.

Clarifying questions expected:

  • "Is native loading="lazy" acceptable, or do you need custom thresholds, placeholders and status callbacks?"
  • "How early should loading start, and is the feed scrolled by the window or by an inner container?"
  • "Do we know each image's dimensions up front, so the box can reserve its size?"

Code / implementation expected: Yes. A working component in your chosen framework with the observer, placeholder, fade-in, error state and cleanup.

  • React
  • IntersectionObserver
  • Performance

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read · 10 sections

React solution

Every file of the working solution. Open it in the playground to run and change it.

ReactComplete solution
import { useState } from "react";
import LazyImage from "./src/components/LazyImage";
import { photos } from "./src/data/photos";

function Feed({ onStatus }) {
  return (
    <div className="feed" data-testid="feed">
      {photos.map((p) => (
        <figure key={p.id}>
          <LazyImage
            src={p.src}
            alt={p.alt}
            width={640}
            height={400}
            placeholder={p.placeholder}
            onStatus={(s) => onStatus(p.id, s)}
          />
          <figcaption>
            {p.id}. {p.alt}
          </figcaption>
        </figure>
      ))}
    </div>
  );
}

export default function App() {
  const [run, setRun] = useState(0); // bump to remount the feed
  const [statuses, setStatuses] = useState({});

  const report = (id, s) =>
    setStatuses((prev) => (prev[id] === s ? prev : { ...prev, [id]: s }));
  const values = Object.values(statuses);
  const requested = values.length;
  const loaded = values.filter((s) => s === "loaded").length;
  const failed = values.filter((s) => s === "error").length;

  function reset() {
    setStatuses({});
    setRun((r) => r + 1);
  }

  return (
    <main className="app">
      <header className="bar">
        <h1>Lazy-loaded images</h1>
        <p data-testid="stats" aria-live="polite">
          Requested {requested} of {photos.length} · loaded {loaded} · failed{" "}
          {failed}
        </p>
        <button type="button" onClick={reset}>
          Reset demo
        </button>
      </header>
      <p className="hint">
        Scroll the feed: each photo is requested only when it comes within 200px
        of the view.
      </p>
      <Feed key={run} onStatus={report} />
    </main>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 28 of 107 in Machine coding

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

Build an Image Carousel

Continue