skip to content

Cache Toolkit

Django's cache framework puts one API over Redis, Memcached, database and local-memory stores, used per page, fragment or key. Interviewers probe invalidation and stale per-user pages.

part ofDjangooverview, primer and where to startread it →
on this pageshow

explore

questions

16

With Django's low-level cache API, how do cache.set(), cache.get() and cache.add() behave, and what do timeout=None and timeout=0 mean?

level: juniorimportance: must knowfreq 62%

answer

  1. any picklable value
  2. a miss returns the default
  3. add only when absent
  4. None forever, 0 not at all

basics

~20 s

Django's cache.set(key, value, timeout) stores a picklable value, cache.get(key, default) returns it or the default on a miss, and cache.add() stores only if the key is absent. timeout=None means never expire; timeout=0 means do not cache.

solid answer

~40 s

`from django.core.cache import cache` gives the default alias. `cache.set("leaderboard:weekly", rows, 300)` stores any picklable object for 300 seconds; leaving `timeout` out uses the alias's `TIMEOUT` (default 300). `cache.get(key)` returns the value or `None` on a miss, or the `default` you pass; to tell a cached `None` from a miss, pass a sentinel such as `object()`. `cache.add(key, value)` writes only when the key is not already present and returns `True` or `False`. For the timeout, `None` stores the value with no expiry and `0` does not cache it, so `set(key, value, 0)` effectively removes whatever was there. Every method also has an `a`-prefixed twin for async code, such as `await cache.aget(key)`.

go deeper

for a junior

Recall set, get with a default, add, and that None means forever while 0 means not cached.

for a middle

Explain the TIMEOUT fallback, pickling, the sentinel pattern for cached None, and add's return value.

for a senior

Choose timeouts deliberately per value, and use add's first-writer-wins behaviour where concurrent writers meet.

for a principal

Set conventions for keys, timeouts and what may be cached so a team's cache usage stays predictable.

## The low-level API in one import Django's **low-level cache API** is for caching values, not whole pages: a computed leaderboard, the result of an expensive query, a rendered snippet, an API response. You import a backend object and call methods on it: ```python from django.core.cache import cache # the "default" alias from django.core.cache import caches # caches["other"] for other aliases ``` Every built-in backend implements the same methods, so code does not change when the backend does. ## set, get and add | Method | What it does | Returns | |---|---|---| | `cache.set(key, value, timeout=..., version=None)` | Stores or overwrites the value | `None` | | `cache.get(key, default=None, version=None)` | Reads the value | The value, or `default` on a miss | | `cache.add(key, value, timeout=..., version=None)` | Stores only if the key is absent | `True` if stored, `False` otherwise | - **Keys** should be strings. Keep them short and free of spaces: the Memcached backends reject keys over 250 characters or containing whitespace, and the other backends warn about them. - **Values** can be any **picklable** Python object: lists of dicts, model instances, dataclasses. Lambdas, open files and database connections cannot be pickled. - **`add()`** is the "only if empty" write. It is the building block for "first writer wins" logic, and on Redis and Memcached it maps to an atomic server-side operation. ## Timeout semantics The `timeout` argument is in **seconds**: 1. **Omitted**: uses the alias's `TIMEOUT` from `CACHES`, which defaults to `300`. 2. **A positive number**: expire after that many seconds. 3. **`None`**: store with **no expiry**. The entry stays until it is deleted, overwritten or evicted by the backend. 4. **`0`**: **do not cache**. The value expires immediately; on several backends a `set()` with `0` simply deletes the existing key. Confusing `0` with "forever" is the classic mistake: code meant to cache a leaderboard permanently with `timeout=0` caches nothing, and every request recomputes it. ## Telling a cached None from a miss `cache.get()` returns `None` both when the key is missing and when the stored value is `None`. If `None` is a legitimate result, for example "no leader yet this week", pass a sentinel: ```python MISSING = object() value = cache.get("leaderboard:weekly", MISSING) if value is MISSING: value = compute_leaderboard() cache.set("leaderboard:weekly", value, 300) ``` ## Related calls worth knowing - **`cache.has_key(key)`** and **`key in cache`** report whether a live, unexpired entry exists. - **`cache.get_or_set(key, default, timeout)`** combines the read, the fallback and the write; `default` may be a callable. - **`cache.delete(key)`** removes one entry and returns whether a key was deleted. - **Async twins**: `aget`, `aset`, `aadd`, `adelete` and the rest take the same arguments and are awaited in async views. ## Choosing keys and values A few habits keep low-level caching predictable: - **Namespace keys by purpose**: `leaderboard:weekly`, `leaderboard:weekly:page:2`, `player:42:rank`. Readable keys are easy to delete deliberately and easy to find when debugging. - **Put every input in the key**: if the leaderboard depends on the season and the region, both belong in the key, otherwise one region's rows are served to another. - **Cache plain data rather than live objects**: a list of dicts or tuples pickles small and survives code changes better than full model instances, which drag their whole field state and class path into the pickle. - **Keep values modest**: large pickles cost time to serialise on every write and read, and Memcached rejects values above its item size limit. - **Pick the timeout from how stale the data may be**, not from how expensive it is to compute; expensive data that must be fresh needs invalidation, not a long timeout. ## A leaderboard example A view shows the weekly leaderboard, which needs an expensive aggregate query: - Read with `cache.get("leaderboard:weekly")`. - On a miss, run the query, then `cache.set("leaderboard:weekly", rows, 300)`. - When a match result is recorded, call `cache.delete("leaderboard:weekly")` so the next reader rebuilds it. That is the whole pattern at junior level; the traps come from concurrency, invalidation and the choice of backend.

  • Why can cache.get() not tell you whether a key holds None?
    `cache.get()` returns its `default`, which is `None` unless you pass another value, both on a miss and when the stored value is `None`. Passing a unique sentinel such as `object()` as the default separates the two cases, because a miss returns that exact object.
  • What does cache.add() return, and when would you use it instead of set()?
    It returns `True` if it stored the value and `False` if the key already existed. Use it when the first writer must win, for example initialising a counter before `incr()`, or claiming a short-lived marker so only one worker rebuilds an expensive value.

