Learning pathsA
GUIDED PRACTICE

Tinder

Design a location-aware discovery and matching service with profile cards, likes, mutual matches, and messaging handoff.

Interview scope and guarantees

Show a bounded set of nearby eligible profiles, record likes or passes, create a match when likes are mutual, and let matched users start a conversation. Location and recommendations may be stale; match identity and blocks require enforced rules.

Capacity worksheet

Assume 5 million daily users perform 100 swipes: 500 million/day, about 5,787/s average and 28,935/s at 5× peak. A 100-byte swipe record adds 50 GB/day raw. Do not compute pairwise scores for all users; retrieve a small eligible candidate set first.

Concrete API contract

Contract / pseudocode
GET /recommendations?cursor=...
POST /swipes {targetId,decision,requestId}
GET /matches?cursor=...
POST /blocks {targetId}

Data model and access paths

Contract / pseudocode
swipes(actor_id,target_id,decision,version); UNIQUE(actor_id,target_id)
matches(match_id PK,low_user_id,high_user_id,generation,state,created_at); UNIQUE(low_user_id,high_user_id,generation)
UNIQUE(low_user_id,high_user_id) WHERE state=active
profiles(user_id PK, coarse_cell, preferences, updated_at)
blocks(actor_id,target_id)
pair_state(low_id,high_id,policy_version,match_generation); PK(low_id,high_id)
conversations(id PK,match_id UNIQUE)
exposures(viewer_id,candidate_id,last_seen_at); PK(viewer_id,candidate_id)

Evolve a solution and explain each change

Three architecture decisions for Tinder, including the pressure each introduces.
Scroll to inspect the diagram, or open it at full size.

Figure — Three architecture decisions for Tinder, including the pressure each introduces.

Step 1: Generate candidates

Query nearby eligible profiles and apply user preferences. A spatial match does not establish mutual interest or consent.

Step 2: Persist directed swipes

Store one decision per actor and target; detect the reciprocal like. Two simultaneous likes can each miss the other without coordination.

Step 3: Commit one match

Serialize a canonical pair or use a transactionally checked pair record; notify asynchronously. Retries and stale recommendations still need privacy filtering.

Responsibility overview

Connected responsibilities for Tinder. Trace the authoritative and derived paths separately.
Scroll to inspect the diagram, or open it at full size.

Figure — Connected responsibilities for Tinder. Trace the authoritative and derived paths separately.

Worked end-to-end scenario

User A likes B while B likes A from another region. Canonicalize the unordered pair as (min ID, max ID), route pair decisions to one owner, and record both directed likes. When both likes exist, a unique match row is created once and an outbox schedules notifications. A repeated swipe returns the stored decision. If a block arrives before the notification worker runs, the worker and chat admission must apply the block policy. Discoverability and matching are different paths: a cached profile recommendation is not permission to message someone.

Why these access paths matter

Index candidate profiles by coarse location cells and eligibility filters, then refine by actual distance. Swipe lookup uses actor and target; match uniqueness uses the canonical pair. Hide blocked users during candidate generation and result serving. Exact location exposure is unnecessary for distance filtering; return a product-appropriate approximate distance.

Build the baseline first

Tinder: baseline request paths.
Scroll to inspect the diagram, or open it at full size.
  1. Profile request → eligibility filter → nearby candidates.
  2. Swipe → durable pair state → mutual-like check.
  3. Unique match insert → outbox → notifications.

Evolve the design under load

Tinder: additional scaling and recovery paths.
Scroll to inspect the diagram, or open it at full size.
  1. Location updates → cell index → candidate cache.
  2. Pair-key routing → serialized mutual-like evaluation.
  3. Recommendation workers → precomputed bounded batches.

Defend the hardest decision

A naive implementation writes A likes B, checks B likes A, and can miss a match if both transactions read before the other commits. Route both directions to a canonical pair owner, lock the pair row, or use an idempotent reconciler over durable likes. A unique canonical pair key prevents duplicate matches but does not by itself guarantee that a match is eventually discovered.

Failure and recovery analysis

A profile is recommended just before that user blocks the viewer. Revalidate block and eligibility at interaction time; removing a cached recommendation later is insufficient. If match notification publication fails, the match remains visible from durable state and the outbox retries. Notification failure must not delete the match.

Security and privacy boundary

Protect precise location, enforce age and eligibility policies, and rate-limit automated scraping and swiping.

Interview follow-ups with reasoning

Question: How do you avoid repeatedly showing passed profiles?

Show answer and explanation

Answer: Store exclusions and define whether they expire.

Question: How does a popular profile affect partitioning?

Show answer and explanation

Answer: Separate profile read caching from writes keyed by actor or canonical pair.

Question: What is the location privacy boundary?

Show answer and explanation

Answer: Return coarse distance bands rather than exact coordinates.

Operate and verify the design

Recommendation latency, mutual-like reconciliation lag and duplicate-match attempts.

Commit reciprocal likes concurrently and verify eventual creation of exactly one canonical match.

A second scenario to test transfer

