Skip to content
ADevGuide Logo ADevGuide
Go back

Write-Through vs Write-Back Caching: Key Differences

By Pratik Bhuite | 21 min read

Hub: Java / Interview Fundamentals

Series: Backend Interview Mastery

Last verified: Sep 7, 2026

Part 25 of 32 in the Backend Interview Mastery

Key Takeaways

On this page
Reading Comfort:

Write-through versus write-back caching

An application accepts a customer preference update in 5 ms, then the cache node fails before the database receives it. Was the update successful? The answer depends on the cache write policy, not just on whether Redis was fast. Write-through and write-back make opposite promises about when durable storage must acknowledge a write.

This guide follows the cache-aside pattern article. Cache-aside explains how applications populate and invalidate read caches. Here, the focus is the write path: what the caller may safely believe after a success response, and what must happen when a cache, worker, or database fails.

Table of Contents

Open Table of Contents

The Core Difference

Write-through caching keeps the cache update and backing-store write on one synchronous request path. The service returns success only after the durable store confirms the write. That is usually coordination between two operations, not one distributed atomic transaction: a database commit can succeed while the cache update fails, so the application needs a repair path. Write-back caching, also called write-behind, first accepts the change in the cache or buffer and persists it asynchronously later.

The distinction is not a naming preference. It determines the acknowledged durability point. In write-through, the database acknowledgement is on the critical path. In write-back, a durable queue, log, or cache entry must survive long enough for a separate worker to flush the change. A process-local map is not a write-back system; it is an unprotected loss window.

flowchart TD
    A[Client write] --> B{Policy}
    B -->|Write-through| C[Update cache and backing store]
    C --> D[Backing store confirms]
    D --> E[Return success]
    B -->|Write-back| F[Persist change in cache or durable buffer]
    F --> G[Return success]
    G --> H[Async worker flushes backing store]

Both policies still need a defined source of truth. With ordinary application caching, the database usually remains authoritative. With a deliberate write-back design, the durable write log may temporarily become the authoritative newest state until the backing store catches up. That is a much stronger operational commitment than using Redis as a convenient read cache.

Write-Through: Persist Before Acknowledging

In write-through, a write updates the cached representation and the backing store before the request is considered successful. Implementations vary: an application can perform the two writes itself, or a cache integration can coordinate the backing-store write. The important contract is the same: do not acknowledge durable success merely because the cache accepted the value.

sequenceDiagram
    participant C as Client
    participant A as Application
    participant D as Database
    participant H as Cache
    C->>A: Update profile
    A->>D: Commit canonical update
    D-->>A: Commit confirmed
    A->>H: Set fresh cache value
    H-->>A: Set confirmed
    A-->>C: Success

The ordering above favors database correctness. If the cache write fails after the database commit, return success only if the endpoint’s contract is durable persistence; delete or retry the cache key so a later read cannot receive an old representation. If an implementation instead writes cache first, it must ensure a database failure cannot leave a cache value visible as a successful update. A database-first write plus invalidation is often the simpler default for application-managed caches.

Write-through is useful when a newly written value is likely to be read soon and a warmed cache is valuable: account preferences, product metadata, entitlement records, and configuration reads are common examples. It costs at least one durable-store round trip on every write and can populate cache space with values that no one rereads. It also cannot make a multi-key update magically atomic; use a database transaction for the authoritative change and design cache repair around it.

Write-Back: Buffer Then Persist

With write-back, the request acknowledges after placing a change in a fast layer. A separate worker batches, coalesces, and writes those changes to the backing store. That can produce high write throughput because multiple updates to the same key can become one database write.

sequenceDiagram
    participant C as Client
    participant A as Application
    participant B as Durable buffer
    participant W as Flush worker
    participant D as Database
    C->>A: Record counter increment
    A->>B: Append versioned change
    B-->>A: Durable append confirmed
    A-->>C: Success
    W->>B: Read pending changes
    W->>D: Apply idempotent batch
    D-->>W: Commit confirmed
    W->>B: Mark flushed

The word durable in that diagram is non-negotiable. If the only copy exists in volatile cache memory, a restart, eviction, failover, or memory-pressure event can erase acknowledged writes. A safer design uses a replicated durable stream or write-ahead log, makes the worker idempotent, records a per-key version or sequence number, and monitors the backlog. The cache can still hold the newest value for reads, but it is not the only recovery source.