A timeout of None is a note pinned to the board with no date; a timeout of 0 is a note that you throw away the moment you write it.

saying these in an interview costs you the question

  • timeout=0 caches the value forever.
  • Omitting timeout stores the value with no expiry.
  • cache.get() raises KeyError when the key is missing.
  • cache.add() overwrites an existing value like set().
  • Only strings and numbers can be stored in Django's cache.
open as a page

What does Django's cache_page decorator do, and what do its timeout, cache and key_prefix arguments control?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Django's cache_page stores a view's whole GET or HEAD response in the cache and serves it for later requests to the same URL. timeout is the lifetime in seconds, cache picks the CACHES alias, and key_prefix namespaces the keys.

open as a page

A Django weather-forecast site runs on three servers; which built-in cache backend would you pick for its CACHES default, and why?

level: middleimportance: must knowfreq 55%

basics

~20 s

Pick a shared network backend, RedisCache or PyMemcacheCache, so all three servers and every worker read the same forecasts. DatabaseCache works when no cache server is allowed; LocMemCache, FileBasedCache and DummyCache do not share across servers.

open as a page

Why does Django's LocMemCache backend give inconsistent results once a site runs under several worker processes?

level: middleimportance: must knowfreq 62%

basics

~20 s

LocMemCache keeps entries in a Python dict inside each process. With several worker processes every worker has its own private copy, so a set or delete in one worker is invisible to the others and readers see stale or missing values.

open as a page

How does Django's {% cache %} template tag cache a fragment per user or language, and how do you invalidate that fragment from Python code?

level: middleimportance: must knowfreq 48%

basics

~20 s

Django's {% cache timeout name var1 var2 %} stores the rendered block under a key from the fragment name and the extra arguments' string values. To invalidate, rebuild it with make_template_fragment_key(name, [vars]) and delete it from the same alias.

open as a page

In Django's MIDDLEWARE setting, why must UpdateCacheMiddleware come first and FetchFromCacheMiddleware come last?

level: middleimportance: must knowfreq 55%

basics

~20 s

