In short: A cache makes two decisions. One is when a saved value is too old (its TTL). The other is what to remove when the cache is full (eviction). Each choice is a trade between fresh data, storage and network use.
The question
An app shows a list of posts. Each post has a photo and a small avatar. The user scrolls down, goes back up, and scrolls down again. Without a cache, the app downloads the same files each time. It is slow, it costs mobile data, and it uses battery.
So the app keeps a copy. A cache is a place that holds copies of values so the app can reuse them. But a cache raises two new problems:
- The copy can be old. A price changes on the server, but the phone still shows the old price.
- The cache can be full. A phone has limited memory and storage. Something has to go.
These two problems are different. An expiry time does not free space. A removal rule does not make a value fresh. [Opinion]
The lookup path
A good design looks in the fastest place first: memory, then disk, then the network. Each step costs more than the one before. [Opinion] A hit means the value was in the cache. A miss means it was not, so the app goes one step further.
The diagram shows two rules in colour. The eviction rule decides what to remove when a cache is full. The expiry rule decides if a saved value is too old. The next sections explain each one.
The two coloured boxes are the rules in this guide. The eviction rule works when the app writes into a full cache. The expiry rule works when the app reads a value. [Opinion]
Expiry: is it too old?
A TTL means "time to live". The app saves the time it fetched a value. On each read it compares the age with the TTL. If the value is older, the app fetches it again. A value that is older than the real one is called stale.
The right TTL depends on how fast the data changes and how bad it is to show an old value. [Opinion]
- A stock price changes every second. A wrong value is harmful. Use a very short TTL, or do not cache it. Prefer a live connection.
- A feed changes often. An old feed is fine for a moment. Show the saved feed at once, then refresh it in the background.
- A profile photo changes rarely. A long TTL is fine. A new photo can use a new URL, so the old file is never reused.
- A booking price must be right before payment. Show the cached price, but ask the server again before the user pays.
Another choice is to ask the server if the value changed. The server sends a version tag (an ETag) with the value. Later the app sends the tag back and asks if the value changed. If not, the server sends a short reply and the app keeps its copy. This costs a small request and not a full download. [Not verified] I did not check the HTTP caching rules for this guide. Read the official specification before you quote a header.
Eviction: what goes first?
When the cache is full, an eviction rule picks what to remove. Each rule makes a different bet about the future.
| Rule | Removes first | Good when | Bad when |
|---|---|---|---|
| Least recently used (LRU) | The value used longest ago | Users go back to recent things, as in a scrolling list | One burst of one-time values pushes out the useful ones |
| Oldest first (FIFO) | The value that was saved first | You want a simple rule and use has no pattern | A popular value saved early is removed, such as an avatar on every row |
| Size-based | The biggest value | Many small values matter more than one big one | The big value is the one the user needs. It goes first next time too |
| Priority-based | The value with the lowest priority | Some data must stay, such as an offline timetable | Priorities are set badly, so nothing important is ever removed and the cache fills with "important" items |
The LRU row is checked. Android describes LruCache like this: each time a value is accessed, it moves to the head of a queue. When a value is added to a full cache, the value at the end of the queue is evicted (Android: LruCache). [Verified: Android reference, 2026-10-15] The other rows are my reading. [Opinion]
A cache can mix rules. For example, it can use LRU plus a size limit plus a TTL. [Opinion]
To feel the difference, open the cache explorer. Run the avatar case with FIFO and then with LRU. Then run the big file case with each rule.
Count or bytes?
A cache needs a limit. You can count items or count bytes. Counting items is wrong when items have very different sizes. Ten thumbnails and ten full photos do not use the same memory. [Opinion]
- Android: by default,
LruCachemeasures its size in entries. You overridesizeOfto measure in other units. The reference shows a cache limited to 4 MiB of bitmaps (Android: LruCache). [Verified: Android reference, 2026-10-15] - iOS:
NSCachehascountLimit, the maximum number of objects, andtotalCostLimit, a limit on total cost (Apple: NSCache). [Verified: Apple documentation, 2026-10-15]
Apple says totalCostLimit is not a strict limit. An object could be evicted at once, at a later time, or possibly never. The order of eviction is not guaranteed (Apple: totalCostLimit). [Verified: Apple documentation, 2026-10-15] So do not build a design that needs a specific item to be removed.
The system also evicts
You are not the only one who removes things. The phone does it too.
- Android calls
onTrimMemorywhen the operating system decides it is a good time to trim memory. At the background level, the docs call it a good opportunity to clean up resources that can be rebuilt quickly if the user comes back (Android: ComponentCallbacks2). [Verified: Android reference, 2026-10-15] The docs also say never to compare to exact values of the level, because new levels may be added. Test for greater or equal instead. NSCachehas automatic eviction. If other apps need memory, it removes some items (Apple: NSCache). [Verified: Apple documentation, 2026-10-15]- On iOS, the
URLCachedisk part may be purged when the system runs low on disk space, but only when your app is not running (Apple: URLCache). [Verified: Apple documentation, 2026-10-15]
The lesson: a cache is a hint, not storage. The app must work when any cached value is gone. [Opinion]
What you pay for each choice
A long TTL, a short TTL, a big cache and a small cache all cost something. A good answer names the cost of each choice. [Opinion]
| Choice | You gain | You pay |
|---|---|---|
| Longer TTL | Fewer fetches, less data and battery | More chances to show an old value |
| Shorter TTL | Fresher values | More fetches, and a slow screen when the network is bad |
| Bigger cache | More hits | More memory or storage, and the system may clear it anyway |
| Smaller cache | Less memory and storage | More misses and more fetches |
| Check with the server before reuse | Fresh values with a small request | One round trip each time, and it fails without a network |
| Show saved value, then refresh | A fast screen, and a fresh value soon after | The value on screen can change while the user reads it |
All cells are [Opinion]. In a round, say which cost you accept and why. For example: "I accept a one minute old feed, because opening the app fast matters more than being exact."
What must never be evicted
A cache holds copies. The source of truth is somewhere else. Eviction is safe only for copies.
Some data has no other copy. An unsent message, an unsaved edit, or a payment that is waiting to be sent exists only on the phone. Keep it in a database table such as an outbox, never in an evictable cache. The offline-first guide shows how this fits together. The idempotency keys guide shows how to send such data again safely. [Opinion]
When I would choose which
- Images in a scrolling list. A memory cache with a byte limit and LRU. A disk cache with a byte limit and LRU. The image loading case does this.
- Feed pages. Save them in the database. Show them first. Refresh in the background. Use a TTL to decide when to refresh.
- A value used on every screen. The current user, for example. Give it high priority, or keep it in the database and not in the cache.
- Offline content. A timetable or a downloaded map. This is not a cache. Download it on purpose and keep it. The public transport case does this.
- Data that must be right. Balances and payment status. Do not use a cached value for a decision. Ask the server.
- Search results. A short TTL and a small byte limit. The user rarely goes back to old searches.
All of these are [Opinion].
Mistakes I see
These are my reading of designs, not claims about any company.
- The cache has no limit. It grows until the system kills the app.
- The limit counts items, but the items have very different sizes.
- There is an eviction rule but no TTL, so old values stay forever as long as they are used.
- There is a TTL but no size limit, so expired values still use space until someone reads them.
- The app treats the cache as storage and crashes or shows a blank screen when a value is gone.
- The cache key leaves out a part of the request, such as the image size or the language, so the wrong value comes back. See cache key.
- Two screens ask for the same missing value at the same time, and the app fetches it twice. The image loading case shows how to share one fetch.
- The app clears the whole cache on every memory warning and then refetches everything at once.
On Android
LruCache is a ready LRU memory cache. It is thread-safe, and it does not allow null keys or values (Android: LruCache). [Verified: Android reference, 2026-10-15] Override sizeOf to count bytes, not entries.
Respond to onTrimMemory by trimming your memory cache, and keep the disk cache. That matches the doc text about cleaning up resources that can be rebuilt quickly. For a disk cache and HTTP caching, most apps use the library they already have for networking and images. [Opinion] I did not verify the defaults of any library.
On iOS
Use NSCache for memory. It evicts on its own when memory is low, and you can call it from many threads without a lock (Apple: NSCache). [Verified: Apple documentation, 2026-10-15] Set a cost limit and still expect it to be loose.
For network responses, URLCache gives a memory part and a disk part, and you can set the size of each (Apple: URLCache). [Verified: Apple documentation, 2026-10-15] The disk part can be purged when the app is not running, so never keep the only copy there.
How to say it in the round
This is a short answer you can adapt. Use your own words. [Opinion]
"I cache in two layers, memory and disk, and both have a size limit in bytes. When a layer is full I remove the value used longest ago. Each value also has a TTL that fits how often it changes. An old value is shown only if the screen can live with it. Anything the user cannot get again, like an unsent message, is not in the cache. It is in the database."
Questions an interviewer may ask next:
- What happens when the cache is full of old data?
- How do you choose the TTL?
- What if one huge item pushes out everything else?
- What happens to your design if the system clears the cache?
- How do you keep two screens from fetching the same value twice?
Check yourself
Try to answer before you open each answer.
1. A cache has a TTL of one hour but no size limit. What goes wrong?
Values that nobody reads again stay in the cache, because expiry is only checked when a value is read. The cache grows. You need a size limit and an eviction rule as well.
2. The avatar is on every row, but FIFO keeps removing it. Why?
FIFO removes the value that was saved first. The avatar was saved first, so it goes first, even though it is used all the time. LRU would keep it, because each use moves it to the front.
3. Why is "the cache is full" a bad reason to say the data is safe to delete?
It is only safe to delete a copy. If the cache holds the only copy of an unsent change, deleting it loses the change. Keep such data in a database.
See it in a case
- Image loading library: a memory cache with a byte limit, a disk cache with LRU, and one shared fetch for the same image. The build guide has the details.
- News feed: saved pages, a refresh, and a feed that stays fresh enough.
- Public transport app: a timetable that works offline, with live delays on top.
- Feature flags: values saved on the phone, and what to do when they are old.
To try the rules yourself, open the cache explorer.
Words used here
- Cache: a saved copy that the app can reuse.
- Eviction: removing items from a cache to make room.
- LRU cache: a cache that removes the item used longest ago.
- TTL: how long a saved value may be used.
- Stale data: a saved value that is older than the real one.
- ETag: a version tag that lets the app ask if a file changed.
- Cache key: the label that finds a value in a cache.
- Source of truth: the one place that holds the real value.
Sources
- LruCache (Android reference)
- ComponentCallbacks2 (Android reference)
- NSCache (Apple)
- totalCostLimit (Apple)
- URLCache (Apple)
Words on this page (8)
- Cache
- A saved copy of a value that the app can reuse. It is fast to read, and it may be removed at any time.
- Cache key
- The label of a saved item. Two requests with the same key share one saved copy.
- Eviction
- Removing items from a cache to make room for new ones. A rule decides which items go first.
- ETag
- A version tag that the server sends with a file. The app sends it back later to ask if the file changed.
- LRU cache
- A cache that removes the item used longest ago when it is full. LRU means least recently used.
- Source of truth
- The one copy of the data that the app trusts. In these cases it is the database on the phone, and the screen only reads it.
- Stale data
- Saved data that may be out of date. The app should show how old it is, or refresh it.
- TTL
- Time to live. How long a saved item or a message stays valid before it expires.
Verification notes (last reviewed 15 Oct 2026)
Written on 2026-10-15. I fetched the Android LruCache reference and the Android ComponentCallbacks2 reference, and I fetched the Apple JSON for NSCache, NSCache totalCostLimit and URLCache. The quoted facts are: how LruCache moves a value to the head of a queue and evicts the end of the queue; that LruCache counts entries by default and that sizeOf changes the unit; that the class is thread-safe and takes no null key or value; the onTrimMemory text for the background level and the note about exact values; that NSCache has automatic eviction when memory is needed, that it can be used from many threads, that totalCostLimit is not a strict limit and the eviction order is not guaranteed; and that the URLCache disk part may be purged on iOS when the system runs low on disk space and the app is not running. I could not fetch the HTTP caching specification (RFC 9111) or the MDN Cache-Control page from this session, so I made no claim about header rules. I did not check Room, Core Data, OkHttp, Coil, Kingfisher or Nuke. The rule table, the cost table, the list of choices, the list of mistakes and the interview answer are my own reading. The cases were read in the repository, not re-checked against live pages. The cache explorer was run in a browser and its numbers were checked against the model code.
Keep reading
Get the free one-page cheat sheet
One page: the five steps, a 45-minute time plan and two checklists. Print it or keep it on your phone.
We email you the cheat sheet after you confirm your address. You can also open it now from the Start here page. You get a short note when a new case is published. Unsubscribe any time. Privacy.