Write-back fits workloads where a bounded delay is acceptable and write coalescing materially reduces cost: telemetry aggregates, non-critical counters, activity feeds, and derived search or analytics projections. It is a poor default for payment records, inventory reservations, password changes, or any action that must survive immediately and be read correctly by another system. For those, an explicit durable database commit or an event log with a clear acknowledgement contract is usually required.

Comparison Table

QuestionWrite-throughWrite-back
When can the caller receive success?After backing storage confirms.After the buffer accepts the write.
Write latencyIncludes backing-store latency.Usually lower on the request path.
Loss risk after acknowledgementBounded by backing-store durability.Depends on durability and replication of the buffer.
Backing-store loadOne persistent write per logical write.Can batch and coalesce many changes.
Read freshnessCache can be warmed immediately after a successful write.Cache may have newer data than the backing store.
Recovery complexityCache repair and invalidation.Replay, ordering, deduplication, poison messages, and backlog recovery.
Typical fitDurable writes that will be read again soon.High-rate, delay-tolerant, replayable derived writes.

Do not confuse write-back with asynchronous database replication. A primary database can commit a write durably and later replicate it, while write-back delays even the backing-store write. The database replication guide covers the separate question of how copies of an already committed database change stay current.

Failure Windows and Recovery

Every cache policy has failure windows. The responsible design names them and gives each one an owner.

Database commit succeeds, cache update fails

This is the common write-through repair case. The database has the new canonical value but the cache might hold an old one. Delete the key, retry the update asynchronously, or use a short TTL as a safety bound. Do not roll back a committed business operation merely because a performance layer is unavailable unless the product explicitly requires a synchronized cache.

Cache or buffer succeeds, backing-store write fails

For write-back, this is expected temporarily. The flush worker must retry with exponential backoff and a retry budget, preserving order when the domain requires it. Its write operation must be idempotent: a worker can crash after the database commit but before it marks a record flushed. A stable event ID, expected version, or upsert condition lets a replay detect that the effect already exists.

A newer change overtakes an older delayed change

Suppose a user changes a display name from A to B, then to C. If delayed events are applied out of order, B can overwrite C. Partition a key’s events consistently, attach a monotonic version, and reject an update whose version is older than the stored version. A timestamp alone is risky when clocks are not tightly controlled.

Backlog grows without bound

A low-latency request path can hide a saturated database until the buffer becomes full. Monitor queue age, pending keys, flush rate, retry rate, error count, and oldest-unflushed version. Establish a limit before launch: slow or reject new writes, switch to a durable direct-write path, or shed only operations whose loss the product allows. Backpressure is a feature, not an alarm you hope never fires.

Choosing a Write Policy

Start with the business promise, not the cache product:

  1. What exactly does a 200 response mean: accepted for later processing, or durably recorded?
  2. Can a reader see the new value before the backing store does?
  3. What is the maximum acceptable loss window and recovery point objective?
  4. Is the write replayable and idempotent?
  5. Is batching valuable enough to justify a durable buffer, worker fleet, replay tools, and on-call alerts?

For many backend APIs, the answer leads to a database commit followed by cache invalidation, as in cache-aside. Write-through adds value when eagerly warming the cache after a durable write is worth the additional coupling. Choose write-back only when the workload and product contract explicitly tolerate asynchronous persistence and you can operate it as a small data pipeline.

Before either policy, make sure the underlying query and schema are healthy. A cache can reduce pressure, but it does not fix a poor access path; use the slow SQL debugging guide to investigate the source workload. For a fleet-wide cache, the distributed cache system design guide adds sharding, replication, hot keys, and capacity trade-offs.

Implementation Sketches

These TypeScript-like examples show the contracts, not a production cache client. Real implementations need deadlines, metrics, connection handling, and transactions appropriate to the storage engine.

Durable write then cache repair

type Preference = { userId: string; theme: "light" | "dark"; version: number };

