Interview Setup
Interview Prompt
Design a product review and rating system: users write reviews, see aggregates and helpful-sorted lists, sellers respond, with verified purchase badges and spam controls.
Clarifying Questions (ask before designing)
| Question | Why it matters |
|---|---|
| Read vs write ratio? | 500K aggregate reads/sec vs 200K new reviews/day, requiring aggressive caching of aggregates. |
| Exact aggregate or eventual? | A 5-minute TTL cache lag is acceptable on product page stars. |
| Full-text search in reviews? | Elasticsearch powers search within product reviews. |
| One review per user per product? | UNIQUE constraint ensures single review with update policy rather than duplicate rows. |
Scope
In scope
- Write/read reviews
- Star aggregates + distribution
- Helpful votes
- Verified purchase
- Seller responses
- Spam rate limits
Out of scope (state explicitly)
- Product catalog and inventory service
- Checkout and payment processing
- ML fraud pipeline detail
Functional Requirements
Start by confirming whether verified-purchase badges, media attachments, or fake-review detection are in scope. Ask how fresh aggregate ratings must appear on the product page.
- Write reviews: Text reviews with 1-5 star rating for products/services
- Photo/video reviews: Attach media to reviews
- Helpful votes: Other users vote reviews as "helpful" or "not helpful"
- Verified purchase badge: Mark reviews from actual buyers
- Aggregate ratings: Average rating, rating distribution histogram, total count per product
- Sort/filter reviews: By recency, helpfulness, rating, verified purchase
- Seller responses: Sellers can respond publicly to reviews
- Edit/delete own reviews: Users manage their own reviews
- Anti-abuse: Detect and filter fake/spam reviews via ML + rules
- Review summary: AI-generated summary of common themes across reviews
Non-Functional Requirements
Interviewers focus primarily on read latency on product pages and eventual consistency for star aggregates. Emphasize pre-computed ratings, as performing live COUNT operations across billions of rows per page load is completely infeasible.
- Low Latency: Aggregate ratings in < 50 ms (shown on every product page)
- Eventual Consistency: New review reflected in aggregates within 5 minutes
- Scale: 500M+ products, 5B+ reviews, 200K new reviews/day
- Availability: 99.99% read; 99.9% write
- Fraud Resistant: Detect and suppress fake reviews within hours
Capacity Estimations
Run this math before you size Elasticsearch. Total review count and new reviews per day tell you index growth; product-page read QPS drives Redis aggregate cache sizing.
| Metric | Calculation | Value |
|---|---|---|
| Total products | Given | 500M |
| Total reviews | Given | 5B |
| New reviews / day | Given (assumption documented in value) | 200K |
| Review reads / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 100K |
| Aggregate rating reads / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 500K |
| Total review storage | Given | 5 TB text + 100 TB media |
Architecture Diagram
In the interview: emphasize that star ratings are pre-computed aggregates rather than live queries, and call out the five-minute eventual consistency window proactively.
The architecture cleanly decouples the write and read paths through an asynchronous event bridge. Review submissions persist durably in PostgreSQL before publishing change events to Kafka, which updates running aggregate counters in Redis and synchronizes Elasticsearch for faceted full-text search. Reads query Redis directly for sub-millisecond star ratings on high-traffic product pages. For order validation and fraud screening integrations, see the Order Management System and the Fraud Detection System.
Component Deep Dives
Aggregate Rating: Pre-Computed Architecture
Pre-Computed Aggregates and Event Pipeline
Every product page view depends on sub-50 ms star ratings, which makes live SQL COUNT queries across billions of rows impractical. Review writes persist to PostgreSQL and fan out asynchronously through Kafka, maintaining a five-minute eventual consistency window for public star displays. For caching strategies, review Caching Patterns & Invalidation.
Event Bus Design (Kafka)
Topic: review-created
Partitions: 64
Partition key: product_id (preserves per-product aggregate ordering)
Retention: 7 days
Replication factor: 3, min.insync.replicas: 2
Producer: Review Service after PostgreSQL INSERT (idempotent producer)
Event: { review_id, product_id, user_id, stars, body, media_urls[], timestamp }
Consumer groups:
1. aggregate-worker: INCR product_ratings star_N, recompute avg, and update Redis rating:{product_id} with TTL 300s
2. search-indexer: full-text index review body in Elasticsearch
3. fraud-detector: ML and NLP spam and bot scoring, then flag for moderation
4. notification: alert seller of new reviews
5. ai-summary: weekly batch LLM summary per product (staff tier)
Topic: review-vote-events: async INCR helpful_count (avoids hot row contention on viral reviews)
Sync path: POST review -> INSERT PostgreSQL -> publish event -> return 201 Created
Async path: aggregate update in under 5s; reads hit Redis cache (99% hit rate)
DLQ: review-created-dlq; nightly batch recomputes aggregates from reviews tableBayesian Average
Bayesian Average Rating Calculation
A single five-star review should not outrank a product with ten thousand positive reviews averaging 4.5 stars. Bayesian averaging pulls sparse rating distributions toward a global prior mean to prevent sample-size distortion.
Product A: 1 review, 5.0 avg. Product B: 10K reviews, 4.5 avg. Simple average ranks A higher. That's statistically wrong. Bayesian Average (what Amazon and IMDb use in production): bayesian_avg = (C * m + sum_of_ratings) / (C + total_reviews) C = 25 (minimum reviews required for confidence) m = 3.7 (global platform average rating) Product A: (25 * 3.7 + 5) / (25 + 1) = 3.75 Product B: (25 * 3.7 + 45000) / (25 + 10000) = 4.498 Product B correctly ranks higher. As review volume increases, the Bayesian average smoothly converges to the true sample average.
Fake Review Detection
Multi-Signal ML Fraud Detection
Abuse mitigation combines behavioral telemetry, order verification, text semantics, and graph network analysis to assign review confidence scores before public indexing.
Signals for ML fraud detection: Behavioral: - Account age < 7 days and review posted: suspicious - 50 reviews in 1 day: automated bot behavior - All 5-star or all 1-star patterns: biased review history - Copy-pasted text across unrelated products Purchase verification: - No purchase record: unverified review receiving lower aggregate weight - Purchased and returned same day: suspicious behavior Text analysis (NLP): - Generic text: "Great product! Highly recommended!" without specific product attributes - Sentiment mismatch: enthusiastic text paired with a 1-star rating - Language similarity across reviews submitted from the same IP address Network analysis: - Multiple reviews from the same IP or device identifier for the same product - Review rings: coordinated clusters of accounts mutually reviewing identical products Timing: - Product suddenly receives 500 5-star reviews in 1 hour compared to baseline 5 per day ML Model (XGBoost): Features: all behavioral, textual, network, and timing signals Output: P(fake) probability score P(fake) > 0.8: auto-suppress review and enqueue for human moderation P(fake) between 0.5 and 0.8: flag review with reduced weighting in aggregates P(fake) < 0.5: show normally Suppressed reviews are excluded from aggregate star distributions. Nightly batch reconciliation corrects totals for retroactively suppressed reviews.
API Design
Review and Rating REST APIs
The platform exposes RESTful endpoints for submitting reviews with attached media, paginating filtered reviews with pre-aggregated metadata, and voting on helpfulness.
Write Review
POST /api/v1/products/{product_id}/reviews
{
"rating": 4,
"title": "Great quality, slow shipping",
"body": "The product quality is excellent but shipping took 2 weeks...",
"photos": ["https://cdn.../photo1.jpg"]
}
Response: 201 Created
{ "review_id": "rev-uuid", "status": "published" }Get Reviews for Product
GET /api/v1/products/{product_id}/reviews?sort=helpful&rating=4&verified=true&page=1&limit=10
Response: 200 OK
{
"aggregate": {
"avg_rating": 4.3, "total": 50000,
"distribution": {"5": 30000, "4": 10000, "3": 5000, "2": 3000, "1": 2000}
},
"reviews": [
{ "review_id": "rev-uuid", "user": {"name": "John D.", "verified": true},
"rating": 4, "title": "Great quality", "body": "...",
"helpful_count": 234, "photos": [...], "created_at": "2026-03-10",
"seller_response": {"body": "Thank you for your feedback!", "responded_at": "2026-03-11"} }
]
}Vote Helpful
POST /api/v1/reviews/{review_id}/helpful
{ "helpful": true }
Response: 200 OK
{ "helpful_count": 235 }Common Error Responses
400 Bad Request: invalid input, missing required fields, or malformed JSON payload 401 Unauthorized: missing or invalid authentication token or API key 403 Forbidden: authenticated caller lacks required permissions for this resource 404 Not Found: requested resource ID does not exist 409 Conflict: duplicate write or version conflict, retry with a unique idempotency key 422 Unprocessable Entity: syntactically valid request failed semantic business validation 429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header 500 Internal Error: unexpected server failure, retry safely with an idempotency key 503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff
Data Model
PostgreSQL Schema: Reviews and Aggregates
PostgreSQL serves as the transactional system of record, enforcing uniqueness constraints per user and product alongside pre-aggregated rating counters and seller responses.
CREATE TABLE reviews (
review_id UUID PRIMARY KEY,
product_id VARCHAR(50) NOT NULL,
user_id UUID NOT NULL,
order_id UUID,
rating SMALLINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
title VARCHAR(200),
body TEXT,
photos JSONB,
verified_purchase BOOLEAN DEFAULT FALSE,
helpful_count INT DEFAULT 0,
unhelpful_count INT DEFAULT 0,
status ENUM('published','suppressed','pending_review','deleted') DEFAULT 'published',
fraud_score DECIMAL(4,3),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ,
UNIQUE (product_id, user_id), -- one review per user per product
INDEX idx_product (product_id, status, created_at DESC),
INDEX idx_product_helpful (product_id, status, helpful_count DESC),
INDEX idx_user (user_id, created_at DESC)
);
CREATE TABLE product_ratings (
product_id VARCHAR(50) PRIMARY KEY,
avg_rating DECIMAL(3,2),
total_reviews INT DEFAULT 0,
star_1 INT DEFAULT 0, star_2 INT DEFAULT 0, star_3 INT DEFAULT 0,
star_4 INT DEFAULT 0, star_5 INT DEFAULT 0,
updated_at TIMESTAMPTZ
);
CREATE TABLE review_votes (
user_id UUID NOT NULL,
review_id UUID NOT NULL,
helpful BOOLEAN NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, review_id)
);
CREATE TABLE seller_responses (
response_id UUID PRIMARY KEY,
review_id UUID NOT NULL UNIQUE,
seller_id UUID NOT NULL,
body TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);Redis: Caching and Deduplication
Redis caches hot aggregate rating hashes for sub-millisecond page rendering and tracks 24-hour helpful vote deduplication keys.
# Aggregate ratings cache
rating:{product_id} --> Hash { avg, total, s1, s2, s3, s4, s5 }
TTL: 300
# Helpful vote dedup
voted:{user_id}:{review_id} --> "1"
TTL: 86400
# Review count per user per day (rate limit)
review_rate:{user_id}:{date} --> INT
TTL: 86400Elasticsearch: Full-Text and Faceted Search
Elasticsearch indexes review bodies to power full-text keyword search and filtering by star ratings, verified purchases, and media attachments.
{
"review_id": "rev-uuid", "product_id": "prod-123",
"rating": 4, "title": "Great quality",
"body": "The product quality is excellent but shipping was slow...",
"verified": true, "helpful_count": 234, "created_at": "2026-03-10"
}
// Queries: "battery life" within reviews of product X
// Filter by: rating >= 4, verified only, sort by helpfulFault Tolerance
| Concern | Solution |
|---|---|
| Review written but aggregate not updated |
|
| Duplicate review | UNIQUE(product_id, user_id) constraint prevents duplicates |
| Rating cache stale |
|
| Fake review flood |
|
| Vote spam | One vote per user per review (DB primary key constraint + Redis dedup) |
| Aggregate drift |
|
Additional Considerations
Interview Walkthrough
- 25-minute cut
Skip arch50/arch75 depth unless staff.
- Trust signals: verified-purchase badges and fraud filters (5 min)
- Write path to PostgreSQL with asynchronous aggregate recomputation (6 min)
- Product average rating updated on review events (5 min)
- Helpful-vote ranking using Wilson score interval (5 min)
- Moderation: ML toxicity scoring to route auto-rejects or review queues (4 min)
- Start with trust: verified-purchase badges and one-review-per-order constraints prevent fake reviews from polluting rating signals.
- Explain the write path to PostgreSQL as the transactional source of truth, followed by asynchronous indexing into Elasticsearch for full-text search and faceted filtering.
- Cover aggregate rating updates: recompute product averages upon new review events and cache aggregates in Redis with write invalidation.
- Walk through helpful-vote ranking with Wilson score intervals, balancing positive ratios against total sample size so a 10 out of 10 score outranks 100 out of 200.
- Detail the moderation pipeline: ML toxicity scoring routes clear violations to auto-rejection, sends borderline cases to human moderation, and publishes approved reviews to Elasticsearch.
- Discuss photo reviews stored in S3 with CDN edge delivery, indexing media metadata alongside text in Elasticsearch for media-only review filters.
- Highlight the common pitfall of sorting reviews by raw helpful_count, which disproportionately favors older reviews and buries fresh, relevant product feedback.
Engineering Trade-offs
Review Ordering: Wilson Score Interval
Review ranking balances statistical confidence against recency, resolving the trade-off between raw helpful count sorting and verified satisfaction ratios.
Problem: Sorting by helpful_count biases the display toward older reviews that had months to accumulate votes. Wilson Score balances helpfulness ratio with a statistical confidence interval: wilson = (p + z^2/(2n) - z*sqrt((p(1-p) + z^2/(4n))/n)) / (1 + z^2/n) p = helpful_votes / total_votes n = total_votes z = 1.96 (corresponding to 95% confidence) Review A: 10 out of 10 helpful -> wilson = 0.72 Review B: 100 out of 200 helpful -> wilson = 0.46 Review A correctly ranks higher despite having fewer absolute votes. Production platforms like Reddit and Amazon rely on Wilson score ranking for community feedback.
AI Review Summaries
Synthesizing key sentiment themes from tens of thousands of reviews allows shoppers to grasp common feedback immediately without reading long paginated lists.
Instead of reading 50K reviews, the product page displays: "Customers frequently mention excellent battery life (45%) and lightweight design (38%). Common complaints note slow charging (12%) and screen glare (8%)." Implementation: 1. Weekly batch processing per product for SKUs with more than 50 reviews 2. Extract recurring topics from review text via BERT NER or LDA topic modeling 3. Identify the top 5 positive and top 3 negative sentiment themes 4. Generate a concise customer summary using an LLM 5. Cache results in the product_review_summary table and Redis Cost considerations: Approximately $0.01 per summary across 10M eligible products with over 50 reviews. One-time generation costs ~$100K, followed by weekly refreshes for active products at ~$20K per week.
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.