Reviewed by 6 specialized AI reviewers. Explore the diagram and the full per-section feedback below.
Loading diagram…
Overall this is a credible mid-level design with correct core modeling, reasonable scale estimation, and several good architectural instincts. The candidate demonstrates solid understanding, but the missing API coverage for explicit requirements and the unclear feed/storage design keep it short of a stronger hire signal.
Latency target is explicitly defined
The design states a concrete P95 latency target for fetching potential matches (<200ms), which is the right kind of measurable NFR for this product and gives a clear bar for evaluating whether the system meets user experience expectations.
Consistency model is appropriately scoped
Calling out eventual consistency for profile updates while requiring read-your-own-write behavior for the updating user is a sensible tradeoff for this use case. It balances scalability with a user experience requirement that would otherwise be confusing if users could not immediately see their own changes.
Write throughput assumption is quantified
The design translates the DAU assumption into an estimated swipe rate (~23K/sec average), which is useful for sizing and for checking whether the system can handle the expected write load instead of leaving scalability as a vague statement.
Availability target is missing
Basic NFR coverage should include an explicit availability goal, especially for a consumer app at 20M DAU. Without a target such as 99.9% or 99.99% for core flows like swiping and match delivery, it is hard to judge whether the design is sufficient. Add clear uptime/SLA expectations for the main user actions.
Real-time match requirement is not measurable
The design says match updates should be real time, but does not define what that means operationally. Specify a target such as match notification delivery within a few seconds at P95/P99 so the system can be evaluated against a concrete latency objective.
Average swipe rate alone may understate capacity needs
The stated 23K swipes/sec is an average derived from DAU, but large consumer apps usually see strong diurnal peaks. It would be better to also state an expected peak multiplier or peak QPS target so the scalability requirement is complete and not accidentally under-provisioned.
Core matching nouns are present
The design identifies the main domain entities needed for the stated requirements: Profile for user preferences and distance settings, Swipe for yes/no decisions, and Match for mutual positive swipes. These are the key nouns needed to model the basic dating flow.
Core traffic estimates are present
The section includes the key baseline numbers expected at this level: DAU, average user actions per day, derived total daily operations, and rough read/write QPS. That gives the rest of the design a usable capacity anchor instead of staying purely qualitative.
Storage sizing is methodical
The candidate converts swipe volume into daily and yearly storage using an explicit per-record size assumption, and also separately sizes profile storage. Even if the exact byte assumptions may vary, the approach is correct and in the right form for capacity planning.
Numbers are in the right ballpark for the stated assumptions
For 20M DAU and 100 swipes per user per day, arriving at roughly 23K average write QPS and tens of thousands of read QPS is a reasonable order-of-magnitude estimate. This is appropriate for a mid-level interview and shows the candidate understands how to translate product usage into system load.
Only average QPS is estimated, not peak traffic
Using daily volume divided by 86,400 gives average load, but a consumer app with 20M DAU will have strong diurnal peaks. Sizing only for 23K write QPS and 45K read QPS risks underprovisioning by several times during busy hours. Add a peak multiplier assumption (for example 3x-5x average) and compute peak read/write QPS explicitly.
Read estimate is too lightly justified
The statement that each swipe needs about 2 reads is plausible, but it is not broken down. For example, candidate retrieval, profile fetch, prior-swipe filtering, and mutual-like check may each contribute reads unless cached or precomputed. A short explanation of what those reads represent would make the estimate more defensible.
Match-rate calculation should clarify whether it is event rate or stored data rate
The estimate of about 23 matches/sec is directionally fine, but it would be stronger to state whether this is used for notification throughput, match-record writes, or both. Tying the number to a concrete capacity implication would make the calculation more useful.
Core swipe flow is represented
The routes cover the main user actions in the requirements: fetching a candidate stack, setting matching preferences, and submitting a swipe decision. This gives a workable end-to-end API for the basic matching flow.
Simple action-oriented swipe endpoint
Using a dedicated POST endpoint for swipes is a reasonable choice because a swipe is an event/action rather than a pure resource replacement. Including the decision in the body keeps the API straightforward for clients.
Missing profile creation/update API
One of the functional requirements is that users can create a profile with preferences, but the routes only include preferences and not profile creation or update. Add profile endpoints such as POST /profiles or POST /me/profile and PATCH /me/profile so clients can create and maintain the user profile needed for matching.
No API/message path for match notification
The requirements explicitly say users get a match notification after a mutual swipe, but there is no endpoint or real-time message design for delivering that notification. Add either a push/notification API contract or a WebSocket/SSE event such as match_created with the matched user payload so the client can receive the match result.
Primary entities do not have clear CRUD coverage
For the main entities exposed here, only partial operations are defined. Preferences can be set but not retrieved or updated explicitly, and profiles are missing entirely. A more complete API would include GET /me/preferences and PATCH /me/preferences, plus profile create/read/update routes.
Feed route mixes client-supplied distance with stored preferences
The requirements say users specify a maximum distance as part of their preferences, but the feed endpoint also accepts distance as a query parameter. This creates ambiguity about which value is authoritative and can let clients bypass saved preferences. Prefer GET /feed?lat=...&long=... and use the stored maxDistance server-side, or clearly define override behavior.
Use a user-scoped REST shape for preferences
POST /preferences works, but a more conventional structure would scope preferences to the authenticated user, for example GET /me/preferences and PATCH /me/preferences. This makes ownership clearer and better aligns with REST resource modeling.
Reasonable service separation by domain
Splitting the system into profile, feed, swipe, and notification services is a solid high-level architecture choice. It keeps the main functional flows isolated and makes the end-to-end design easier to scale independently for read-heavy feed traffic versus write-heavy swipe traffic.
Asynchronous match notification path
Using Redis Streams between swipe processing and notification delivery is a good pattern for decoupling user actions from notification fanout. This improves resilience and helps absorb spikes in swipe volume without blocking the swipe API.
Search index for feed filtering
Introducing Elasticsearch for feed retrieval is a sensible choice for preference-based discovery, especially at the stated 20M DAU scale. It is better suited than a primary relational database for filtering candidate profiles by attributes and location-related queries.
Feed query path is inconsistent and unlikely to scale
The feed service is shown querying Postgres to 'show users within max_distance not in match and swipe table', while Elasticsearch is also present for search. Doing distance filtering plus exclusion against swipe/match history directly in Postgres at 20M DAU will become a bottleneck, and the architecture does not clearly define which store is the source for candidate generation. A better design is to use Elasticsearch (or another geo-capable index) for candidate retrieval and use a dedicated exclusion store/cache for already-swiped or matched users.
Duplicate components are not explained as replicas or shards
Profile service, feed service, swipe service, and notification servers each appear twice, but there is no annotation explaining whether these are replicas for HA, horizontal scaling, or accidental duplication. At HLD level this creates ambiguity in the deployment model. Label them clearly as stateless replicas behind the gateway/load balancer or show partitioning strategy if they are sharded.
Notification presence model is incomplete
The design uses POST /online and SSE notification servers, but there is no clear presence store or connection/session management component to track which users are currently connected to which notification server. Without that, routing real-time match notifications reliably is difficult. Add a lightweight presence registry in Redis or similar so notification workers can determine whether to deliver via SSE or fall back to push.
Several components are weakly integrated or orphan-like
The generic 'cache' populated by Cron and read by Feed service is too vague, and Cron -> Elasticsearch is also unclear. These components do not have a well-defined role in the main functional flows, which makes the architecture harder to reason about operationally. Either specify exactly what is being precomputed and why, or remove these boxes to keep the design focused and avoid orphaned infrastructure.
Swipe and match storage boundaries are unclear
Swipe service writes to Cassandra and Postgres, but the ownership of swipe records versus match records is not clearly separated. This can lead to consistency and operational complexity if both databases participate in the same user action. A cleaner HLD would define Cassandra as the high-write swipe store and Postgres as the durable match/profile store, with an explicit async or transactional strategy for match creation.
Cache strategy needs clearer purpose
The design includes both 'Swipe cache' and another generic 'cache', but their contents, eviction policy, and role in reducing database load are not described. Clarifying whether Swipe cache stores prior swipe exclusions, bloom filters, or hot candidate sets would make the scaling story stronger.
Draw your architecture for Dating App / Tinder and get an instant hire/no-hire signal from 6 specialized AI reviewers — free to start.