The fastest cache layer in most systems and the coherence risks that come from keeping separate local copies in each instance.
In-process and local memory caches live inside the running application or on the same host. They are usually the lowest-latency cache layer available because they avoid network round trips entirely. A lookup might be a simple hash map access inside the process. That makes them attractive for configuration values, repeated object reads, expensive computed results, and other small hot datasets.
The trade-off is that each process owns its own copy. In a horizontally scaled application, ten instances may each cache the same key independently. That is great for speed, but it creates duplication, uneven warm-up, and coherence problems when underlying data changes.
flowchart LR
A["App instance A\nlocal cache"] --> D["Origin store"]
B["App instance B\nlocal cache"] --> D
C["App instance C\nlocal cache"] --> D
A -. "separate copy" .-> B
B -. "separate copy" .-> C
This is often the first cache teams add because it is so easy to reach for. A memoized function, an LRU map, or a framework-provided local cache can remove a surprising amount of repeated work quickly. But local ease can hide distributed consequences:
Local caches are strongest when the cached answer:
They are especially good for:
The main weakness of local caches is not speed. It is agreement. If each application instance holds its own copy, then writes or invalidation events must somehow reach all of them. Some systems accept that eventual divergence. Others add explicit fan-out invalidation or version checks. Either way, the cache is no longer purely local in reasoning terms.
That is why local caches are best for data where “slightly different across instances for a short time” is acceptable. When all nodes must agree almost immediately, a local-only approach gets harder to trust.
This example shows a simple local memoization cache for configuration data inside one process. The pattern is fast, but every process still owns its own copy.
1const configCache = new Map<string, { value: string; expiresAt: number }>();
2
3async function getConfigValue(key: string): Promise<string> {
4 const cached = configCache.get(key);
5 const now = Date.now();
6
7 if (cached && cached.expiresAt > now) {
8 return cached.value;
9 }
10
11 const fresh = await readConfigFromStore(key);
12 configCache.set(key, {
13 value: fresh,
14 expiresAt: now + 60_000
15 });
16
17 return fresh;
18}
What to notice:
A service runs 40 replicas behind a load balancer. Would a local cache alone guarantee consistent answers across replicas after a write?
Usually no. It may still be a valid design if bounded divergence is acceptable, but local caches do not provide shared coherence by default. The team must decide whether that inconsistency window is acceptable or whether a shared cache or explicit invalidation mechanism is needed.