Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
A movie reviews aggregator does something deceptively simple: it collects opinions about films from many sources and shows users a single, trustworthy score. The real engineering challenge is not displaying reviews but deciding how to weigh them. A professional critic on Rotten Tomatoes and a casual user on IMDb are both valid voices, but treating them equally produces misleading scores. The system must aggregate intelligently, not just average blindly.
Aggregate reviews from multiple sources: The system crawls reviews from external platforms like Rotten Tomatoes, IMDb, and Metacritic, then normalizes them into a unified format. This is the foundational capability: without multi-source aggregation, users would just visit each site individually.
User-submitted reviews and ratings: Authenticated users submit their own text reviews and numeric ratings (1-10 scale). User reviews are first-class data alongside critic reviews, but they carry different weight in the aggregated score.
Search and filter movies: Users search by title, genre, release year, or rating range. A user looking for "highly-rated sci-fi films from 2024" should get results in under 200ms. This requires a dedicated search index, not just database queries.
Display aggregated scores: Each movie page shows a composite score that blends critic reviews, user reviews, and source credibility. The score updates in near-real-time as new reviews arrive, so a movie's page reflects its current reception.
Recommendation engines, social features (following other reviewers), and movie ticket purchasing. These are valuable extensions but distract from the core problem: building a reliable review aggregation pipeline.
High availability: The system must stay up during peak traffic, which coincides with major movie releases. When a Marvel film opens, millions of users check reviews simultaneously. An outage during opening weekend means lost trust and traffic that never returns.
Low-latency page loads (under 300ms): Movie pages with aggregated scores, review snippets, and filtering must load fast. Users compare multiple movies before deciding what to watch, so every extra 100ms of latency increases bounce rate.
Scalability for traffic spikes: A blockbuster release can spike traffic 10x above normal. The architecture must absorb these spikes without degrading response times for other movies.
Key Insight
Why is eventual consistency acceptable for aggregated scores? Unlike a bank balance where every cent matters, movie scores are inherently approximate. No user notices if a score says 7.3 versus 7.4 during a brief sync delay. This relaxation lets us cache aggressively and batch score recalculations, which is the architectural foundation for handling traffic spikes.
The numbers for a movie reviews aggregator reveal a read-heavy system with periodic write spikes. Most users browse and compare movies without writing reviews, but when a blockbuster opens, both reads and writes spike simultaneously. Designing for the average means the system collapses exactly when it matters most.
10 million registered users with roughly 1 million daily active users. Each active user averages 10 page views per day (searching, comparing, reading reviews), producing 10 million page views daily.
Average read QPS: 10M page views / 86,400 seconds = roughly 115 reads per second. This is modest for a single service.
Peak read QPS during releases: Traffic spikes 5x during major releases, reaching 500-600 reads per second. This is still manageable with proper caching.
Review submissions: About 1 million reviews per day across all sources (user submissions plus crawled reviews). Average write QPS: roughly 12 writes per second, peaking at 50-60 during blockbuster openings.
Read-to-write ratio: roughly 10:1. This tells us the architecture must be heavily optimized for reads. Caching becomes essential, not optional.
Review storage: Each review averages 1KB (text content, rating, metadata, source info). At 1 million reviews per day: 1M x 1KB = 1GB per day, roughly 365GB per year. This is modest and fits comfortably in a single MongoDB replica set.
Movie metadata: With 500K movies in the catalog, each with 2-3KB of metadata (title, genre, year, cast, poster URL, aggregated scores): 500K x 3KB = 1.5GB total. This fits entirely in memory, making PostgreSQL with connection pooling more than sufficient.
Search index: Elasticsearch indexes review text and movie metadata for full-text search. The index is roughly 2x the raw data due to inverted index overhead: about 750GB after one year, requiring a small Elasticsearch cluster (3-5 nodes).
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
Post review: post/movie_id
POST /v1/reviews
Headers: Authorization: Bearer <token>
Body: {
movie_id: string,
rating: number (1-10),
text: string,
spoiler: boolean
}
Response: 201 Created {
review_id: string,
status: "pending_moderation"
}
The response returns "pending_moderation" rather than "published" because new reviews pass through spam detection and content moderation before appearing publicly. This protects the aggregated score from being manipulated by bot-submitted reviews. The client can poll or subscribe to a webhook for status updates.
GET /v1/movies/:movie_id
Response: 200 OK {
movie_id: string,
title: string,
year: number,
genre: [string],
aggregated_score: {
overall: number,
critic_score: number,
user_score: number,
total_reviews: number
},
recent_reviews: [{
source: string,
author: string,
rating: number,
snippet: string
}]
}
The response separates critic_score and user_score so the client can display both. The overall score is a weighted blend, not a simple average. Recent reviews are included inline to avoid a second round-trip for the most common use case: landing on a movie page and scanning top reviews.
GET /v1/search/movies?q=inception&genre=sci-fi&min_rating=7&page=1
Response: 200 OK {
results: [{
movie_id: string,
title: string,
year: number,
aggregated_score: number,
review_count: number
}],
total: number,
page: number,
per_page: number
}
Search supports full-text query on title, filtering by genre, year, and minimum rating, and cursor-based pagination. The response includes the aggregated score so users can compare movies directly from search results without clicking into each page.
A movie reviews aggregator does something deceptively simple: it collects opinions about films from many sources and shows users a single, trustworthy score. The real engineering challenge is not displaying reviews but deciding how to weigh them. A professional critic on Rotten Tomatoes and a casual user on IMDb are both valid voices, but treating them equally produces misleading scores. The system must aggregate intelligently, not just average blindly.
Aggregate reviews from multiple sources: The system crawls reviews from external platforms like Rotten Tomatoes, IMDb, and Metacritic, then normalizes them into a unified format. This is the foundational capability: without multi-source aggregation, users would just visit each site individually.
User-submitted reviews and ratings: Authenticated users submit their own text reviews and numeric ratings (1-10 scale). User reviews are first-class data alongside critic reviews, but they carry different weight in the aggregated score.
Search and filter movies: Users search by title, genre, release year, or rating range. A user looking for "highly-rated sci-fi films from 2024" should get results in under 200ms. This requires a dedicated search index, not just database queries.
Display aggregated scores: Each movie page shows a composite score that blends critic reviews, user reviews, and source credibility. The score updates in near-real-time as new reviews arrive, so a movie's page reflects its current reception.
Recommendation engines, social features (following other reviewers), and movie ticket purchasing. These are valuable extensions but distract from the core problem: building a reliable review aggregation pipeline.
High availability: The system must stay up during peak traffic, which coincides with major movie releases. When a Marvel film opens, millions of users check reviews simultaneously. An outage during opening weekend means lost trust and traffic that never returns.
Low-latency page loads (under 300ms): Movie pages with aggregated scores, review snippets, and filtering must load fast. Users compare multiple movies before deciding what to watch, so every extra 100ms of latency increases bounce rate.
Scalability for traffic spikes: A blockbuster release can spike traffic 10x above normal. The architecture must absorb these spikes without degrading response times for other movies.
Key Insight
Why is eventual consistency acceptable for aggregated scores? Unlike a bank balance where every cent matters, movie scores are inherently approximate. No user notices if a score says 7.3 versus 7.4 during a brief sync delay. This relaxation lets us cache aggressively and batch score recalculations, which is the architectural foundation for handling traffic spikes.
The numbers for a movie reviews aggregator reveal a read-heavy system with periodic write spikes. Most users browse and compare movies without writing reviews, but when a blockbuster opens, both reads and writes spike simultaneously. Designing for the average means the system collapses exactly when it matters most.
10 million registered users with roughly 1 million daily active users. Each active user averages 10 page views per day (searching, comparing, reading reviews), producing 10 million page views daily.
Average read QPS: 10M page views / 86,400 seconds = roughly 115 reads per second. This is modest for a single service.
Peak read QPS during releases: Traffic spikes 5x during major releases, reaching 500-600 reads per second. This is still manageable with proper caching.
Review submissions: About 1 million reviews per day across all sources (user submissions plus crawled reviews). Average write QPS: roughly 12 writes per second, peaking at 50-60 during blockbuster openings.
Read-to-write ratio: roughly 10:1. This tells us the architecture must be heavily optimized for reads. Caching becomes essential, not optional.
Interview Tip
The read-to-write ratio is the single most important number for architecture decisions. At 10:1, every optimization dollar spent on the read path has 10x the impact of the same dollar spent on writes. This is why Redis caching and Elasticsearch indexing are non-negotiable, not nice-to-haves.
Review storage: Each review averages 1KB (text content, rating, metadata, source info). At 1 million reviews per day: 1M x 1KB = 1GB per day, roughly 365GB per year. This is modest and fits comfortably in a single MongoDB replica set.
Movie metadata: With 500K movies in the catalog, each with 2-3KB of metadata (title, genre, year, cast, poster URL, aggregated scores): 500K x 3KB = 1.5GB total. This fits entirely in memory, making PostgreSQL with connection pooling more than sufficient.
Search index: Elasticsearch indexes review text and movie metadata for full-text search. The index is roughly 2x the raw data due to inverted index overhead: about 750GB after one year, requiring a small Elasticsearch cluster (3-5 nodes).
Average response size: A movie page returns scores plus 10-20 review snippets, roughly 15KB. At peak 600 QPS: 600 x 15KB = 9MB/sec outbound, trivial for modern infrastructure.
The API surface reflects the four core operations: submitting reviews, reading movie pages with aggregated scores, searching movies, and managing user accounts. Each endpoint is designed around its specific access pattern.
POST /v1/reviews
Headers: Authorization: Bearer <token>
Body: {
movie_id: string,
rating: number (1-10),
text: string,
spoiler: boolean
}
Response: 201 Created {
review_id: string,
status: "pending_moderation"
}
The response returns "pending_moderation" rather than "published" because new reviews pass through spam detection and content moderation before appearing publicly. This protects the aggregated score from being manipulated by bot-submitted reviews. The client can poll or subscribe to a webhook for status updates.
GET /v1/movies/:movie_id
Response: 200 OK {
movie_id: string,
title: string,
year: number,
genre: [string],
aggregated_score: {
overall: number,
critic_score: number,
user_score: number,
total_reviews: number
},
recent_reviews: [{
source: string,
author: string,
rating: number,
snippet: string
}]
}
The response separates critic_score and user_score so the client can display both. The overall score is a weighted blend, not a simple average. Recent reviews are included inline to avoid a second round-trip for the most common use case: landing on a movie page and scanning top reviews.
GET /v1/search/movies?q=inception&genre=sci-fi&min_rating=7&page=1
Response: 200 OK {
results: [{
movie_id: string,
title: string,
year: number,
aggregated_score: number,
review_count: number
}],
total: number,
page: number,
per_page: number
}
Search supports full-text query on title, filtering by genre, year, and minimum rating, and cursor-based pagination. The response includes the aggregated score so users can compare movies directly from search results without clicking into each page.
GET /v1/movies/:movie_id/reviews?sort=recent&page=2&per_page=20
Response: 200 OK {
reviews: [{
review_id: string,
author: string,
source: string,
rating: number,
text: string,
sentiment: string,
created_at: ISO-8601
}],
total: number,
page: number
}
Reviews can be sorted by recency, rating, or helpfulness. The sentiment field (positive, negative, neutral) comes from the NLP pipeline and lets the client offer "show only positive reviews" filtering without another API call.
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
partitition by user id hash
A user requests a movie page. The API Gateway routes the request to the Movie Service. The Movie Service checks Redis first. On a cache hit (90% of the time), the assembled response returns immediately in under 50ms. On a cache miss, the Movie Service queries PostgreSQL for movie metadata and MongoDB for recent reviews, assembles the response, caches it in Redis, and returns it. This two-tier lookup adds 100-200ms but only happens for 10% of requests.
A user submits a review. The API Gateway authenticates the request via the Auth Service, then routes to the Review Service. The Review Service validates the input, runs spam detection, and stores the review in MongoDB with status "pending_moderation." A moderation worker (automated or human) processes the queue. Once approved, the review status changes to "published," triggering two downstream events: the Aggregation Worker recalculates the movie's score, and an Elasticsearch indexer updates the search index.
Background crawlers periodically fetch reviews from external APIs (IMDb, Rotten Tomatoes, Metacritic). Each crawled batch passes through the Deduplication Service, which checks the dedup_hash against existing reviews. New reviews are stored in MongoDB and fed into the same Aggregation Worker pipeline. The Aggregation Worker recalculates the weighted score for any movie that received new reviews and updates the aggregated_score column in PostgreSQL.
Two request flows dominate the system: users searching for and viewing movie reviews (read), and reviews entering the system from users or crawlers (write). Walking through each step reveals where latency hides and where failures propagate.
Movie page read flow with cache hit and miss paths
Step 1: The user types a search query (such as "Inception sci-fi"). The client sends GET /v1/search/movies with query parameters to the API Gateway.
Step 2: The API Gateway routes to the Search Service. The Search Service queries Elasticsearch with the text query and any filters (genre, year, rating range). Elasticsearch returns matching movie IDs ranked by relevance, with aggregated scores included in the index.
Step 3: The Search Service returns paginated results. Each result includes the movie title, year, genre, aggregated score, and review count. The user scans scores and selects a movie.
Step 4: The client sends GET /v1/movies/:movie_id. The API Gateway routes to the Movie Service. The Movie Service checks Redis for a cached response.
Step 5 (cache hit): Redis returns the assembled movie page data including metadata, scores, and recent reviews. Total latency: 20-50ms. This is the happy path for 90% of requests.
Step 6 (cache miss): The Movie Service queries PostgreSQL for movie metadata (sub-millisecond, data is in buffer pool) and MongoDB for the top 20 reviews sorted by recency and relevance. It assembles the response, writes it to Redis with a 5-minute TTL, and returns it. Total latency: 150-250ms.
Step 1: An authenticated user submits a review via POST /v1/reviews with their rating, text, and movie_id.
Step 2: The API Gateway validates the auth token via the Auth Service. On success, it routes to the Review Service.
Step 3: The Review Service validates the input (rating in range, text length within limits, movie_id exists). It runs a lightweight spam check (keyword filtering, duplicate detection against recent submissions from the same user).
Step 4: The review is stored in MongoDB with status "pending_moderation." The API returns 201 Created with the review ID.
Step 5: A moderation worker picks up the pending review. Automated moderation checks for profanity, hate speech, and spam patterns. Reviews that pass automated checks are published. Flagged reviews go to a human moderation queue.
Step 6: On publication, two events fire. The Aggregation Worker fetches all published reviews for the movie, recalculates the weighted score, and updates PostgreSQL. The Elasticsearch indexer adds the new review to the search index.
Step 7: The Aggregation Worker invalidates the Redis cache key for the affected movie. The next read request rebuilds the cache with the updated score.
Step 1: A scheduled crawler job activates every 15-30 minutes. It fetches reviews from external APIs since the last sync timestamp.
Step 2: Crawled reviews pass through normalization (rating scale conversion) and deduplication (hash comparison).
Step 3: New reviews are stored in MongoDB and queued for sentiment analysis. The scoring engine recalculates affected movies' scores in batch.
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
movies
movie_id UUID Primary Key
title VARCHAR(255)
year INT
genre VARCHAR(50)[]
director VARCHAR(255)
poster_url TEXT
aggregated_score DECIMAL(3,1)
critic_score DECIMAL(3,1)
user_score DECIMAL(3,1)
total_reviews INT
created_at TIMESTAMP
updated_at TIMESTAMP
users
user_id UUID Primary Key
email VARCHAR(255) UNIQUE
username VARCHAR(50) UNIQUE
password_hash VARCHAR(255)
reputation INT DEFAULT 0
created_at TIMESTAMP
PostgreSQL handles structured data with ACID guarantees. User accounts need strong consistency because authentication decisions must never serve stale data. Movie metadata rarely changes and fits in memory (1.5GB for 500K movies), making PostgreSQL's buffer pool highly effective.
The aggregated_score, critic_score, and user_score columns are denormalized onto the movies table. The alternative, calculating scores on every read by scanning all reviews, is prohibitively expensive when a popular movie has 50,000 reviews. Denormalization trades write-time computation (score recalculation) for read-time simplicity.
reviews collection
_id ObjectId
movie_id String (indexed)
user_id String (nullable, null for crawled)
source String ("user", "imdb", "rotten_tomatoes")
author String
rating Number (1-10, normalized)
text String
sentiment String ("positive", "negative", "neutral")
credibility Number (0.0 - 1.0)
status String ("published", "pending", "rejected")
source_url String (nullable)
dedup_hash String (indexed, unique)
created_at Date
updated_at Date
Why MongoDB instead of PostgreSQL for reviews? Reviews come from diverse sources with varying schemas. An IMDb review has different metadata fields than a Rotten Tomatoes review. MongoDB's flexible schema handles this without ALTER TABLE migrations every time a new source is added. The document model also maps naturally to the API response shape: one document equals one review in the response.
The dedup_hash field is a hash of (movie_id + source + author + normalized text) used to detect duplicate reviews crawled from multiple sources. A unique index on this field prevents storing the same review twice.
Elasticsearch indexes movie metadata and review text for full-text search. The index is a read replica, not the source of truth. When a new movie or review is added to PostgreSQL or MongoDB, an event triggers an index update.
The search index enables queries that would be expensive in PostgreSQL or MongoDB: "find sci-fi movies from 2024 with rating above 7 where reviews mention 'visual effects'." This combines structured filtering (genre, year, rating) with full-text search (review content) in a single query.
Key: movie:{movie_id}
Value: {title, aggregated_score, critic_score, user_score,
recent_reviews}
TTL: 300 seconds (5 minutes)
Redis caches assembled movie page responses. The 5-minute TTL balances freshness against cache hit rate. During normal traffic, the cache absorbs roughly 90% of reads. During spikes, this cache hit rate is what prevents the downstream databases from being overwhelmed.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
The review aggregation engine is where this system gets interesting. Most components (API Gateway, caching, search) are standard infrastructure. The aggregation engine is the core intellectual challenge: how do you combine reviews from different sources with different scales, different credibility levels, and potential duplicates into a single trustworthy score?
The four-stage review aggregation pipeline
The aggregation engine processes reviews through four stages: Crawl, Deduplicate, Analyze, Score. Each stage is a separate worker that can be scaled independently. This is the core architectural insight of the system: review aggregation is a pipeline problem, not a single computation.
Stage 1 - Crawl: External API crawlers fetch reviews from IMDb, Rotten Tomatoes, and Metacritic on a 15-30 minute schedule. Each source has a dedicated crawler that understands the source's API format and rate limits. Crawlers normalize ratings to a 1-10 scale (Rotten Tomatoes uses percentages, IMDb uses 1-10, Metacritic uses 1-100) and output a standardized review document.
Stage 2 - Deduplicate: The deduplication service hashes each review's key fields (movie_id + source + author + normalized text) and checks against the dedup_hash index in MongoDB. Reviews that match an existing hash are discarded. This catches the common case where the same review appears on multiple aggregator sites or is re-crawled in consecutive cycles.
Stage 3 - Analyze: The sentiment analysis service classifies each review as positive, negative, or neutral using an NLP model. This classification serves two purposes: it powers the "show only positive reviews" filter in the API, and it provides a secondary quality signal. Reviews where the text sentiment contradicts the numeric rating (a 9/10 rating with overwhelmingly negative text) are flagged for moderation.
Stage 4 - Score: The scoring engine computes the weighted aggregated score for each movie. The formula is: sum(rating x credibility) / sum(credibility) across all published reviews. Critic reviews from established sources carry higher credibility (1.0) than user reviews (0.3-0.8), giving professional critics proportionally more influence on the final score.