← All courses

Image Loading & Media Pipeline

🗓 May 31, 2026 ⏱ 3 min read

Why images deserve their own design

Images are usually the largest, most memory-hungry data in an app, and a feed can show hundreds of them. Mishandling images is the #1 cause of out-of-memory crashes and janky scrolling. A proper media pipeline — loading, decoding, resizing, caching, displaying — is essential mobile system design.

What a good image pipeline does

URL Cache? Download Decode+resize Show
  • Cache check — memory then disk, before any download.
  • Download — off the main thread, cancellable when the row scrolls away.
  • Decode & downsample — decode to the display size, not full resolution. A 4000×3000 photo shown in a 200px thumbnail must be downsampled, or it eats ~48MB of RAM.
  • Display — with a placeholder while loading and a fade-in.

The golden rule: never load full-res into a small view

This single mistake causes most image OOM crashes. Always request or decode images at roughly the size they’ll be shown. Backends should offer multiple sizes (thumbnail, medium, full) so the app downloads the smallest sufficient one — saving memory, data and time.

Caching images

  • Memory cache — an LRU cache of decoded bitmaps for instant re-display while scrolling.
  • Disk cache — downloaded files survive restarts; avoids re-downloading.
  • Both must be size-bounded (LRU eviction) — images fill caches fast.

Handling the scrolling-list trap

In a recycled list, a row is reused for different images. If a slow download finishes after the row was reused, you can show the wrong image. The fix: tag each request to its row and cancel/ignore stale results. Good libraries handle this automatically.

Use a battle-tested library

Don’t build this yourself. Use Coil/Glide (Android), SDWebImage/Kingfisher or SwiftUI AsyncImage (iOS), FastImage (React Native), or CachedNetworkImage (Flutter). They handle caching, downsampling, cancellation and the recycling trap correctly.

// Coil — one line; handles cache, downsample, cancellation
imageView.load(url) {
    placeholder(R.drawable.loading)
    crossfade(true)
}

Beyond images

Video and audio add streaming, buffering and codecs (ExoPlayer/AVPlayer). The same principles apply: stream rather than fully download, cache, free resources when off-screen, and do heavy work off the main thread.

Common mistakes

  • Loading full-resolution images into thumbnails (OOM crashes).
  • Decoding images on the main thread (jank).
  • Wrong image in recycled rows from uncancelled requests.
  • Unbounded image caches filling memory/storage.
Summary: Treat images as a pipeline: check cache → download off-thread → downsample to display size → show with a placeholder. Never load full-res into small views, bound your memory/disk caches, cancel stale requests in recycled lists, and use a proven library rather than rolling your own.