A likes B at 10:00, B likes A at 10:01, and the two requests hit different application workers. Each durable Like becomes visible; exactly one transaction wins creation of a canonical pair key. Both clients receive the same match ID. A retried request returns that identity rather than making a second match or notification.

Two opposite-like requests race. How do you create one match and prevent repeated notifications?

Show answer and explanation

Answer: Lock or route both directed actions through the canonical pair authority. Under that authority, persist the unique actor-target Like, read both directions, and insert the unique match and outbox event when mutual. Independent read-after-write checks can miss both concurrent Likes; a unique match constraint alone only prevents duplicates. Recheck blocks before notification and chat admission.

Compare alternatives

DecisionBenefitTrade-off
Coarse candidate poolFast discoveryStale or duplicate candidates
Exact final eligibilityPrivacy and filtersRead-path checks
Canonical pair keySingle mutual matchPair ordering and retries
Exposure cursorLess repetitionPer-viewer state volume

A design-changing exercise

Both reciprocal checks return absent before either swipe commits. What prevents a missed match?

Show answer and explanation

Answer: A serialized pair decision or transactional retry/reconciliation on the pair is required. Independent read-then-write checks do not guarantee detection.

Design workshop: serialize the pair, not the whole population

Discovery, swipe, match and authorized chat are core; sophisticated recommendation-model training is an extension. Assume discovery p95 under 300 ms, pair decisions under 200 ms within their home region and no chat admitted after a block is authoritative. Exact locations are not returned to users.

The pair key is ordered(low_user_id,high_user_id). Create pair_state with this primary key using an insert-on-conflict, then lock it before changing directed likes. This is important: locking a row that does not yet exist is not a mutual-exclusion mechanism. Both directions use the same authority and transaction.

sql
BEGIN;
INSERT INTO pair_state(low_id,high_id) VALUES (:low,:high)
ON CONFLICT DO NOTHING;
SELECT * FROM pair_state WHERE low_id=:low AND high_id=:high FOR UPDATE;
-- Verify block/account state under the chosen pair policy.
-- Persist the direction's Like or Pass and stable request result.
-- If both directions now Like, insert one unique match and its outbox.
COMMIT;

If A and B like concurrently, the first holder persists one Like; the second sees it after acquiring the pair lock and creates the match. A unique match row alone prevents duplicate matches but cannot make independent read-before-commit checks discover a missing one. A reconciler scanning durable pair changes is still useful repair, not a substitute for explaining the chosen concurrency rule.

Concurrent likes under one pair lock. User A to Pair authority: Like B; create/lock canonical pair; User B to Pair authority: Like A; wait for same pair lock; Pair authority to User A: Commit A's Like; no match yet; Pair authority to User B: Read A's committed Like; commit match M; Pair authority to Notifier: Outbox M; retry uses stable match identity; Notifier to Pair authority: Recheck current block policy before notify
Scroll to inspect the diagram, or open it at full size.

Figure — Concurrent likes under one pair lock.

A block command locks the same pair record and increments its policy version. Candidate caches may still contain the profile, but response filtering and chat admission consult the current block/account policy. Notification after a block suppresses content rather than treating an old match event as permission. An unmatch closes the existing match; whether future reciprocal Likes can create a new match is a chosen generation policy. For this scenario require explicit new Likes after unmatch; do not replay old Likes into a new chat.

Discovery uses coarse cells and eligibility filters, fetches a bounded candidate pool, computes exact distance internally and ranks by declared preferences. Store exposure(viewer_id,candidate_id,last_seen_at) or a bounded recent-exposure set to reduce repeats. A signed discovery cursor pins a pool/version; an exact location never belongs in that cursor. Pass records expire only under a stated rediscovery policy.

Match and chat contracts. Swipe / Actor/request -> decision and pair version / Changed payload under same key; Mutual match / Unique canonical pair + match generation / Blocked or disabled account; Start conversation / POST /matches/id/conversation -> stable conversation ID / Caller not an active matched participant; Send message / Conversation/sender/client-message identity / Match closed or current block denies admission
Scroll to inspect the diagram, or open it at full size.

Figure — Match and chat contracts.

Add matches(match_id,pair_key,generation,state) and conversations(match_id UNIQUE,id). Chat storage/streaming can reuse a conversation design, but every send still checks active participant authorization. Do not duplicate all messaging infrastructure in this chapter; clearly state the contract between matching and messaging. Regional writes route to the pair's home shard; failover fences the old writer before another owner accepts commands.

Exercise: Both Likes commit on independent workers without either seeing the other. Is UNIQUE(pair) sufficient?

Show answer and explanation

Answer: No. There may be no attempted match insert. Route or lock the canonical pair as above, or provide explicit eventual reconciliation over both durable Likes. The first solves the primary race; reconciliation repairs missed processing.

Technical references

PostgreSQL transaction isolation and concurrent updates.

11:00Self-guided practice timer
The timer resets when you leave this page. Save your design separately.
Your challenge

Two opposite-like requests race. How do you create one match and prevent repeated notifications?

Your design draft

Clarify assumptions, explain your approach, and test the difficult cases. Save your draft, then compare it with the study notes.

Read study notes

Self-review checklist

Self-guided practice. Automated AI feedback and code execution are not connected.