01What drives who shows up in my stack — preferences, distance, or both?
Users set preferences (age range, interests) and a maximum distance; the stack only contains candidates satisfying both.
Free full guide
Design Tinder — a location-based dating app with swipe feeds, mutual-match detection, and recommendation of nearby profiles.
The interview rhythm stays compact, so the page can spend attention on the actual design decisions.
Do not only state requirements. Ask for them. Each card pairs the design constraint with a clarification question you can say out loud before drawing the architecture.
01What drives who shows up in my stack — preferences, distance, or both?
Users set preferences (age range, interests) and a maximum distance; the stack only contains candidates satisfying both.
02Is swiping one-at-a-time, and can a profile ever come back?
Users swipe yes/no one profile at a time, and a swiped profile never reappears — repeat profiles read as broken.
03What happens at the moment of a mutual yes?
Both users get the match notification immediately — the moment the second yes lands, not minutes later.
04Can a user undo a left swipe (rewind)?
Within a short window, yes — and the rewound profile can appear in the stack again.
05What does unmatching do?
Unmatch removes the match and closes the chat as one action — a half-revoked match (chat alive, match gone) is a trust bug.
06A user changes preferences mid-session - what happens to their deck?
New preferences take effect immediately — the very next profile shown must satisfy the updated filters, not the old ones.
Out of scopePhoto upload pipeline · Messaging after a match · Premium features (super swipes, boosts)
01If two people swipe yes on each other at nearly the same instant, is exactly one match guaranteed — never zero, never two?
Exactly one match, detected immediately — never zero, never two, no matter how close the timing.
02What swipe volume are we sizing for?
20M daily active users (DAU) × ~100 swipes ≈ 2B swipes/day — about 23K swipes per second on average.
03How fast must the stack appear?
Under 300 ms.
04How hard is the "never show twice" rule?
Hard: it must hold across sessions, devices, and reinstalls — showing a profile twice is worse than quietly skipping one candidate.
05What location data can other users ever see?
Only a coarse distance bucket ("~5 km away") — raw lat/lng never leaves the backend, in any payload.
Real interviews probe far more than a tidy list. These are the scope questions that separate candidates who interrogate the problem from those who recite it.
Treat every estimate as a pressure that justifies a component: cache, queue, partition, replica, worker pool, or fallback path.
Swipe write rate
20M daily active users (DAU) × ~100 swipes/day2B ÷ 86,400 s ≈ 23K swipes/s average, evenings ×3-5 ≈ 100K/s peak
Swipes need a write-optimized store; the mutual check must stay O(1) per swipe at this rate.
Seen-profile memory
Assume a heavy user swipes ~50K profiles over a lifetimeBloom filter at 1% false positives ≈ 10 bits/entry → 50K × 10 b ≈ 62 KB per user
The entire "never show twice" guard fits in kilobytes per user — the exact swipe history stays on disk.
Deck precompute cost
Assume a deck of ~200 candidates per active user, refreshed when low20M users × 200 IDs × 8 B ≈ 32 GB
Precomputed decks for every active user fit in one cache tier — that is what makes <300 ms feasible.
Why not query live
A dense city can hold roughly 1% of 20M DAU inside one max-distance circle — about 200K users. Each one still needs geo, age, preference, and not-seen filtering.200K candidates × ~1-2 µs each to intersect filters and check the seen list ≈ 200-400 ms per request — the entire 300 ms budget burned before ranking even starts
The feed budget rules out running the live query on the request path. Instead, precompute the deck and top it up in the background.
Match check cost
Every yes-swipe checks the reverse direction1 atomic read-modify-write per swipe ≈ sub-ms in memory
Keeping the pair’s swipe state co-located (one key) is what keeps the check one operation.
Decision example
A hundred thousand swipes a second at peak. The one moment that cannot go wrong is two people saying yes to each other at the same time.
I would store each pair’s swipe state under one key — the two user IDs, smaller first. Then I record the swipe and check for the reverse swipe as one atomic operation. A Lua script in Redis does that read-modify-write in a single step, and the durable copy is written to the swipe store behind it. Decks are precomputed per user and topped up by a background geo query. Each user’s seen profiles live in a Bloom filter, so "never show twice" costs kilobytes, not a history scan.
What I would NOT do: record the swipe and check the reverse in two separate steps. Two simultaneous yes-swipes each miss the other, and the match never fires. That check-then-act gap is the same race that double-sells concert seats — the check and the write must be one operation. I also would not build the feed as a live geo query per request, because that burns the whole 300 ms budget on index lookups.
If Redis becomes the bottleneck, or cluster failover gets too complex, I would move the atomicity into the storage layer. A compound partition key (smaller_id:larger_id) makes both swipes land in one partition. There, a lightweight transaction covers the pair — the database’s own compare-and-set, slower than Redis but built in. It costs more per swipe, but saves one moving system.
One complete picture first, then each path as its own diagram — the write path and the read path carry different traffic and justify different components.
Complete picture
The swipe path is the consistency-critical spine; the feed path is read-optimized and precomputed. Dashed = maintained asynchronously from durable swipes, rebuildable after cache loss.
Path 1
One atomic operation records the swipe and checks the reverse. The durable write follows. If a Redis node is lost, it replays from the durable store and loses at most the last few unflushed swipes — an accepted trade for swipe-path speed.
Path 2
The request path only reads the deck. When it runs low, the background query refills it, excluding seen profiles via the Bloom filter — false positives skip a candidate, never repeat one.
Before optimizing, make the contract inspectable: endpoints, entities, ownership, retries, and state.
POST/profile
req{ age_min, age_max, distance_km, interested_in }
res200 profile
Identity from the session token. Preference changes invalidate the precomputed deck.
GET/feed
res200 User[] (next slice of the deck)
Serves the precomputed deck; when it runs low, a fresh geo+preference query tops it up. Location comes from the session context, not query parameters.
POST/swipe/{target_user_id}
req{ decision: yes | no }
res200 { matched: boolean } — matched:true fires both notifications
The atomic step: record the swipe AND check the reverse swipe as one operation.
Core entities
Useruser_id (PK) · profile · preferences · geo_cell (coarse)
Location is stored as a coarse geo cell for matching; precise coordinates are never served to other clients.
Swipeswiping_user · target_user · decision: yes/no · swiped_at
Written durably to the swipe store; also folded into the swiper’s Bloom filter of seen profiles.
Matchmatch_id (PK) · user_a · user_b · matched_at
Pick one lane for the final third of the interview. Each lane gives you the topic, the interviewer question it should answer, and the failure mode to avoid.
Two users swipe yes on each other in the same 10 ms. Walk the exact operations that produce one match, not zero.
Make it one atomic operation. Store both users' swipe state under a single key, the two IDs sorted smaller first. Record the swipe and check for the reverse swipe in one Redis Lua script. The second swipe always sees the first, so exactly one match fires.
Read-then-write in two steps — both swipes miss each other and the match silently never happens.
How do you exclude 50,000 already-swiped profiles from every deck refill without scanning history?
Use a per-user Bloom filter of every profile they've swiped. At 50K entries that's about 62 KB. Screen each deck candidate against it; a false positive just skips a candidate and never repeats one. A Bloom filter can't unlearn, so keep the last few swipes in a small exact buffer — that's what powers rewind.
Treating Bloom false positives as a bug — skipping one candidate is the designed cost; repeating one is the failure.
An active user swipes through their whole precomputed deck. What refills it, how fresh is it, and what latency do they see?
Answer every request from the precomputed deck cache. Refills fire asynchronously at a low-water mark, so a background geo-and-preference query rebuilds the 200-candidate deck before the user hits bottom. That precompute is what keeps a live multi-filter query out of the 300 ms path. A preference change drops the deck and refills the same way.
Refilling synchronously on the empty-deck request — the 300 ms budget is gone before the query starts.
Matching needs distance, users must never see coordinates. Where does precision get dropped, and what does the API actually return?
Keep precise coordinates server-side only, for candidate selection. The backend computes the distance and rounds it into a coarse bucket like "~5 km away" before it goes into any response. No payload ever carries lat/lng in any field. Rounding on the client is theater — the payload itself is the leak.
Sending raw lat/lng to clients and rounding in the UI — the payload is the leak.
A very popular profile appears in millions of decks. What hot-spots first, and how do you keep exposure balanced?
The profile's pair keys and swipe partitions hot-spot first, so shard or replicate that state. Then give the profile an exposure budget: a cap on how many live decks it can occupy at once, refilled as swipes drain it. That way one popular profile can't fill every nearby deck and starve everyone else of views.
Ignoring exposure skew — without caps, popular profiles dominate every deck and engagement collapses.
Talk through Tinder out loud and get AI scoring on the explanation.