Caching strategies
Skill almasumdev/awesome-mobile-performance-agent-skills/.github/skills/patterns/caching-strategies
Agent skills for profiling and optimizing mobile app performance (startup, memory, frame-rate, network).
npx -y skills add almasumdev/awesome-mobile-performance-agent-skills --skill caching-strategiesAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Apply read-through, write-through, TTL, LRU, and encrypted caches correctly across memory, disk, and secure storage on mobile.
SKILL.md
7.3 KB, as published. Nobody here has run it
Caching Strategies
Instructions
Every cache is a tradeoff between freshness, memory, disk, and security. Pick the strategy to match the data. Mis-sized or mis-policied caches cause jank (evicting too aggressively), memory kills (unbounded growth), or stale UI (never revalidating).
1. Strategy Catalog
| Strategy | When to use |
|---|---|
| Read-through | Default for feed/list screens. Cache sits in front of origin. |
| Write-through | Writes go to cache + origin synchronously. Safe but slow. |
| Write-behind | Write cache immediately; persist to origin async. Fast but risky. |
| Cache-aside | App checks cache; on miss, fetches and populates. Most flexible. |
| Stale-while-revalidate | Serve cached while refreshing in background. |
| TTL (time-to-live) | Expire entries after N seconds. Easy to reason about. |
| LRU (least recently used) | Bounded by size; evicts coldest. Best for memory caches. |
| LFU (least frequently used) | Retains hot items. Better for mixed workloads. |
| Keyset / content-addressable | Immutable blobs keyed by hash (e.g., images). |
2. Sizing
| Layer | Size budget (typical) |
|---|---|
| JSON/memory cache | 4–16 MB, LRU by entry size |
| Image memory cache | 20–25% of process heap |
| HTTP disk cache (OkHttp/URLCache) | 20–50 MB for API JSON |
| Image disk cache | 128–256 MB |
| SQLite/Drift/Realm | Product-driven; cap at 200 MB and evict LRU rows |
3. Read-Through + Stale-While-Revalidate
Most data is "it's fine if it's 60 seconds old." Render cache, refresh in background, diff in.
Kotlin:
class FeedRepository(private val api: Api, private val cache: Cache<String, Feed>) {
fun feed(userId: String): Flow<Feed> = flow {
cache.get(userId)?.let { emit(it) } // instant render
try {
val fresh = api.feed(userId)
cache.put(userId, fresh)
emit(fresh)
} catch (e: IOException) { /* keep cached version */ }
}
}
Swift:
actor FeedRepository {
private let api: Api
private var cache: [String: Feed] = [:]
func feed(for userId: String) -> AsyncStream<Feed> {
AsyncStream { cont in
Task {
if let c = cache[userId] { cont.yield(c) }
if let fresh = try? await api.feed(userId) {
cache[userId] = fresh
cont.yield(fresh)
}
cont.finish()
}
}
}
}
4. TTL and LRU
Pure TTL is easy but can cause all entries to expire simultaneously. Combine with an LRU that tracks size.
Kotlin using Caffeine:
val cache: Cache<String, Feed> = Caffeine.newBuilder()
.maximumWeight(8L * 1024 * 1024) // 8 MB
.weigher<String, Feed> { _, v -> v.byteSize }
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats()
.build()
Swift NSCache is LRU with size limits:
let cache = NSCache<NSString, Feed>()
cache.totalCostLimit = 8 * 1024 * 1024
cache.setObject(feed, forKey: userId as NSString, cost: feed.byteSize)
5. Persistence Layer
For true offline, persist in a local DB or a typed disk cache:
- Drift (Flutter) / Isar / Realm — typed, queryable, indexed.
- Room (Android) — first-party, coroutines/Flow support.
- Core Data / SwiftData (iOS) or GRDB / Realm.
- WatermelonDB / Realm / MMKV for React Native.
Rule: the DB is the source of truth. Network writes into the DB; UI reads from the DB reactively. This eliminates two-source-of-truth bugs.
6. Encrypted Caches
For anything that touches PII, health, financial, or auth:
| Platform | Storage |
|---|---|
| iOS | Keychain for secrets; file protection class completeUntilFirstUserAuthentication for PII. |
| Android | EncryptedSharedPreferences / EncryptedFile + Keystore |
| Flutter | flutter_secure_storage (Keychain/Keystore under the hood) |
| RN | react-native-keychain, expo-secure-store |
Android Jetpack Security example:
val masterKey = MasterKey.Builder(ctx)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
ctx, "secure_prefs", masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
iOS Keychain (simple wrapper):
func set(_ value: String, key: String) throws {
let q: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: key,
kSecValueData: Data(value.utf8),
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
]
SecItemDelete(q as CFDictionary)
let status = SecItemAdd(q as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError(status) }
}
Rules:
- Never cache bearer tokens in
UserDefaults/SharedPreferences/AsyncStorage. - Tie sensitive cache lifetime to auth session.
- On logout, zero the cache (
cache.invalidateAll(), delete encrypted files).
7. Invalidation
Surgical invalidation beats blanket invalidation.
- Tag cache entries by domain (
user:123,feed:home). On mutation, invalidate only matching tags. - react-query:
queryClient.invalidateQueries({ queryKey: ['feed'] }). - Paging 3:
pagingSource.invalidate().
8. Metrics
Every cache should expose:
- Hit rate (hits / (hits + misses)).
- Eviction rate.
- Byte size.
- p95
get/putlatency.
Log these daily. A hit rate trending down is the early signal of a cache key bug.
9. Anti-patterns
- Unbounded caches — always cap by size or count.
- Caching everything — auth responses, one-time requests, huge binaries: usually not worth it.
- Cache objects that reference a Context/View — guaranteed leak.
- Shared cache across users on the same device — data leakage after account switch; partition by user id.
Checklist
- Cache strategy selected per data type; documented.
- Memory caches bounded by size (LRU) and TTL.
- Disk caches bounded; eviction policy tested.
- Sensitive data uses Keychain / Keystore / secure storage APIs.
- DB is the single source of truth where offline matters.
- Surgical invalidation on writes; blanket
clearAllonly on logout. - Hit rate, eviction rate, and size emitted as telemetry.
- Cache is partitioned per user / account.