Android Development

    How to Design an Image Loading Library in Android - LRU CACHE

    A
    AzeemJan 27, 2026 · 5 min read
    122
    How to Design an Image Loading Library in Android - LRU CACHE

    Every Android app needs fast and efficient image loading—whether it’s for a social media feed, an e-commerce catalog, or a news app. We often use libraries like Glide, Picasso, or Coil, but have you ever thought about how they work under the hood?

    Imagine you’re asked in a system design interview: (Most Commonly Asked)

    💡 “Design an image loading library from scratch.”

    Sounds exciting, right? Let’s break it down! 👇

    The Problem – Why Do We Need a Custom Image Loader?

    Downloading and displaying images might seem simple, but it comes with challenges:

    Slow Loading – Downloading large images blocks the UI.

    Network Overhead – Fetching the same image multiple times wastes bandwidth.

    Memory Issues (OOM Errors) – Unoptimized images kill your app.

    Scrolling Lag – If images are not efficiently loaded, RecyclerView stutters.

    A well-designed image loading library should solve these problems efficiently.

    High-Level Architecture of an Image Loader

    A modern image loader consists of four major components:

    1. Request Manager – Handles API calls and queues requests.

    2. Cache Layer – Stores images in memory & disk to avoid re-downloading.

    3. Decoding & Transformation – Resizes, crops, or applies filters to images.

    4. UI Renderer – Efficiently loads images into an ImageView without blocking the UI.

    Before moving ahead, kindly join android whatsapp community

    https://chat.whatsapp.com/JGIQRz0RRyhIrEe8oNfHYR

    Get follow our page TheCodingShef

    https://www.linkedin.com/company/thecodingshef/

    Let’s build this step by step! 🏗️

    📡 Step 1: Image Downloading (Fetcher Layer)

    Before caching, we need to download images efficiently.

    ✅ Use OkHttp for fast & optimized network calls.

    ✅ Implement request deduplication (Avoid multiple calls for the same URL).

    ✅ Support timeouts & retries to handle slow networks.

    fun fetchImage(url: String): Bitmap? {
        val request = Request.Builder().url(url).build()
        val response = OkHttpClient().newCall(request).execute()
        return BitmapFactory.decodeStream(response.body?.byteStream())
    }
    code

    Optimization Tip: Support different formats (WebP, PNG, JPEG) to reduce file size.

    Step 2: Implementing a Dual Cache System (Memory + Disk)

    Caching is the key to speed!

    🔹 Memory Cache (RAM) – Fastest access, but limited space.

    🔹 Disk Cache (Storage) – Slower but persistent across app restarts.

    Memory Cache using LruCache (Least Recently Used Eviction)

    val memoryCache = LruCache<String, Bitmap>(cacheSize)
    
    fun getFromMemoryCache(url: String): Bitmap? = memoryCache.get(url)
    fun putInMemoryCache(url: String, bitmap: Bitmap) = memoryCache.put(url, bitmap)
    code

    Disk Cache using File Storage

    fun getFromDiskCache(url: String): Bitmap? {
        val file = File(cacheDir, url.hashCode().toString())
        return if (file.exists()) BitmapFactory.decodeFile(file.path) else null
    }
    
    fun putInDiskCache(url: String, bitmap: Bitmap) {
        val file = File(cacheDir, url.hashCode().toString())
        FileOutputStream(file).use { bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) }
    }
    code

    Optimization Tip: Use LRU eviction to remove the least-used items when memory is full.

    Step 3: Image Decoding & Transformation

    Images should be optimized for UI.

    ✅ Resizing – Load only the necessary resolution (avoid full-size images).

    ✅ Caching Transformed Images – Don’t reprocess the same image repeatedly.

    ✅ Lazy Loading – Load only when required (use RecyclerView ViewHolders).

    fun decodeAndResize(image: Bitmap, width: Int, height: Int): Bitmap {
        return Bitmap.createScaledBitmap(image, width, height, true)
    }
    code

    Optimization Tip: Store multiple resolutions in cache for different screen sizes.

    Step 4: Rendering Efficiently into ImageView

    Once we have an image (from cache or network), we bind it to an ImageView without blocking the UI thread.

    ✅ Use Coroutines or Executors to load images asynchronously.

    ✅ Prevent UI Flickering by showing placeholders while loading.

    ✅ Cancel Unused Requests when the user scrolls away (important for RecyclerView).

    fun loadImage(url: String, imageView: ImageView) {
        val cachedBitmap = getFromMemoryCache(url) ?: getFromDiskCache(url)
    
        if (cachedBitmap != null) {
            imageView.setImageBitmap(cachedBitmap)
        } else {
            CoroutineScope(Dispatchers.IO).launch {
                val bitmap = fetchImage(url)
                bitmap?.let {
                    putInMemoryCache(url, it)
                    putInDiskCache(url, it)
                    withContext(Dispatchers.Main) { imageView.setImageBitmap(it) }
                }
            }
        }
    }
    code

    Optimization Tip: Avoid blocking the UI thread by running operations in the background.

    Bonus: Advanced Optimizations

    🔹 Preloading & Prefetching – Load images before they are needed.

    🔹 Request Deduplication – If the same image is requested multiple times, return the same Future instead of re-downloading.

    🔹 Adaptive Caching – Store high-res images for large screens, low-res for small screens.

    🔹 Glide-Like API Support – Provide a simple API:

    ImageLoader.load(url).placeholder(R.drawable.loading).into(imageView)
    code

    🎯 Final Workflow – How It All Comes Together

    Step 1: Check if the image exists in memory cache (LruCache).

    Step 2: If not found, check disk cache (DiskLruCache).

    Step 3: If not cached, fetch from the network (OkHttp).

    Step 4: Resize & cache the image in memory & disk for future use.

    Step 5: Render the image efficiently in ImageView.

    🚀 Final Thoughts – Why Build Your Own Library?

    While Glide, Picasso, and Coil do an amazing job, building an image loading library helps you:

    ✅ Understand how caching & memory management works.

    ✅ Optimize performance for specific use cases.

    ✅ Handle custom transformations & rendering.

    Wait for next Article where i will explain whole code. How you like this article. Let me know in the comments! 👇

    android
    lru cache
    Image loading library
    Android system design
    Was this helpful?

    Comments (0)

    No comments yet. Be the first to share your thoughts!

    Leave a Comment

    Get the latest updates

    Join our newsletter and never miss an article.

    By subscribing, you agree to our Privacy Policy and consent to receive updates.

    Ready to Start Learning?

    Join thousands of students building their dream careers

    Explore Courses