Coding problem · Easy · Design & Data Structures
Read-Your-Writes Replica Router
Easy Practical tasks
Profile pages read from an asynchronous read replica, and users complain that their own edits disappear after saving. Implement the routing rule that gives read-your-writes:
Router(maxLagMs):
write(user, nowMs)— writes always go to the primary; remember the time of the user's latest write and return"primary".read(user, nowMs, replicaLagMs)— the replica has applied every write made at or beforenowMs − replicaLagMs. Return"primary"if the replica is unhealthy (replicaLagMs > maxLagMs) or if it hasn't applied this user's latest write yet; otherwise return"replica".
Times stand in for log positions (Postgres LSN / MySQL GTID) to keep the problem simple. See Read-Your-Writes and Monotonic Reads.
Example 1
Input: ["Router","write","read","read","read","read"] [[5000],["ada",1000],["ada",1100,300],["bob",1100,300],["ada",1400,300],["bob",1500,6000]] Output: [null,"primary","primary","replica","replica","primary"] Explanation: Ada's write at 1000 isn't on the replica at 1100 (it has data up to 800); Bob never wrote; at 1400 the replica has data up to 1100; at 1500 the replica is 6 s behind, beyond the 5 s limit.
Hints
Hint 1
Track the latest write time per user.
Hint 2
The replica contains a write made at time t if t ≤ nowMs − replicaLagMs.
Write and run your solution
Loading the editor… (JavaScript is required to run code in your browser.)
Solution and explanation
Show the solution
Time O(1) per call · Space O(users)
Keep the latest write time (in production: the primary's WAL position after the write) per user. A replica that is
lag behind has applied everything up to now − lag; if the user's latest write is newer than that, reading from the
replica could show stale data, so route to the primary. Lag beyond the limit means the replica is unhealthy for
everyone. This exact check beats a fixed "sticky for N seconds" window when lag spikes.
Reference solution
class Router {
constructor(maxLagMs) {
this.maxLagMs = maxLagMs;
this.lastWrite = new Map();
}
write(user, nowMs) {
this.lastWrite.set(user, nowMs);
return "primary";
}
read(user, nowMs, replicaLagMs) {
if (replicaLagMs > this.maxLagMs) return "primary";
const last = this.lastWrite.get(user);
if (last !== undefined && last > nowMs - replicaLagMs) return "primary";
return "replica";
}
}
class Router {
private lastWrite = new Map<string, number>();
constructor(private maxLagMs: number) {}
write(user: string, nowMs: number): string {
this.lastWrite.set(user, nowMs);
return "primary";
}
read(user: string, nowMs: number, replicaLagMs: number): string {
if (replicaLagMs > this.maxLagMs) return "primary";
const last = this.lastWrite.get(user);
if (last !== undefined && last > nowMs - replicaLagMs) return "primary";
return "replica";
}
}
class Router:
def __init__(self, max_lag_ms: int):
self.max_lag_ms = max_lag_ms
self.last_write: dict[str, int] = {}
def write(self, user: str, now_ms: int) -> str:
self.last_write[user] = now_ms
return "primary"
def read(self, user: str, now_ms: int, replica_lag_ms: int) -> str:
if replica_lag_ms > self.max_lag_ms:
return "primary"
last = self.last_write.get(user)
if last is not None and last > now_ms - replica_lag_ms:
return "primary"
return "replica"
<?php
class Router
{
private array $lastWrite = [];
public function __construct(private int $maxLagMs)
{
}
public function write(string $user, int $nowMs): string
{
$this->lastWrite[$user] = $nowMs;
return 'primary';
}
public function read(string $user, int $nowMs, int $replicaLagMs): string
{
if ($replicaLagMs > $this->maxLagMs) {
return 'primary';
}
$last = $this->lastWrite[$user] ?? null;
if ($last !== null && $last > $nowMs - $replicaLagMs) {
return 'primary';
}
return 'replica';
}
}
class Router {
private final int maxLagMs;
private final Map<String, Integer> lastWrite = new HashMap<>();
public Router(int maxLagMs) { this.maxLagMs = maxLagMs; }
public String write(String user, int nowMs) {
lastWrite.put(user, nowMs);
return "primary";
}
public String read(String user, int nowMs, int replicaLagMs) {
if (replicaLagMs > maxLagMs) return "primary";
Integer last = lastWrite.get(user);
if (last != null && last > nowMs - replicaLagMs) return "primary";
return "replica";
}
}
Common mistakes
- Comparing with
≥instead of>at the boundary. - Forgetting users who never wrote can always use a healthy replica.
- Ignoring an unhealthy replica.
Follow-up questions
- Store positions server-side so read-your-writes works across devices.
- Add monotonic reads: remember the newest position each user has seen.
More Design & Data Structures problems
- LRU Cache · Medium
- Token Bucket Rate Limiter · Medium
- Key-Value Store with TTL · Medium
- URL Shortener Codes (Base62) · Easy
- Build an EventEmitter · Medium
- Row Lock Manager with Deadlock Detection · Hard