Stack Overflow (Q&A platform) — LLD
Problem statement
Design the domain model for a Q&A site: users ask questions with tags, others answer, comment, vote; the asker accepts an answer; reputation and badges are awarded by rules; questions can be closed/reopened by privileged users; search by tag/keyword.
Requirements
Functional
Post question (title, body, tags), answers, comments on both.
Upvote/downvote questions and answers (one vote per user per post, changeable).
Accept an answer; edit history; close/reopen/delete with privileges.
Reputation rules and badges; notifications on activity.
Search by keywords and tags; sort by votes/recency.
Non-functional
Vote counts consistent (no double voting).
Reputation rules extensible (strategy); privileges derived from reputation.
Clean inheritance: Question and Answer share Post behaviour.
API
Question ask(userId, title, body, tags)
Answer answer(userId, questionId, body)
Comment comment(userId, postId, text)
void vote(userId, postId, VoteType) // idempotent per (user, post); flips if changed
void accept(askerId, answerId)
void close(userId, questionId, reason) // requires privilege
List<Question> search(query, tags, sort)
Data model
User(id, name, reputation, badges,Privilegesderived).Post(abstract: id, author, body, createdAt,List<Comment>,Map<userId, VoteType>votes, score,EditHistory) →Question(title, tags,List<Answer>, acceptedAnswer, status: OPEN|CLOSED|DELETED, views),Answer(question, isAccepted).Comment(author, text, createdAt),Tag(name, description).Vote(user, post, type);ReputationRule(strategy):onUpvote,onDownvote,onAccept;Badge,BadgeRule.SearchIndex(tag → questions, keyword inverted index).NotificationService(observer on post events).
Design
- Post hierarchy: shared voting/commenting/editing in
Post;QuestionandAnsweradd specifics — classic inheritance question. - Voting:
votesmap keyed by user;vote()removes the previous vote's effect and applies the new one, so score = upvotes − downvotes stays consistent; a user cannot vote on their own post. - Reputation engine: post events (
Upvoted,Downvoted,Accepted) go toReputationService, which appliesReputationRules (+10 upvote on answer, +5 on question, +15 accepted, −2 downvote received, −1 for downvoting) andBadgeRules; reputation floor 1; daily cap. - Privileges computed from reputation thresholds (
canClose ≥ 3000,canEditOthers ≥ 2000); checked in service methods. - Search: tag index + simple inverted index over title/body; sort strategies (votes, newest, active).
- Observer: subscribers (question author, answerers) notified on new answers/comments/accepts.
Class diagram
Deep dives
Vote change semantics: upvote → downvote must undo +1 and apply −1 (net −2) and adjust reputation both ways.
Accepting: only the asker; accepting a new answer un-accepts the old one; reputation moves accordingly.
Edit history as immutable versions (memento-like) enables rollback and 'edited by' attribution.
Closing keeps the question readable, blocks new answers; deletion soft-deletes.
Bottlenecks & mitigations
Linear search over questions → inverted index / search engine (see e-commerce search HLD).
Vote storms on a hot post → per-post lock or DB row; counts cached.
Reputation recalculation on vote reversal → event-sourced reputation ledger for auditability.
Failure modes
- Double vote → map keyed by user makes it idempotent.
- Accept after question closed → allowed (SO allows); document the rule.
- User deleted → posts remain, attributed to 'deleted user'.
- Reputation below 1 → floor.
Code skeleton
import time
from collections import defaultdict
class User:
def __init__(self, uid): self.id, self.rep, self.badges = uid, 1, set()
def can(self, action):
return {"close": 3000, "edit_others": 2000, "vote": 15}.get(action, 0) <= self.rep
class Post:
def __init__(self, author, body): self.author, self.body, self.votes, self.comments, self.ts = author, body, {}, [], time.time()
def score(self): return sum(self.votes.values())
def vote(self, user, v, rep):
if user is self.author: raise Exception("cannot vote on own post")
if not user.can("vote"): raise Exception("need 15 reputation to vote")
old = self.votes.get(user.id, 0)
if old == v: return
self.votes[user.id] = v
rep.on_vote(self, old, v, user)
class Question(Post):
def __init__(self, author, title, body, tags):
super().__init__(author, body); self.title, self.tags, self.answers, self.accepted, self.status = title, set(tags), [], None, "OPEN"
def accept(self, asker, ans, rep):
if asker is not self.author: raise Exception("only asker can accept")
if self.accepted: rep.on_accept(self.accepted, -1)
self.accepted = ans; rep.on_accept(ans, +1)
class Answer(Post):
def __init__(self, author, question, body): super().__init__(author, body); self.question = question
class ReputationService:
UP = {"Question": 5, "Answer": 10}
def on_vote(self, post, old, new, voter):
kind = type(post).__name__
def effect(v): return self.UP[kind] if v == 1 else (-2 if v == -1 else 0)
post.author.rep = max(1, post.author.rep - effect(old) + effect(new))
voter.rep += (-1 if new == -1 else 0) - (-1 if old == -1 else 0)
def on_accept(self, ans, sign): ans.author.rep = max(1, ans.author.rep + 15 * sign)
class StackOverflow:
def __init__(self): self.questions, self.by_tag, self.rep = [], defaultdict(list), ReputationService()
def ask(self, user, title, body, tags):
q = Question(user, title, body, tags); self.questions.append(q)
for t in q.tags: self.by_tag[t].append(q)
return q
def answer(self, user, q, body):
if q.status != "OPEN": raise Exception("question closed")
a = Answer(user, q, body); q.answers.append(a); return a
def search(self, text=None, tag=None, sort="votes"):
pool = self.by_tag[tag] if tag else self.questions
res = [q for q in pool if not text or text.lower() in (q.title + q.body).lower()]
key = {"votes": lambda q: -q.score(), "newest": lambda q: -q.ts}[sort]
return sorted(res, key=key)
Follow-up questions interviewers ask
Add bounties with expiry.
Add duplicate-question detection.
How would you scale reads (feed of newest questions) — see news feed HLD.