</> MAANG.io
system design · 301

Advanced

Master system design interviews with scalable architecture patterns, distributed systems, and real-world design challenges.

0/480 solved 0% complete

Section 8: Forty Crossings and One Lost User - Deep Dive: Freshness, Flapping & Notification Semantics

Executive Summary: Section 7 got the message to four channels. This section makes sure the human on the other end sees "Maya is nearby" exactly once, while the nearby list behind that alert stays correct on every move. We follow Nina along the 5 km boundary to show how the naive distance < 5km rule becomes a notification storm, then build the mechanism that fixes it: an enter/exit hysteresis band with a per-pair cooldown on the alert, expressed as a state machine. We then handle the three semantics candidates skip entirely: what an expired location means, how to order points from a device clock you cannot trust, and how to stop a phone and a watch from double-notifying one friendship. The reusable lesson: in a proximity product the distance computation is a one-liner, and the state machine is the feature.

Nina is cycling along a road that runs, with real geographic cruelty, almost exactly along the 5 km boundary from Raj in Dolores Park. Her phone is on the default cadence — a point every thirty seconds, so a hundred and twenty points over an hour of riding. Her distance to Raj wobbles across the 5 km line roughly forty times and sits just inside it for most of the ride, so under the obvious rule — compute haversine, notify on every update where the distance is under 5 km — Raj's phone buzzes on nearly every one of those hundred and twenty updates about a friend who never actually arrived anywhere.

What happens next is not a latency regression. Raj turns the feature off, and a user who turns a location feature off does not come back. This is the single most common correctness bug in this design, and it is invisible in every architecture diagram, because nothing is wrong with the architecture. The bug lives in the semantics.

Key Insight: Notification correctness is not "did we compute the distance right". It is "does the event we emitted correspond to something a human would describe as having happened". Those are different questions, and only the second one has a state machine in the answer.

Hysteresis: Giving the Boundary Width

The fix is the oldest trick in control systems: separate the threshold that turns something on from the threshold that turns it off. Fire "arrived nearby" when the distance for a pair crosses below 5 km. Consider that pair departed only when the distance rises above 6 km. The kilometre between the two is a dead band where no transition happens at all, which means Nina's wobble — a few hundred metres either side of 5 km — produces no arrival after the first one, because she never leaves the band.

Hysteresis alone is not quite enough. If Nina rides a kilometre inland for lunch and comes back, she crosses out at 6 km and in again at 5 km, legitimately, and Raj gets a second alert for the same friend in the same half hour. So a per-pair cooldown sits on top: never alert the same pair more than once per hour. The state lives exactly where Section 5 put it — a pair:{a}:{b} record holding {state, last_notified_at} — and it is the only per-pair data in the system, which is why it is bounded by the number of pairs that are actually near each other rather than by the size of the friend graph.

Be precise about what that cooldown suppresses, because getting it wrong is a worse bug than the flapping it prevents. The WebSocket event stream carries every genuine state transition — friend_nearby on an enter, friend_moved to keep the distance label live between arrival and departure, friend_departed on an exit — so Raj's nearby list is always correct. The cooldown gates only the user-visible alert: the banner, the buzz, and the offline push of Section 11. Suppress the event as well and a friend who legitimately re-enters forty minutes after leaving never reappears on Raj's screen at all — missing from a list whose entire job is to say who is nearby.

The arithmetic of Nina's hour makes the layering concrete:

Rule Mechanism Alerts in Nina's hour Verdict
distance < 5km Stateless predicate per update ~120 Rejected — this is the bug
Enter 5 km, exit 6 km Per-pair state, dead band 1 on the boundary ride, 2 once she rides in for lunch and back Better, still re-announces a round trip
Band plus one-hour cooldown State plus last_notified_at 1 Shipped

Rejected alternative: the stateless rule, kept because it needs no storage and no per-pair bookkeeping. Flip trigger: the numbers 5 km, 6 km and one hour are product dials, not architecture — if Section 11's notification-per-pair rate dashboard (the flapping canary) climbs, widen the band or lengthen the cooldown without touching a line of the fan-out path. The same machinery is reused wholesale when geofenced places arrive in Section 12, at which point the band widths become per-geofence rather than global.

The per-pair state machine: Near, Away, and two distinct presence-unknown states that decide whether a return fires an alert

Quick check before moving on:

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.