async function updatePreference(next: Preference): Promise<void> {
  await database.updatePreference(next); // Success means the source of truth committed.

  try {
    await cache.set(`preference:v1:${next.userId}`, JSON.stringify(next), {
      ttlSeconds: 300,
    });
  } catch (error) {
    logger.warn({ error, userId: next.userId }, "Cache refresh failed");

    // A later read must reload instead of trusting an older cached value.
    try {
      await cache.delete(`preference:v1:${next.userId}`);
    } catch (deleteError) {
      // Do not report failure after the database has durably committed.
      logger.warn({ deleteError, userId: next.userId }, "Cache repair failed");
    }
  }
}

This is closer to a database-first write-through implementation than an independent cache write. The endpoint does not promise a warm cache; it promises a durable preference update. If cache repair fails too, the bounded TTL and a retry job limit the stale window.

Write-back requires a durable event and idempotent flush

type CounterEvent = {
  eventId: string;
  key: string;
  delta: number;
  version: number;
};

async function recordView(event: CounterEvent): Promise<void> {
  // The stream acknowledgement is the durability boundary for this API.
  await durableStream.append(event.key, event);
}

async function flushView(event: CounterEvent): Promise<void> {
  await database.applyCounterDeltaOnce(event);
  // The database operation records eventId, so a replay cannot increment twice.
}

The key decision is not syntax. durableStream.append needs a documented replication and acknowledgement policy, while applyCounterDeltaOnce needs a unique event ID or equivalent idempotency rule. Without those two properties, a fast success response simply moves failure risk out of sight.

Production Checklist

  • Define the durability promise for every successful response.
  • Keep the database or durable log authoritative; do not treat an evictable cache as the sole record of acknowledged writes.
  • Make writes idempotent and versioned where retries or reordering are possible.
  • Bound cache timeouts and make cache failures degrade safely.
  • For write-back, use a durable replicated buffer, track backlog age, and test worker replay.
  • Test database failure, cache restart, duplicate delivery, out-of-order updates, and buffer saturation.
  • Measure write latency, flush latency, oldest pending event, retries, cache errors, stale reads, and recovery time.

Interview Questions

1. What is the difference between write-through and write-back caching?

Write-through waits for the backing store before success, so its acknowledgement has the backing store’s durability properties. Write-back acknowledges after a fast buffer accepts the write and persists later. It can reduce request latency and batch writes, but its correctness depends on a durable buffer, replay, ordering, and idempotency.

2. Is write-back safe with Redis?

Not by itself. Redis persistence and replication choices may make it a component of a durable design, but an evicted key, failover gap, or unavailable persistence path can still lose a change. I would use a deliberately durable, monitored log as the acknowledgement boundary and treat Redis as a cache unless I can prove its configured durability meets the product’s loss tolerance.

3. When would you choose write-through over cache invalidation?

I would choose it when the write is durable and the same object will likely be read immediately, so eagerly refreshing the cache reduces a predictable miss. If the write affects many derived views or the writer cannot construct the full canonical representation, database-first invalidation is safer and simpler.

4. How do you prevent an older write-back event from overwriting a newer value?

I partition events by the entity key, attach a monotonic version, and use a conditional backing-store update that rejects older versions. The worker must preserve the required key ordering, and the write must be idempotent because a crash can deliver the same event more than once.

5. What metrics prove a write-back system is healthy?

I monitor acknowledgement latency, pending-event count, oldest event age, flush throughput, retry rate, consumer errors, database conditional-write failures, and replay duration. A low client latency metric alone is misleading; it can improve while the durable store falls further behind.

Conclusion

Write-through trades some write latency for a straightforward durability promise. Write-back trades that simplicity for throughput and lower request latency, but only works safely when buffering, replay, ordering, idempotency, and backpressure are designed as first-class parts of the system. Start with a database commit and cache invalidation for most APIs, then introduce write-through or write-back only when their specific trade-off solves a measured problem.

References

  1. AWS: Database Caching Strategies Using Redis
  2. Redis: Cache Consistency Strategies
  3. Microsoft: Cache-Aside Pattern

YouTube Videos

  1. How Does Caching on the Backend Work?
  2. System Design: Caching Patterns

Share this post on:

Next in Series

Continue through the [object Object] with the next recommended article.

Related Posts

Keep Learning with New Posts

Subscribe through RSS and follow the project to get new series updates.

Was this guide helpful?

Share detailed feedback

Previous Post
Cache Eviction Policies: LRU, LFU, FIFO and TTL
Next Post
Cache-Aside Pattern: Read and Write Flows Explained