Marketplace
Walk through a complete marketplace design, from a basic listing service to a geospatial-aware search platform handling 100M DAU with sub-200ms search, location-based discovery, and real-time seller-buyer messaging.
TL;DR
- Keep listing metadata and ownership in PostgreSQL/PostGIS, and upload photo bytes directly to object storage with short-lived pre-signed URLs.
- Publish listing changes through Kafka so a dedicated search indexer can keep Elasticsearch eventually consistent without slowing the write path.
- Use Elasticsearch for one compound search over text, geo-distance, category, price, and
status=active; use cursor pagination for stable result pages. - Store buyer-seller messages in a listing-partitioned relational table at this scale and publish notification events asynchronously.
- Start recommendations with recency and proximity, then add content-based or item-item similarity as interaction data grows. Do not make recommendation inference block search.
- Accept a small search-index lag for listing creation and sold status; make the source of truth and the user-visible consistency guarantee explicit.
Scope and assumptions
This article designs a local-goods marketplace where sellers publish listings and buyers discover nearby items, ask questions, and see listings disappear after they are sold. The core design covers listing metadata, photo upload, geo-text search, messaging, sold-state propagation, and a small recommendation path.
The illustrative interview scenario assumes:
- 100 million daily active users, up to 500 million active listings, about 2,000 listing writes per second, and about 50,000 searches per second at peak.
- Each listing has one seller, a category, a price, a location, and a bounded set of photos. Photos are immutable binary assets; listing metadata is the transactional source of truth.
- Search results may lag the latest listing update by a few seconds, but writes and messages must be durable. A sold listing should stop appearing in normal results as soon as the indexer catches up.
- The buyer usually searches within a radius and may add full-text, category, and price filters. Results are ranked and cursor-paginated rather than exported without a bound.
- Payments, escrow, reputation, ads, disputes, and fraud detection are separate products. All rates and latency targets below are interview requirements, not provider guarantees.
What is an online marketplace?
An online marketplace, like Craigslist or Facebook Marketplace, connects sellers with buyers nearby. The interesting engineering challenge is combining geospatial search, full-text search, and real-time messaging into a single coherent system that stays fast when listings number in the hundreds of millions.
Functional Requirements
Core Requirements
- Sellers can create listings with title, description, price, photos, and location.
- Buyers can browse and search listings filtered by category, price range, and proximity to their location.
- Buyers can message sellers directly about a specific listing.
- Sellers can mark a listing as sold, hiding it from search results.
Below the Line (out of scope)
- Integrated payments and escrow
- Buyer and seller reviews and reputation scores
- Promoted or sponsored listing placement
- Dispute resolution and fraud detection
The hardest part in scope: Geospatial search combined with full-text filtering. A buyer types "vintage guitar" and wants results sorted by distance, not just text relevance. That combination of geo and text is where the design gets interesting, and where naive SQL queries fall apart at scale.
Integrated payments are below the line because they require a licensed payment processor, escrow logic, and regulatory compliance. To add them, integrate a marketplace payment provider such as Stripe Connect and build a separate escrow service that holds funds until the buyer confirms receipt.
Reputation scores are below the line because they require a review submission pipeline and fraud detection to prevent fake reviews. To add them, add a Review entity after a transaction closes and roll up scores asynchronously into seller profiles.
Promoted listings are below the line because they introduce an ad auction mechanism. To add them, build a thin ad-serving layer that injects sponsored results at fixed positions in the search response.
Non-Functional Requirements
Core Requirements
- Availability: 99.9% uptime. Availability over consistency for search (a slightly stale search result is acceptable; a failed search is not).
- Search latency: Search results return in under 200ms p99, including geo-filter and text-match scoring.
- Scale: 100M DAU, 500M total active listings. Peak write rate: ~2,000 new listings per second. Peak search rate: ~50,000 searches per second.
- Message delivery: Messages between buyer and seller delivered within 500ms.
- Durability: Listings and messages are never lost. Photos stored durably in object storage.
Below the Line
- Sub-50ms search via CDN-edge caching of popular query results
- Real-time sold status propagation across all active sessions
Read/write ratio: For every listing created, expect roughly 25 searches that scan that listing. This 25:1 read skew shapes the entire storage and caching strategy. The search path must be fast and horizontally scalable. The write path handles a tiny fraction of the traffic.
Under 200ms search latency means a naive SELECT * FROM listings WHERE ST_DWithin(location, ?, ?) against a 500M-row PostgreSQL table is not viable without spatial indexing. Even with a PostGIS GiST index, filtering 500M rows by geo-box and then by text is slow without a dedicated search engine. The 100M DAU target means the search tier must scale horizontally with no single bottleneck.
30-second answer
Use PostgreSQL/PostGIS as the source of truth for listings, users, and messages, and object storage for photos. A seller uploads photos directly with pre-signed URLs, then the Listing Service writes metadata transactionally and emits a listing event. Search Indexers consume those events and update a denormalized Elasticsearch index that combines BM25 text search, geo-distance, category, price, and active-status filters. The Search Service serves cursor-paginated results from Elasticsearch, while the Messaging Service writes conversation messages to a listing-partitioned table and emits notification events. Mark-as-sold is authoritative in PostgreSQL and becomes visible in search asynchronously within the stated window.
5-minute explanation
Separate the transactional write path from the read-heavy discovery path. PostgreSQL owns listing state, seller ownership, coordinates, and message history. Object storage owns photo bytes, so large uploads do not consume application-server connections. A Listing Service validates ownership and fields, writes the listing, and publishes an event through an outbox or durable queue so search indexing can retry safely.
The search index is a denormalized projection, not the source of truth. Elasticsearch can evaluate geo-distance, text relevance, category, price, and status=active in one query and can scale search independently with shards and replicas. Cursor pagination avoids unstable offsets as new listings arrive. A sold update follows the same event path; the short index lag is an explicit consistency trade-off rather than a hidden failure.
Messaging has a different access pattern and does not need to share the search store. The service derives the seller from the listing and the buyer from authentication, stores messages ordered by listing and time, and publishes notification events asynchronously. Recommendations can begin with recency and proximity, then use browsing events and offline item similarity. The final architecture therefore has one authoritative relational path, one search projection, one binary-object path, and isolated asynchronous consumers.
45-minute interview approach
Use this agenda to keep the discussion focused on the marketplace bottleneck—compound geo-text discovery—while still covering the other user journeys:
- 0-5 minutes — clarify the prompt: Confirm whether this is local goods or a transactional marketplace, the buyer location source, radius behavior, ranking expectations, photo limits, messaging scope, and whether payment is included.
- 5-10 minutes — requirements and estimates: State 500M active listings, 50K peak searches/sec, 2K writes/sec, the 200ms p99 search target, durable listings/messages, and the acceptable search-index lag. Call out the read skew.
- 10-15 minutes — entities and APIs: Define
Listing,Photo,User, andMessage. Sketch pre-signed photo upload, listing creation, compound search with a cursor, message send/history, and sold-state update. - 15-25 minutes — baseline architecture and critical flows: Draw API Gateway, Listing Service, PostgreSQL/PostGIS, object storage, Kafka, Search Indexer, Elasticsearch, and Messaging Service. Walk through create, search, message, and mark-as-sold flows.
- 25-35 minutes — choose the deep dive: Prioritize geo indexing and the geo-text query. If the interviewer chooses another path, compare bounding boxes/geohashes/geo-distance, relational full-text/GIN/Elasticsearch, or recommendation baselines and their scale limits.
- 35-41 minutes — reliability, security, and operations: Cover outbox delivery, reindexing, stale search results, photo cleanup, rate limits, authorization, PII, spam, index lag, and notification retries. Explain what happens when Elasticsearch or Kafka is unavailable.
- 41-45 minutes — trade-offs and close: Re-state PostgreSQL as authoritative, Elasticsearch as a rebuildable projection, the consistency window for sold listings, the fallback recommendation strategy, and how payments or multi-region writes would change the design.
Core Entities
- Listing: The core object. Carries title, description, price, category, status (active/sold), and a geographic coordinate (latitude + longitude). Belongs to exactly one seller.
- Photo: A binary asset attached to a listing. Stored in object storage (S3); the listing record stores only the photo URLs.
- User: The account that creates listings or sends messages. Carries an ID, display name, and an optional saved location for proximity defaults.
- Message: A single message in a conversation between a buyer and a seller about a specific listing. A conversation is implicitly defined by the
(listing_id, buyer_id, seller_id)triple.
Full schema, indexes, and column types are deferred to the data model deep dive. These four entities are sufficient to drive the API design and High-Level Design.
API Design
Start with one endpoint per functional requirement, then evolve where the naive shape needs adjustment.
FR 1: Create a listing
POST /listings
Authorization: Bearer <token>
Body: {
title: string,
description: string,
price_cents: number,
category: string,
location: { lat: number, lng: number },
photo_ids: string[] // pre-uploaded to S3, see note below
}
Response: 201 { listing_id, status: "active" }
Photos are not included in this request body. Embedding binary files in JSON is inefficient and creates timeouts on large images. Instead, clients upload photos directly to S3 via pre-signed URLs (a separate POST /photos/upload-url endpoint returns a short-lived signed URL). Once uploaded, the client passes the resulting photo IDs to this endpoint.
FR 2: Search listings
GET /listings/search
Query: {
q?: string, // full-text query (e.g. "vintage guitar")
lat: number,
lng: number,
radius_km: number, // defaults to 25km
category?: string,
min_price?: number,
max_price?: number,
cursor?: string, // for cursor-based pagination
limit?: number // default 20
}
Response: 200 {
listings: [Listing],
next_cursor: string | null
}
Cursor-based pagination over offset pagination because search results shift as new listings are posted. Offset pagination would show duplicates or skip items; a cursor anchors the result window to a stable position.
FR 3: Send a message to a seller
POST /listings/{listing_id}/messages
Authorization: Bearer <token>
Body: { text: string }
Response: 201 { message_id, conversation_id, sent_at }
The server derives seller_id from the listing, and buyer_id from the auth token. No need to pass either in the body.
FR 4: Get conversation messages
GET /listings/{listing_id}/messages
Authorization: Bearer <token>
Query: { cursor?: string, limit?: number }
Response: 200 {
messages: [Message],
next_cursor: string | null
}
FR 5: Mark a listing as sold
PATCH /listings/{listing_id}
Authorization: Bearer <token>
Body: { status: "sold" }
Response: 200 { listing_id, status: "sold" }
PATCH rather than a dedicated /listings/{id}/sold endpoint because status is a field on the listing. PATCH is idiomatic for partial updates. The server must validate that only the listing owner can change status.
High-Level Design and Critical Flows
1. Sellers can create a listing with photos and location
The write path: seller uploads photos to S3, then submits listing metadata to the Listing Service, which writes to the database.
Components:
- Client: Web or mobile app. Fetches a pre-signed S3 URL, uploads photos directly to S3, then POSTs listing metadata to the API.
- API Gateway: Routes requests, handles auth token validation, and enforces rate limits to prevent listing spam.
- Listing Service: Validates the listing fields, persists the record to PostgreSQL, and publishes a
listing.createdevent to a message queue for async downstream processing (search indexing). - PostgreSQL: Stores listing records with geographic coordinates as a PostGIS
GEOGRAPHYcolumn. This is the source of truth. - S3: Stores raw photo bytes. The Listing Service stores only the photo URLs in PostgreSQL.
Request walkthrough:
- Client calls
POST /photos/upload-urland receives a pre-signed S3 URL (valid for 10 minutes). - Client uploads the photo directly to S3 using the pre-signed URL. S3 returns the photo URL.
- Client calls
POST /listingswith metadata including the photo URLs. - Listing Service validates all fields and writes the listing row to PostgreSQL.
- Listing Service publishes
listing.createdevent to Kafka for downstream processing. - Listing Service returns
201 { listing_id, status: "active" }to the client.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.