Django runs middleware top-down on requests and bottom-up on responses. UpdateCacheMiddleware must be first so it stores the response after every other middleware has added its Vary headers; FetchFromCacheMiddleware must be last so lookups see the same request state.

open as a page

In Django, how do you configure more than one cache in the CACHES setting and use a non-default cache from your code?

level: juniorimportance: should knowfreq 45%

basics

~10 s

Django's CACHES setting maps alias names to a dict with BACKEND, LOCATION and options; a 'default' alias is required. Code uses django.core.cache.cache for the default and caches['alias'] for any other.

open as a page

In Django's CACHES setting, how do KEY_PREFIX, VERSION and KEY_FUNCTION combine into the key actually stored in the cache backend?

level: middleimportance: should knowfreq 38%

basics

~20 s

Django never stores your key verbatim: the default key function joins KEY_PREFIX, VERSION and the key with colons, so with KEY_PREFIX 'site' the key 'forecast' is stored as 'site:1:forecast'. KEY_FUNCTION swaps in a custom joiner, for example one that hashes long keys.

open as a page

In Django's cache API, how do delete(), delete_many(), touch() and incr_version() differ when you need to invalidate or extend cached leaderboard entries?

level: middleimportance: should knowfreq 32%

basics

~20 s

Django's delete() removes one key, delete_many() removes a list, and touch() only changes an existing key's expiry. incr_version() moves one key's value to a higher version, so readers using the default version miss it; it is not a group invalidation.

open as a page

Is Django's cache.incr() atomic, and what happens when you call it on a key that does not exist yet?

level: middleimportance: should knowfreq 34%

basics

~10 s

Django's cache.incr() is atomic only on backends with a native increment, like Memcached and Redis; others read then write. A missing key raises ValueError, so initialise the counter with cache.add(key, 0) first.

open as a page

In Django, how do never_cache and patch_cache_control let a view opt out of or tune the page cache, and which responses are never stored?

level: middleimportance: should knowfreq 35%

basics

~20 s

Django's page cache reads the response headers: never_cache adds private, no-cache and no-store, which UpdateCacheMiddleware and cache_page refuse to store. patch_cache_control edits Cache-Control, for example max-age to set the lifetime; non-200, streaming and Vary: * responses are never stored.

open as a page

Two Django projects share one Redis database with different KEY_PREFIX values; what does cache.clear() in one project remove, and how do you isolate them properly?

level: seniorimportance: should knowfreq 28%

basics

~20 s

cache.clear() on RedisCache runs FLUSHDB, so it wipes the whole Redis database, including the other project's keys; KEY_PREFIX does not scope it. Isolate projects with separate Redis databases or servers in LOCATION, and delete known keys instead of clearing.

open as a page

A Django leaderboard is cached with cache.get_or_set(key, compute_leaderboard, 300); when the entry expires under heavy traffic the database spikes. What does get_or_set actually do, and how would you fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Django's get_or_set is get, call the default, add, get again, with no lock, so every request missing an expired hot key runs the expensive callable. Refresh the value ahead of expiry, or let one worker claim the rebuild with add().

open as a page

A Django events calendar view wrapped in cache_page shows one member's name and RSVP buttons to anonymous visitors; why does this happen, and how do you fix it?

level: seniorimportance: should knowfreq 45%

basics

~20 s

cache_page stores the response before SessionMiddleware adds Vary: Cookie, so the first member's personalised page is keyed by URL alone and served to everyone. Cache only the public calendar and never_cache the logged-in view, or vary explicitly inside the decorator.

open as a page

In an async Django view, what do cache.aget() and cache.aset() actually do with the built-in cache backends?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Django's aget() and aset() are async wrappers: with every built-in backend they run the synchronous method through sync_to_async in a worker thread. They keep the event loop unblocked, but the cache I/O itself is not asynchronous.

open as a page

How does Django's page cache build the key for a stored response, and why does it keep a separate header list for each URL?

level: middleimportance: nice to knowfreq 25%

basics

~20 s

Django hashes the full URL plus the request's values for the headers named in the response's Vary. Since Vary exists only on responses, it saves that header list under a URL-only key and reads it back on later requests.

open as a page