← Back to list

Day — 4: Caching Client-side, CDN, server-side, cache invalidation

Every time your app asks the server for data it already has, you’re wasting your user’s battery, their data plan, and their time — and they…

Ahmed Kenawy · 2026-06-11 18:43 · 0 claps · 4.0 min read
#system-design-concepts #design-systems #android #android-app-development #android-development
Open on Medium ↗
Wiki topics: PRD · Product Design 📱 · Mobile Development

Day — 4: Caching Client-side, CDN, server-side, cache invalidation

Every time your app asks the server for data it already has, you’re wasting your user’s battery, their data plan, and their time — and they feel it.

The Problem Every Android Dev Faces

You open an app. You saw this screen five minutes ago. The exact same data. But the app shows a spinner anyway, waits two seconds, then loads the same list you already saw.

That’s a caching failure. Not a network failure — a design failure.

Or the opposite: your app shows data from three days ago because it cached too aggressively and never refreshed. The user thinks the app is broken.

Both problems have the same root cause — no clear caching strategy. This article gives you one.

What Is Caching — Simply Explained

Caching means saving a copy of something close to you so you don’t have to go get it again.

Think of it like your kitchen. You don’t drive to the supermarket every time you need water. You have a bottle in the fridge. The fridge is your cache.

Your goal as an Android developer: answer as many requests as possible from Layer 1 or Layer 2. Only go to Layer 4 when you absolutely have to.

The 5 Caching Strategies — Simply Explained

Strategy 1 — Cache-Aside (the most common one)

Check the cache first. If the data is there — use it. If not — fetch from server, then save it in the cache for next time.

This is what Room + Repository pattern does in Android. It’s the right default for almost everything.

Strategy 2 — TTL (Time To Live)

The data expires after a set time. After 5 minutes, throw it away and fetch fresh data. Simple, but can show stale data right before expiry.

Strategy 3 — Write-Through

When the user changes something, update the cache AND the server at the same time. Always consistent, but every write is slightly slower.

Strategy 4 — Optimistic Update

Update the cache immediately when user does something (like, comment, edit). Then sync with server in the background. If the server fails — roll back.

This is what makes apps feel instant. The user taps Like — the heart fills immediately. They don’t wait for the server.

Strategy 5 — Invalidate on Write

When data changes, delete the old cached version completely. Next read fetches fresh data. Clean but causes one extra network request after every write.

Implementing It in Android

// The Cache-Aside pattern — the right default for Android
@Singleton
class ArticleRepository @Inject constructor(
    private val api: ArticleApi,
    private val dao: ArticleDao
) {
    // Always show cached data first — user sees content instantly
    // Then fetch fresh data — user gets updates without waiting
    fun getArticles(): Flow<List<Article>> = flow {

        // Step 1: Show what we have saved locally (works offline too)
        val saved = dao.getAll()
        if (saved.isNotEmpty()) emit(saved)

        // Step 2: Quietly fetch fresh data from server
        try {
            val fresh = api.getArticles()
            dao.deleteAll()      // remove old cached data
            dao.insertAll(fresh) // save new data
            emit(fresh)          // update the UI
        } catch (e: IOException) {
            // No internet — that's fine, user already sees saved data
            if (saved.isEmpty()) throw e
        }
    }.flowOn(Dispatchers.IO)

    // Optimistic update — feel instant, sync in background
    suspend fun likeArticle(id: String) {
        dao.setLiked(id, true)     // update UI immediately
        try {
            api.likeArticle(id)    // tell the server
        } catch (e: Exception) {
            dao.setLiked(id, false) // server failed — undo
        }
    }

    // Invalidate on write — fresh data after any change
    suspend fun deleteArticle(id: String) {
        api.deleteArticle(id)  // delete from server
        dao.deleteById(id)     // remove from local cache too
    }
}

Common Mistakes Android Developers Make

Mistake 1 — Fetching from network every time the screen opens

// ❌ Every screen open = network request = spinner = bad UX
override fun onResume() {
    viewModel.loadFromNetwork()
}

// ✅ Show cache instantly, refresh quietly in background
val articles = repo.getArticles() // emits cache first, then network

Mistake 2 — Caching data that changes every second

Don’t cache live scores, stock prices, or real-time chat messages. Cache things that stay the same for minutes or hours — articles, profiles, product lists.

Mistake 3 — Never invalidating the cache

// ❌ User deletes a post — but cache still shows it
api.deletePost(id)

// ✅ Always clean up the cache when data changes
api.deletePost(id)
dao.deleteById(id) // remove from cache too

Mistake 4 — Caching sensitive data on disk

Never cache auth tokens, passwords, or payment info in OkHttp disk cache or Room without encryption. Use EncryptedSharedPreferences or SQLCipher for anything sensitive.

Production-Ready Example — WorkManager Background Sync

// Sync cache in background even when app is closed
class SyncWorker(ctx: Context, params: WorkerParameters)
    : CoroutineWorker(ctx, params) {

    override suspend fun doWork(): Result {
        return try {
            val fresh = api.getArticles()
            dao.deleteAll()
            dao.insertAll(fresh)
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) Result.retry()
            else Result.failure()
        }
    }
}

// Schedule sync every 15 minutes — only when on WiFi
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "article_sync",
    ExistingPeriodicWorkPolicy.KEEP,
    PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
        .setConstraints(
            Constraints.Builder()
                .setRequiredNetworkType(NetworkType.UNMETERED) // WiFi only
                .build()
        ).build()
)

Key Takeaways

  • Caching means saving data close to you so you don’t fetch it again — like keeping water in your fridge instead of going to the supermarket every time.
  • Show cached data first, fetch fresh data in the background — this makes your app feel instant even on slow connections.
  • Cache-Aside with Room is the right default for Android — emit from Room first, then network, then Room again.
  • Optimistic updates (update UI instantly, sync in background) are what make apps feel fast — not faster internet.
  • Never cache sensitive data unencrypted on disk — auth tokens, passwords, and payment info need encryption.

AndroidDevelopment #SystemDesign #Kotlin #MobileDevelopment #SoftwareEngineering


메타데이터
post_id
d6987b4deed6
slug
day-4-caching-client-side-cdn-server-side-cache-invalidation-d6987b4deed6
url
https://medium.com/@a7medsa3dkenawy/day-4-caching-client-side-cdn-server-side-cache-invalidation-d6987b4deed6
canonical_url
https://medium.com/@a7medsa3dkenawy/day-4-caching-client-side-cdn-server-side-cache-invalidation-d6987b4deed6
author_url
https://medium.com/@a7medsa3dkenawy
status
ok
fetched_at
2026-06-12 07:40:50