DEV Community

Cover image for Caching Without the Confusion: Patterns, Trade-offs, and When to Use Each
Rahul Dhawan
Rahul Dhawan

Posted on

Caching Without the Confusion: Patterns, Trade-offs, and When to Use Each

Caching Without the Confusion: Patterns, Trade-offs, and When to Use Each

Caching is one of the simplest ways to make an application feel dramatically faster until stale data, cache misses, or invalidation problems appear.

At its core, a cache stores frequently accessed data in a faster storage layer so the application does not need to repeatedly query a slower source such as a database, external API, or filesystem.

This article explains the most common caching types and strategies, along with practical guidance on when to use each one.

How caching works

A typical cached read follows this flow:

  1. The application requests data.
  2. It checks the cache using a unique key.
  3. On a cache hit, the cached value is returned.
  4. On a cache miss, the application retrieves the value from the source of truth.
  5. The value may then be cached for future requests.

How cache works

A useful cache depends on three decisions:

  • Cache key: How is the value identified? Example: product:123.
  • Expiration: How long can the value remain cached?
  • Eviction: What happens when the cache runs out of space?

Common eviction policies include LRU (least recently used), LFU (least frequently used), and TTL-based expiration.

Cache meme

In-memory vs. distributed caching

In-memory cache

An in-memory cache lives inside a single application process. Examples include a language-native map, an LRU cache library, or a framework's local cache.

Advantages:

  • Extremely fast
  • Simple to implement
  • No network call

Limitations:

  • Each application instance has its own copy
  • Cache contents disappear when the process restarts
  • Memory is limited to the host
  • Data may be inconsistent across instances

Use it when: the data is local, inexpensive to rebuild, and does not need to be shared for example, parsed configuration, static reference data, or short-lived request metadata.

Caching starts at the hardware level.

This diagram shows the typical CPU cache hierarchy: each core has small, fast private L1 and L2 caches, while a larger L3 cache is shared across cores.

CPU Cache hierarchy

Distributed cache

A distributed cache runs outside the application and can be shared by multiple instances. Redis and Memcached are common examples.

Advantages:

  • Shared across application instances
  • Can scale independently
  • Better consistency across a distributed system
  • May support replication and persistence

Limitations:

  • Adds network latency
  • Requires infrastructure and monitoring
  • Can become a dependency or bottleneck
  • Serialization adds overhead

Use it when: several services or application instances need access to the same cached data, such as user sessions, product details, rate-limit counters, or API responses.

Distributed vs In-memory caching

Common caching strategies

1. Cache-aside (lazy loading)

With cache-aside, the application manages the cache directly.

  • Read from the cache first.
  • If the value is missing, read from the database.
  • Store the result in the cache.
  • Return the value.

Cache sequence diagram

Best for: read-heavy workloads where not every record needs to be cached.

Example: An e-commerce site caches popular product pages. Products are added to the cache only after someone requests them.

Watch out for: stale data and repeated misses during traffic spikes. Invalidate or update the cache after database writes, and consider request coalescing to prevent a cache stampede.

Cache meme 2

2. Read-through

Read-through looks similar to cache-aside, but the cache provider—not the application—loads missing values from the database.

The application always asks the cache for data. On a miss, the cache invokes a configured loader, stores the result, and returns it.

Best for: applications that want simpler read logic and have a caching library or platform that supports data loaders.

Example: A service repeatedly loads customer profiles. A read-through abstraction keeps database-loading logic out of business code.

Trade-off: the cache layer becomes more tightly coupled to the data source and its loading rules.

3. Write-through

With write-through, every write is synchronously sent to both the cache and the database. The operation succeeds only after the backing store is updated.

Best for: data that is read frequently after being updated and where cache freshness matters.

Example: A user updates their account preferences. Writing to both layers ensures the next request sees the latest settings.

Trade-off: writes have higher latency, and rarely read data may consume cache space.

4. Write-behind (write-back)

With write-behind, the application writes to the cache first. The cache acknowledges the request and updates the database asynchronously, often in batches.

Best for: write-heavy workloads where throughput is more important than immediate durability.

Example: A gaming platform records rapidly changing counters or activity metrics and periodically flushes them to durable storage.

Trade-off: data can be lost if the cache fails before pending writes reach the database. This strategy requires durable queues, retries, monitoring, and clear failure handling.

5. Write-around

With write-around, the application writes directly to the database without updating the cache. Future reads load the new value into the cache if needed.

Best for: write-heavy data that may never be read soon after it is written.

Example: An audit system stores large event records, while only a small percentage are later viewed.

Trade-off: the first read after a write is a cache miss. Existing cached values must also be invalidated to avoid stale reads.

Trade-off flow chart

Quick comparison

Strategy Who loads or writes the cache? Main strength Main risk
Cache-aside Application Flexible and widely supported Stale data and miss storms
Read-through Cache layer Simpler application reads Provider coupling
Write-through Cache and database synchronously Fresh cache after writes Higher write latency
Write-behind Cache first; database later High write throughput Possible data loss
Write-around Database; cache populated later Avoids caching cold writes Read-after-write miss

Production pitfalls to plan for

Caching moves complexity rather than removing it. Before shipping, consider:

  • TTL jitter: Add small randomness to expiration times so many keys do not expire simultaneously.
  • Cache stampede protection: Allow one request to refresh a missing value while others wait or receive slightly stale data.
  • Negative caching: Briefly cache “not found” results to protect the database from repeated invalid requests.
  • Invalidation: Delete or update affected keys whenever the source data changes.
  • Observability: Track hit rate, miss rate, latency, evictions, memory usage, and errors.
  • Failure behavior: Decide whether the application should bypass the cache or fail when it is unavailable.
  • Security: Do not place secrets or sensitive data in a cache without appropriate encryption and access controls.

Final takeaway

There is no universally best caching strategy.

  • Start with cache-aside for most read-heavy applications.
  • Use read-through when your caching platform can cleanly own data loading.
  • Choose write-through when fresh cached data matters more than write speed.
  • Consider write-behind only when you can tolerate delayed persistence and engineer for failure.
  • Use write-around to avoid filling the cache with data unlikely to be read.

A good cache should reduce load without becoming a second, less reliable database. Keep the source of truth clear, define expiration and invalidation rules, and measure whether the cache is actually improving the system.

Thanks for reading

Top comments (0)