How do you design the product review and rating system?
Fast recall
Quick revision
Core answer
Modeling user-generated content with validation rules, denormalization for read performance versus write consistency trade-offs, understanding of eventual consistency, and handling aggregate calculations at scale
Computing average rating on-the-fly from all reviews on every product page load (terrible performance at scale), forgetting verifiedPurchase flag leading to fake reviews, or updating Product's averageRating synchronously in the same transaction causing lock contention
Mention pagination and sorting of reviews by helpfulness or recency, content moderation with profanity filters and spam detection, allowing review edits with version history for transparency, and caching product ratings in Redis for high-traffic products
Interview-ready guide
Interview answer
The Review entity is straightforward at first glance — a review id, the product id, the user id, an integer rating from one to five, an optional text comment, a verified-purchase flag, and created and updated timestamps. I'd validate the rating bounds right at construction, which a Java record makes clean with a compact constructor that throws if the rating is outside one to five.
What lifts this above plain CRUD are two design decisions. The first is the verified-purchase check. Before I accept a review, the ReviewService asks the order repository whether this user actually bought this product, and sets the verifiedPurchase flag accordingly. That single flag is what lets the UI show a 'verified purchase' badge and is a basic defence against fake reviews.
The second is denormalization. The product listing page needs to show an average rating and a review count, and you do not want to recompute an average over thousands of reviews every time someone loads the page. So I store averageRating and reviewCount directly on the Product, and update them whenever a new review comes in. I'd be explicit about the trade-off here, because that's what an interviewer is listening for: denormalizing makes reads fast but introduces a write-time cost and a consistency concern. For ratings, eventual consistency is perfectly acceptable — if the average is a second stale, nobody cares — so it's a good trade.
How do you design the product review and rating system
Direct Answer
The Review entity has fields: reviewId, productId, userId, rating (integer 1 to 5), reviewText (optional comment), verifiedPurchase (boolean indicating if user actually bought the product), createdAt, and updatedAt. Product entity has denormalized fields averageRating and reviewCount for fast display. When a user submits a review, ReviewService validates they purchased the product, saves the Review, then publishes a ReviewCreatedEvent. A background listener recalculates Product's averageRating and reviewCount asynchronously, avoiding locking Product rows on every review submission. This eventual consistency trade-off improves write throughput.
Deep Dive & Architecture
A review has its own identity and rules
Review links a user to a product and stores rating, text, creation time, moderation status, and whether the purchase was verified. Validate the rating range and decide whether one user may keep one active review per product. A database uniqueness rule can enforce that decision even when two requests arrive together.
Eligibility and moderation are separate decisions
The service can ask order history whether the user bought the product, then either allow all reviews with a verifiedPurchase label or restrict reviews to purchasers. Moderation status such as PENDING, PUBLISHED, or REJECTED should be explicit so hidden reviews do not affect the public rating.
The displayed average is derived data
Product pages need average rating and count quickly. A small system can calculate them in SQL; a larger read-heavy system can store a RatingSummary and update it transactionally or from review events. If a review changes or is removed, both sum and count must be adjusted. The source reviews remain the truth, while the summary can be rebuilt if it drifts.
Code Example
record Review(String reviewId, String productId, String userId,
int rating, String reviewText, boolean verifiedPurchase, Instant createdAt) {
Review {
if (rating < 1 || rating > 5) throw new IllegalArgumentException("rating 1..5");
}
}
class ReviewService {
BigDecimal submit(String userId, String productId, int rating, String text) {
boolean verified = orderRepo.hasPurchased(userId, productId); // verified-purchase check
Review review = new Review(UUID.randomUUID().toString(), productId, userId,
rating, text, verified, Instant.now());
reviewRepo.save(review);
return productRepo.recalcAverage(productId); // update denormalized averageRating
}
}
Validation in the record's compact constructor; the verified flag comes from the order history.
Common Mistakes & Pitfalls
- Recomputing the average on every page load — denormalize
averageRating/reviewCountontoProduct. - No verified-purchase signal — opens the door to fake reviews; check order history.
- No rating bounds — validate 1–5 at construction.
- Storing rating as a String/float — use a bounded
int.
Check your understanding
Answer these without looking back at the article.
- Design an online shopping system like Amazon. What are the core classes?
- How do you design the order state machine in the shopping system?
- How do you design the inventory management component?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.