Stack Overflow
Q&A platform with reputation system, voting mechanics, and search ranking. Observer pattern for reputation events, Strategy pattern for search, and State pattern for question lifecycle. Reputation thresholds gate privileged actions like voting and closing.
Key Abstractions
Facade coordinating users, questions, answers, votes, tags, and search
Holds title, body, tags, answers list, vote count, and open/closed state
Response to a question with its own vote count and accepted/rejected status
Author with reputation score that gates privileged actions like voting and closing
Categorization label attached to questions for filtering and trending
Strategy interface for ranking search results by relevance, votes, or recency
Class Diagram
The Key Insight
Stack Overflow looks like a simple CRUD app on the surface. Users post questions, others post answers, people vote. But the real design challenge is the reputation system that ties everything together. Reputation is not just a vanity number. It is an access control mechanism. You need 15 reputation to upvote, 50 to comment, 500 to close questions. These thresholds create a self-moderating community where new users earn privileges by contributing quality content.
The voting mechanics have second-order effects that most people miss. An upvote on your answer gives you +10 reputation. A downvote costs the author -2, but it also costs the voter -1. That asymmetry is intentional. It discourages frivolous downvoting while still allowing the community to signal low-quality content. Good contributions earn privileges, which lets users do more moderation, which improves content quality. It is a virtuous cycle built into the data model.
From a patterns perspective, three patterns carry the weight. Observer propagates reputation changes when votes happen. Strategy makes search ranking pluggable (sort by relevance, votes, or date). State manages the question lifecycle: open questions accept answers, closed questions reject them. No boolean flags, no scattered if-checks.
Requirements
Functional
- Users register and build reputation through contributions
- Post questions with title, body, and tags
- Post answers to open questions
- Upvote and downvote questions and answers, with reputation effects on both voter and author
- Accept an answer (only the question author can do this)
- Close questions (requires sufficient reputation threshold)
- Search questions with pluggable ranking strategies (relevance, votes, date)
Non-Functional
- Reputation thresholds enforce privilege escalation for voting, commenting, and closing
- Prevent double-voting by the same user on the same content
- Closed questions must reject new answers at the state level, not through if-checks scattered everywhere
- Search ranking must be swappable without modifying the search infrastructure
Design Decisions
Why State pattern for question lifecycle instead of a status enum?
A status enum like OPEN/CLOSED means every method that cares about state needs if (status == OPEN) ... else ... checks. The Question class accumulates these checks across addAnswer, close, reopen, and protect operations. State pattern moves the behavior into state objects. OpenState.addAnswer() works normally. ClosedState.addAnswer() throws an exception. The Question delegates to its current state without knowing which state it is in. Adding a "protected" state where only high-rep users can answer means creating one new class, not editing five methods.
Why store reputation as a running total instead of computing it?
You could recompute reputation from the full vote history every time you need it. But reputation checks happen constantly: every vote attempt, every comment attempt, every close vote. Scanning all votes for a user on every action is O(n) in the number of votes they have ever received. A running total updated on each vote is O(1). The tradeoff is keeping the total consistent with the underlying events, but that is straightforward when reputation updates happen in the same transaction as the vote.
Why the -1 penalty for downvoting?
Without a cost, downvoting is free. Serial downvoters can tank someone's reputation with zero risk to themselves. The -1 cost makes voters think twice. Is this answer bad enough that I am willing to spend a reputation point to signal it? This small friction dramatically reduces abuse while preserving the ability to flag genuinely poor content.
Why Strategy for search instead of hardcoded sorting?
Users want different things at different times. Someone debugging a specific error wants relevance-based results. Someone exploring a topic wants the highest-voted answers. A moderator tracking recent activity wants date-sorted results. Hardcoding any single approach means the others require code changes. Strategy pattern lets you swap ranking logic at runtime. The search method filters candidates and delegates ranking to whatever strategy is currently active.
Interview Follow-ups
- "How would you implement bounties?" Add a Bounty object that locks reputation from the poster upfront. When the bounty period ends, award the locked reputation to the best answer. If no answer qualifies, half the bounty returns to the poster and the other half is destroyed.
- "How would you handle spam detection?" Introduce a moderation pipeline. Flag posts that match spam patterns like high link density, repeated content, or rapid posting from low-rep accounts. Use the Observer pattern to notify moderators when flags accumulate past a threshold.
- "What about real-time updates when new answers arrive?" WebSocket connections per question page. When an answer is posted, the observer notifies connected clients. Send a lightweight payload with the answer preview so the UI can show "1 new answer posted" without a full page reload.
- "How would you scale the search?" Extract search into a dedicated service backed by Elasticsearch or similar. Index questions and answers as documents. The SearchStrategy interface stays the same at the API layer, but the implementation queries an external index instead of scanning in-memory data.
45-Minute LLD Playbook
Each phase is what the interviewer expects you to do and say. Concrete steps, not topic hints. Diagrams are what you would sketch on the board.
- 15 min
Clarify reputation, voting, and lifecycle rules
GoalPin down the reputation formula, the privilege thresholds, the double-vote rule, and the open-vs-closed lifecycle. End by asking the diagram-or-code question.
Do & Say- ASK·1Open with the load-bearing framing: Stack Overflow is reputation-gated, not user-gated. Reputation is an access control mechanism. I'll plan around three thresholds: 15 to vote, 50 to comment, 500 to close.
- SAY·2State the reputation effects: Upvote gives +10 to author. Downvote costs -2 to author plus -1 to voter. Accept gives +15 to answerer plus +2 to asker. Confirm those numbers?
- SAY·3Pin the vote semantics: One vote per voter per target. Re-voting on the same question or answer is rejected. Voters can vote on questions and answers separately. The vote action and the reputation effect run as one transaction.
- SAY·4Lock the lifecycle: Question has two states for v1: Open and Closed. Open accepts new answers, Closed rejects them with a permission error. Reopen is allowed (Closed -> Open) for v2. Answers don't have lifecycle states in v1.
- SAY·5Pin the search scope: Three strategies. Relevance scores by keyword match count in title plus body. Vote sorts by vote count desc. Date sorts by creation timestamp desc. All three filter to questions containing at least one query term.
- SAY·6Park out of scope: bounties, spam detection, real-time updates, full-text search via Elasticsearch. Ask: Diagram first or jump to code starting with User and the thresholds?
Interviewer is grading: You name reputation as access control, not as a vanity number, in your opening sentence. You commit to the specific numbers (15, 50, 500, +10, -2, -1, +15, +2) instead of leaving them vague. You name the vote-action-and-reputation-as-one-transaction invariant. You park bounties and spam explicitly.
- 25-10 min
Sketch the reputation flow and state machine
GoalShow the vote -> reputation propagation path and the Question state machine, then name where each pattern sits.
Do & Say- DRAW·1Draw the vote flow left to right: vote(voterId, target, isUpvote) -> voter.canVote check (threshold 15) -> dedup check via voteLog -> target.vote(isUpvote) -> author.addReputation(+10 or -2) -> if downvote, voter.addReputation(-1). Say: Five steps, all serialized. The downvote cost on the voter is the friction that prevents serial abuse.
- DRAW·2Sketch User on the side with reputation (int), canVote, canComment, canCloseQuestion as boolean methods that compare reputation to a threshold constant. Say: Methods, not raw threshold checks at call sites. The thresholds live in one place.
- DRAW·3Sketch the Question state machine: OpenState with addAnswer that appends to question.answers, close that flips state to ClosedState. ClosedState with addAnswer that throws PermissionError, close that's a no-op (idempotent). Say: Question.addAnswer delegates to state.addAnswer. Question doesn't know which state it's in. That's the State pattern doing its job.
- DRAW·4Sketch SearchStrategy column with three implementations. Each filters by at least one query term match, then sorts by its own metric. Say: Filter-then-sort. The filter is shared logic, the sort differs per strategy.
- SAY·5Place the three patterns: Observer on the vote-reputation flow (voter and author both get notified through the same vote call). State on QuestionState. Strategy on SearchStrategy.
Interviewer is grading: You diagram the vote flow as five explicit steps and name the downvote cost on voter as anti-abuse. You verbalize 'Question.addAnswer delegates to state.addAnswer' as the State pattern's load-bearing invariant. You commit to filter-then-sort as the shared structure across all three search strategies.
- 325 min
Code in this sequence (bottom-up)
GoalType code in the order it appears in the reference: thresholds, User, State subclasses, Tag, Answer, Question, search strategies, Platform. Talk while you code.
Do & Say- SAY·1Start with the threshold and reputation constants at module level: VOTE_THRESHOLD = 15, COMMENT_THRESHOLD = 50, CLOSE_THRESHOLD = 500, UPVOTE_REP = 10, DOWNVOTE_REP = -2, ANSWER_ACCEPTED_REP = 15, ACCEPT_BONUS_REP = 2. Say: Constants in one place. When the product team changes them, one edit. (~1 min)
- SAY·2Code User. Constructor sets reputation to 1. add_reputation clamps to max(1, ...). can_vote, can_comment, can_close_question return reputation >= threshold. Say: Reputation starts at 1, never drops below 1. That's not arbitrary, it's the floor for protected actions like asking a question. (~3 min)
- SAY·3Code QuestionState abstract with add_answer(question, answer), close(question), state_name. (~1 min)
- SAY·4Code OpenState: appends to question.answers and flips question.state to ClosedState on close. ClosedState raises PermissionError on add_answer and is a no-op on close. (~2 min)
- SAY·5Say: State subclasses know what to do. Question delegates blindly. Adding ProtectedState for high-rep-only answers is one new class with a permission check. (~1 min)
- SAY·6Code Tag with name and question_count, and an increment method. (~1 min)
- SAY·7Code Answer with answer_id, body, author, votes (int default 0), accepted (bool default False), created_at. vote(is_upvote) mutates votes by +/-1. accept sets accepted to True. score property returns votes + (15 if accepted else 0). (~1 min)
- SAY·8Say: Score is a derived value for sorting. The 15-bonus for accepted matches the answer-author reputation award. Intentional symmetry. (~1 min)
- SAY·9Code Question with question_id, title, body, author, tags, answers list, votes int, state defaulting to OpenState(), created_at. add_answer delegates to self.state.add_answer(self, answer). vote and close mirror the pattern. Say: Question.add_answer is one line: state.add_answer(self, answer). All the open-vs-closed branching lives in the state objects. (~3 min)
- SAY·10Code SearchStrategy abstract with rank(questions, query) -> list. (~1 min)
- SAY·11Code RelevanceStrategy: tokenize query, compute relevance score as sum of term counts in (title + body), filter to non-zero scores, sort desc by score. (~2 min)
- SAY·12Code VoteStrategy (filter by at-least-one term match, sort by votes desc) and DateStrategy (same filter, sort by created_at desc). Say: Three strategies, same filter logic, different sort keys. Shared filter is acceptable duplication for v1. (~2 min)
- SAY·13Code Platform fields: _users, _questions, _tags, _search_strategy default RelevanceStrategy, _vote_log (voter_id -> set of target_ids). Implement register_user (generates id, creates User). (~1 min)
- SAY·14Code post_question: creates tags via _get_or_create_tag, increments counts, creates Question. post_answer: creates Answer, calls question.add_answer (routes through state). (~1 min)
- SAY·15Code vote: voter.can_vote check, dedup via _vote_log, target.vote, author.add_reputation by sign, voter.add_reputation(-1) on downvote. (~2 min)
- SAY·16Code accept_answer: enforce question.author.user_id matches, answer.accept, answer.author.add_reputation(+15), question.author.add_reputation(+2). close_question: user.can_close_question check, question.close. set_search_strategy and search delegate to strategy.rank. (~1 min)
- SAY·17Say: vote is the load-bearing method. Five sequential checks and updates, all in one place. (~1 min)
- SAY·18Walk-through, votes: Alice (rep 100) posts q1. Charlie (rep 1) answers a1. Alice upvotes a1: can_vote yes, dedup yes, a1.vote(True), charlie.rep = 11. Bob (rep 50) upvotes: charlie.rep = 21. (~1 min)
- SAY·19Walk-through, accept and close: Alice accepts a1: accepted=True, charlie.rep = 36, alice.rep = 102. Charlie can now vote (36 >= 15). Alice closes q1 (after a rep boost to 500+): state becomes ClosedState. Bob tries to answer q1: ClosedState.add_answer throws PermissionError. (~1 min)
Interviewer is grading: Code compiles in dependency order. You name the threshold constants at the top instead of inlining magic numbers. You verbalize the 'one line: state.add_answer(self, answer)' invariant when writing Question.add_answer. You catch the downvote-cost-on-voter (-1) explicitly. Mental walk-through with real reputation values before declaring done.
- 45 min
Trade-offs and wrap-up
GoalName two trade-offs, volunteer one extension, close with a one-sentence summary.
Do & Say- SAY·1Trade-off one, State pattern over a status enum: A status enum (OPEN, CLOSED) means every method that cares about state grows an if/elif. add_answer needs it, comment needs it, delete needs it.
- SAY·2Defend State: Branching lives in the state objects. add_answer in OpenState does the work, add_answer in ClosedState throws. Adding ProtectedState for high-rep-only answers is one new class, not five edits.
- SAY·3Trade-off two, running reputation total over recompute-from-history: Recomputing from vote history is O(n) in votes ever received. Reputation checks fire on every vote attempt, every comment, every close. Hot path.
- SAY·4Defend the running total: O(1) read, O(1) update per vote. Cost is updating reputation in the same transaction as the vote, which we already do.
- SAY·5Extension to volunteer, bounties: Bounty object locks reputation from the asker upfront for a fixed window (typically 7 days). When the window closes, award the locked rep to the highest-voted answer.
- SAY·6Defend the no-winner rule: If no answer qualifies, half returns to the asker and half is destroyed. Destruction is intentional, prevents bounty farming.
- WATCH·7Be ready for spam detection: A ModerationObserver subscribes to post_question and post_answer events. Flags high-link-density, repeated content, or rapid-fire posting from low-rep accounts. Flag count past threshold triggers a notification to high-rep moderators (canClose level). The Observer pattern means Platform doesn't import the spam detector, the detector subscribes.
- SAY·8Close with one sentence: State on QuestionState for lifecycle gating, Strategy on SearchStrategy for ranking experiments, Observer-style reputation propagation inside vote(), and reputation thresholds gating privileged actions. Three patterns, one virtuous-cycle data model.
Interviewer is grading: Trade-offs are framed as 'we accept X cost because Y benefit' with concrete examples (the 'add_answer in OpenState vs add_answer in ClosedState' contrast). The bounty extension explicitly names the half-returns-half-destroyed rule and the anti-farming motivation. The spam answer correctly uses the existing Observer pattern rather than coupling to Platform.
Code Implementation
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional
import uuid
# --- Reputation thresholds ---
VOTE_THRESHOLD = 15
COMMENT_THRESHOLD = 50
CLOSE_THRESHOLD = 500
UPVOTE_REP = 10
DOWNVOTE_REP = -2
ANSWER_ACCEPTED_REP = 15
ACCEPT_BONUS_REP = 2
class User:
"""Tracks reputation and enforces privilege thresholds.
Reputation starts at 1 and never drops below 1."""
def __init__(self, user_id: str, username: str):
self.user_id = user_id
self.username = username
self._reputation = 1
@property
def reputation(self) -> int:
return self._reputation
def add_reputation(self, points: int) -> None:
self._reputation = max(1, self._reputation + points)
def can_vote(self) -> bool:
return self._reputation >= VOTE_THRESHOLD
def can_comment(self) -> bool:
return self._reputation >= COMMENT_THRESHOLD
def can_close_question(self) -> bool:
return self._reputation >= CLOSE_THRESHOLD
def __repr__(self) -> str:
return f"User({self.username}, rep={self._reputation})"
# --- Question State (State Pattern) ---
class QuestionState(ABC):
@abstractmethod
def add_answer(self, question: "Question", answer: "Answer") -> None: ...
@abstractmethod
def close(self, question: "Question") -> None: ...
@abstractmethod
def state_name(self) -> str: ...
class OpenState(QuestionState):
def add_answer(self, question: "Question", answer: "Answer") -> None:
question.answers.append(answer)
def close(self, question: "Question") -> None:
question.state = ClosedState()
def state_name(self) -> str:
return "OPEN"
class ClosedState(QuestionState):
def add_answer(self, question: "Question", answer: "Answer") -> None:
raise PermissionError("Cannot add answers to a closed question")
def close(self, question: "Question") -> None:
pass # already closed
def state_name(self) -> str:
return "CLOSED"
# --- Tag ---
class Tag:
def __init__(self, name: str):
self.name = name
self.question_count = 0
def increment(self) -> None:
self.question_count += 1
def __repr__(self) -> str:
return self.name
# --- Answer ---
class Answer:
def __init__(self, answer_id: str, body: str, author: User):
self.answer_id = answer_id
self.body = body
self.author = author
self.votes = 0
self.accepted = False
self.created_at = datetime.now()
def vote(self, is_upvote: bool) -> None:
self.votes += 1 if is_upvote else -1
def accept(self) -> None:
self.accepted = True
@property
def score(self) -> int:
return self.votes + (15 if self.accepted else 0)
def __repr__(self) -> str:
status = " [ACCEPTED]" if self.accepted else ""
return f"Answer(by={self.author.username}, votes={self.votes}{status})"
# --- Question ---
class Question:
def __init__(self, question_id: str, title: str, body: str,
author: User, tags: list[Tag]):
self.question_id = question_id
self.title = title
self.body = body
self.author = author
self.tags = tags
self.answers: list[Answer] = []
self.votes = 0
self.state: QuestionState = OpenState()
self.created_at = datetime.now()
def add_answer(self, answer: Answer) -> None:
self.state.add_answer(self, answer)
def vote(self, is_upvote: bool) -> None:
self.votes += 1 if is_upvote else -1
def close(self) -> None:
self.state.close(self)
@property
def score(self) -> int:
return self.votes
def __repr__(self) -> str:
tag_str = ", ".join(t.name for t in self.tags)
return (f"Question('{self.title}', by={self.author.username}, "
f"votes={self.votes}, answers={len(self.answers)}, "
f"state={self.state.state_name()}, tags=[{tag_str}])")
# --- Search Strategies ---
class SearchStrategy(ABC):
@abstractmethod
def rank(self, questions: list[Question], query: str) -> list[Question]: ...
class RelevanceStrategy(SearchStrategy):
"""Scores questions by keyword match frequency in title and body."""
def rank(self, questions: list[Question], query: str) -> list[Question]:
terms = query.lower().split()
def relevance(q: Question) -> int:
text = (q.title + " " + q.body).lower()
return sum(text.count(term) for term in terms)
matching = [q for q in questions if relevance(q) > 0]
return sorted(matching, key=relevance, reverse=True)
class VoteStrategy(SearchStrategy):
"""Returns matching questions sorted by vote count, highest first."""
def rank(self, questions: list[Question], query: str) -> list[Question]:
terms = query.lower().split()
matching = [q for q in questions
if any(t in (q.title + " " + q.body).lower() for t in terms)]
return sorted(matching, key=lambda q: q.votes, reverse=True)
class DateStrategy(SearchStrategy):
"""Returns matching questions sorted by creation date, newest first."""
def rank(self, questions: list[Question], query: str) -> list[Question]:
terms = query.lower().split()
matching = [q for q in questions
if any(t in (q.title + " " + q.body).lower() for t in terms)]
return sorted(matching, key=lambda q: q.created_at, reverse=True)
# --- Platform Facade ---
class Platform:
"""Central coordinator. All user interactions go through here.
Manages reputation effects, vote deduplication, and search."""
def __init__(self):
self._users: dict[str, User] = {}
self._questions: dict[str, Question] = {}
self._tags: dict[str, Tag] = {}
self._search_strategy: SearchStrategy = RelevanceStrategy()
self._vote_log: dict[str, set[str]] = {}
def register_user(self, username: str) -> User:
user_id = str(uuid.uuid4())[:8]
user = User(user_id, username)
self._users[user_id] = user
return user
def post_question(self, user_id: str, title: str, body: str,
tag_names: list[str]) -> Question:
author = self._users[user_id]
tags = [self._get_or_create_tag(name) for name in tag_names]
for tag in tags:
tag.increment()
q_id = str(uuid.uuid4())[:8]
question = Question(q_id, title, body, author, tags)
self._questions[q_id] = question
return question
def post_answer(self, user_id: str, question_id: str, body: str) -> Answer:
author = self._users[user_id]
question = self._questions[question_id]
a_id = str(uuid.uuid4())[:8]
answer = Answer(a_id, body, author)
question.add_answer(answer)
return answer
def vote(self, voter_id: str, question_or_answer, is_upvote: bool) -> None:
voter = self._users[voter_id]
if not voter.can_vote():
raise PermissionError(
f"{voter.username} needs {VOTE_THRESHOLD} reputation to vote "
f"(current: {voter.reputation})")
# Prevent double voting
target_id = getattr(question_or_answer, 'question_id',
getattr(question_or_answer, 'answer_id', None))
voter_targets = self._vote_log.setdefault(voter_id, set())
if target_id in voter_targets:
raise ValueError(f"{voter.username} already voted on this")
voter_targets.add(target_id)
question_or_answer.vote(is_upvote)
# Reputation effects
author = question_or_answer.author
if is_upvote:
author.add_reputation(UPVOTE_REP)
else:
author.add_reputation(DOWNVOTE_REP)
voter.add_reputation(-1) # downvoting costs the voter too
def accept_answer(self, user_id: str, answer: Answer, question: Question) -> None:
if question.author.user_id != user_id:
raise PermissionError("Only the question author can accept an answer")
answer.accept()
answer.author.add_reputation(ANSWER_ACCEPTED_REP)
question.author.add_reputation(ACCEPT_BONUS_REP)
def close_question(self, user_id: str, question_id: str) -> None:
user = self._users[user_id]
if not user.can_close_question():
raise PermissionError(
f"{user.username} needs {CLOSE_THRESHOLD} reputation to close questions")
self._questions[question_id].close()
def set_search_strategy(self, strategy: SearchStrategy) -> None:
self._search_strategy = strategy
def search(self, query: str) -> list[Question]:
all_questions = list(self._questions.values())
return self._search_strategy.rank(all_questions, query)
def _get_or_create_tag(self, name: str) -> Tag:
if name not in self._tags:
self._tags[name] = Tag(name)
return self._tags[name]
if __name__ == "__main__":
platform = Platform()
# Register users
alice = platform.register_user("alice")
bob = platform.register_user("bob")
charlie = platform.register_user("charlie")
# Give alice and bob enough reputation to vote
alice.add_reputation(100)
bob.add_reputation(50)
# Alice posts a question
q1 = platform.post_question(
alice.user_id,
"How does HashMap handle collisions in Java?",
"I know HashMap uses an array of buckets, but what happens when two keys "
"hash to the same bucket? Does it use chaining or open addressing?",
["java", "hashmap", "data-structures"],
)
print(f"Posted: {q1}")
# Bob posts another question
q2 = platform.post_question(
bob.user_id,
"When to use HashMap vs TreeMap?",
"Both implement the Map interface. HashMap gives O(1) lookups, TreeMap gives "
"sorted keys. When should I pick one over the other?",
["java", "hashmap", "collections"],
)
print(f"Posted: {q2}")
# Charlie answers Alice's question
a1 = platform.post_answer(
charlie.user_id,
q1.question_id,
"Java HashMap uses chaining with linked lists. Since Java 8, when a bucket "
"exceeds 8 entries, the linked list converts to a red-black tree for O(log n) lookups.",
)
print(f"\nCharlie answered: {a1}")
# Bob also answers
a2 = platform.post_answer(
bob.user_id,
q1.question_id,
"Chaining. Each bucket is a linked list (or tree after threshold). "
"Open addressing is used by some other implementations like Python dicts.",
)
print(f"Bob answered: {a2}")
# Voting
print("\n--- Voting ---")
platform.vote(alice.user_id, a1, True)
print(f"Alice upvoted Charlie's answer. Charlie rep: {charlie.reputation}")
platform.vote(bob.user_id, a1, True)
print(f"Bob upvoted Charlie's answer. Charlie rep: {charlie.reputation}")
platform.vote(alice.user_id, q2, True)
print(f"Alice upvoted Bob's question. Bob rep: {bob.reputation}")
# Accept answer
print("\n--- Accept answer ---")
platform.accept_answer(alice.user_id, a1, q1)
print(f"Alice accepted Charlie's answer. Charlie rep: {charlie.reputation}")
print(f"Alice rep after accepting: {alice.reputation}")
# Search with different strategies
print("\n--- Search by relevance ---")
platform.set_search_strategy(RelevanceStrategy())
results = platform.search("HashMap")
for r in results:
print(f" {r}")
print("\n--- Search by votes ---")
platform.set_search_strategy(VoteStrategy())
results = platform.search("HashMap")
for r in results:
print(f" {r}")
# Try voting without enough reputation
print("\n--- Permission check ---")
try:
platform.vote(charlie.user_id, q2, True)
print(f"Charlie voted (rep: {charlie.reputation})")
except PermissionError as e:
print(f"Expected error: {e}")
# Close question (need enough rep)
alice.add_reputation(400)
platform.close_question(alice.user_id, q1.question_id)
print(f"\nAlice closed q1. State: {q1.state.state_name()}")
# Try adding answer to closed question
try:
platform.post_answer(bob.user_id, q1.question_id, "Too late!")
except PermissionError as e:
print(f"Expected error: {e}")
# Final state
print(f"\nFinal reputations:")
print(f" Alice: {alice.reputation}")
print(f" Bob: {bob.reputation}")
print(f" Charlie: {charlie.reputation}")
assert q1.state.state_name() == "CLOSED"
assert a1.accepted is True
assert charlie.reputation > 1
assert len(q1.answers) == 2
print("\nAll assertions passed.")Interview Grading by Level
What an interviewer at each level expects to see in your answer. Use this to calibrate, not to perform.
Junior Engineer (L3)
Lists the three patterns and writes Question, Answer, and a working vote method, but reputation thresholds and the state machine stay shaky.
- Names Observer, Strategy, and State correctly.
- Writes User, Question, Answer with the basic fields.
- Implements vote(target, isUpvote) that mutates the target's vote count.
- Recognizes that reputation gates voting and at least mentions a threshold.
- Adds at least RelevanceStrategy as a Strategy implementation.
- Encodes question lifecycle as a boolean is_closed flag with if-checks scattered through methods.
- Hardcodes reputation thresholds as magic numbers at every call site.
- Forgets the downvote cost on the voter, so serial downvoting is free.
- Lets the same user vote multiple times on the same target (no _vote_log dedup).
- Recomputes reputation from history on every check instead of storing a running total.
Mid-Level Engineer (L4)
Drives the design with a real state machine, threshold constants, and the proper bidirectional reputation effect on vote.
- Implements OpenState and ClosedState classes with add_answer and close behaviors split correctly.
- Centralizes thresholds and reputation deltas as module-level constants.
- Implements _vote_log as voter_id -> set of target_ids for dedup.
- Applies the -1 cost to the voter on every downvote alongside the -2 to the author.
- Implements accept_answer with both the +15 to answerer and the +2 to asker, gated on question.author check.
- Does not volunteer bounties or spam detection as the natural next extensions.
- Implements the search filter inside each strategy instead of extracting the shared filter step.
- Treats reputation floor (clamp to 1, never go below) as an implementation detail rather than a deliberate design choice.
Senior Engineer (L5+)
Volunteers extensions, names every reputation invariant, and ties each pattern to a specific failure mode.
- Volunteers bounties (with the half-returns-half-destroyed anti-farming rule) and spam detection (Observer-based ModerationObserver) before being asked.
- Frames reputation as an access control mechanism, not a vanity number, with the exact thresholds named (15, 50, 500).
- Names the downvote-cost-on-voter (-1) as intentional friction that prevents serial abuse while preserving the signaling ability.
- Defends State over enum by naming the concrete failure (every method needing an if/elif as new states are added).
- Proposes Elasticsearch-backed search at scale, keeping the SearchStrategy interface stable but swapping the implementation.
- Names the reputation floor (clamp to 1) as the protection against negative-rep griefing.
- Closes with a one-sentence summary that names all three patterns, the threshold model, and the reputation symmetry in under 20 seconds.
Common Mistakes
- ✗Letting anyone vote without reputation thresholds. Sock puppet accounts and vote manipulation become trivial.
- ✗Computing reputation on the fly from vote history every time. This is expensive. Store it as a running total and update incrementally.
- ✗Hardcoding search ranking logic. Relevance, votes, and date sorting should be swappable strategies, not if-else chains.
- ✗No separation between vote action and reputation effect. Changing the reputation formula means hunting through every place votes are cast.
Key Points
- ✓Reputation is the central mechanic. It gates who can vote, comment, edit, and close questions. Without thresholds, the system has no quality control.
- ✓Observer pattern propagates reputation changes. When a vote happens, both the voter and the content author get reputation adjustments.
- ✓Strategy pattern for search ranking. Users switch between relevance, votes, and date sorting without changing the search infrastructure.
- ✓State pattern governs question lifecycle. Open questions accept answers. Closed questions reject them. The state object enforces this cleanly.