tinder-req.excalidraw


tinder-hld.excalidraw


tinder-deep-dive.excalidraw


1. High-Level Design

Profile Service

Responsible for:

  • Store preferences
  • Fetch candidate profiles

Swipe Service

Responsible for:

  • Store swipes
  • Detect matches
  • Send notifications

Database Choice

Profile DB

Can use:

  • PostgreSQL
  • MySQL

Reason:

  • Moderate traffic
  • Relational data

Swipe DB

Heavy write workload. Options:

Cassandra

Pros:

  • Optimized for writes
  • Handles 100K+ writes/sec

PostgreSQL

Pros:

  • Strong ACID guarantees
  • Easier consistency handling

Trade-off:

  • More nodes required

Match Detection

When User B swipes right:

1. Save swipe
2. Check inverse swipe
3. If exists:
      Create Match
      Notify both users

Notifications: iOS - Apple Push Notification Service (APNS) Android - Firebase Cloud Messaging (FCM)


Deep Dive #1 β€” Consistency

Problem

Both users swipe right simultaneously. A likes B B likes A With eventual consistency:

A cannot see B's swipe
B cannot see A's swipe

β†’ Match missed.


Solution 1

Accept eventual consistency. Run reconciliation job:

Hourly Job
   ↓
Find missed matches
   ↓
Send notifications

Simple but delayed notifications


Solution 2 (Redis)

Use Redis for match lookup.

Redis Key:
userA:userB

Workflow:

Write swipe
Check inverse swipe atomically
Create match

Why?

  • Redis is single-threaded.
  • Supports atomic operations.

Redis provides atomic operations on a single-threaded execution model. When a swipe occurs, I can atomically check whether the inverse swipe already exists and create a match without race conditions.


Solution 3 (PostgreSQL)

Store both decisions in same row.

User1
User2
User1Decision
User2Decision

Postgres row locking + ACID guarantees consistency.

Instead of storing two separate swipe records, I store both users’ decisions in a single row. PostgreSQL row-level locking and ACID transactions guarantee that concurrent updates are serialized, preventing missed matches.


Deep Dive #2 β€” Fast Stack Loading

How do we show Tinder recommendations in < 300ms when finding nearby compatible users is an expensive query?

BEFORE

SELECT *
FROM profiles
WHERE age BETWEEN 25 AND 30
AND gender = 'Female'
AND distance(currentLocation, profileLocation) < 20km

Current Problem

Query:

Find nearby users
Filter by preferences

Expensive because:

  • Geospatial queries
  • Latitude/Longitude search

Solution A: Precompute Stack

  • This is the same idea used by: Facebook Feed Instagram Feed LinkedIn Feed
  • Nightly job: Cron Job -> Generate recommendations -> Store in Cache
  • User request: Cache Hit β†’ Return immediately
  • Pros: Very fast
  • Cons: somthing has changed β†’ if someone from your stack cache deleted their acc | change preferences | change in location
  • How To Fix Cache Invalidation:
    • Whenever something important changes: Location changedPreference changedProfile deleted
    • Invalidate cache.
    • But now Problem Still Exists that we don’t have the new stack cache.

Solution B: Geospatial Index

Option 1: PostgreSQL + PostGIS

Supports: Location-based indexing Now query becomes: Find users in nearby cells instead of: Scan millions of rows but β‡’ You still filter on age, gender, interests, etc.

Option 2: Elasticsearch

Elasticsearch Profile DB β†’ CDC β†’ Elasticsearch Benefits:

  • Fast geospatial search
  • Fast filtering

Deep Dive #3 β€” Avoid Repeat Profiles

Simple Solution

Get recommendation stack 
Get previous swipes from swipe DB
Remove duplicates
Return result

Works but expensive.

Better Solution

Maintain cache:

User
  β†’
Set of Swiped Users

Example:

User123
β†’ {User5, User9, User21}

Fast lookup.

10M users Γ— 100 swipes/day
= 1B swipes/day
 
1B swipes Γ— 100 bytes = 100 GB/day
100 GB/day Γ— 365 days
= 36,500 GB
= 36.5 TB Storage Per Year

Bloom Filter Optimization

Why? Storage becomes huge:

10M users
100 swipes/day
365 days
β‰ˆ 36.5 TB

Use Bloom Filter:

  • It can say:
    • βœ…Β Definitely NOT present
    • ⚠️ Possibly presentΒ (may be wrong)

Before hitting DB: - Check Bloom Filter - If NOT present β†’ skip DB call to check swipesπŸš€ - If maybe present β†’ query DB