Skip to content

Stack Overflow (LLD)#

Problem statement (interviewer prompt)

Design the OO class model for Stack Overflow: users, questions, answers, comments, votes, tags, reputation, and badges. Cover voting effect on reputation, accepting an answer, and tag-based search.

classDiagram
  class User {
    +id, name, email
    +reputation : int
    +badges : List~Badge~
    +vote(post, type)
  }
  class Post {
    <<abstract>>
    +id, author : User
    +body, createdAt
    +votes : Map~User, VoteType~
    +comments : List~Comment~
    +score() int
  }
  class Question {
    +title, tags : Set~Tag~
    +answers : List~Answer~
    +acceptedAnswer : Answer
  }
  class Answer {
    +question : Question
    +accepted : boolean
  }
  class Comment { +author, body, post }
  class Tag { +name, description }
  class Badge { +name, kind }
  Post <|-- Question
  Post <|-- Answer
  Question *-- "many" Answer
  Question *-- "many" Tag
  Post *-- "many" Comment
  User --> Post : authors

Reputation deltas: +10 upvote on Question, +10 upvote on Answer, +15 accepted answer, -2 downvote, -1 to downvoter on Answer.

A common interview problem because it exercises inheritance, observer/event patterns, validation rules, and a clear data model.

Class diagram#

classDiagram
  class User {
    +id, name, email
    +reputation : int
    -badges : List~Badge~
    +ask(title, body, tags) Question
    +answer(q, body) Answer
    +comment(post, body)
    +vote(post, type)
  }
  class Post {
    <<abstract>>
    +id, author : User
    +body, createdAt
    -votes : Map~User, VoteType~
    -comments : List~Comment~
    +addVote(user, type)
    +score() int
  }
  class Question {
    +title, tags : Set~Tag~
    -answers : List~Answer~
    +acceptedAnswer : Answer
    +accept(answer)
  }
  class Answer {
    +question : Question
    +accepted : boolean
  }
  class Comment { +author, body, postId }
  class Tag { +name, description }
  class Badge { +name, kind }
  class VoteType {
    <<enumeration>>
    UP
    DOWN
  }
  Post <|-- Question
  Post <|-- Answer
  Question *-- "many" Answer
  Question *-- "many" Tag
  Post *-- "many" Comment
  User --> Post : authors

Voting flow#

class Post:
    def add_vote(self, user, t: VoteType):
        if user == self.author: raise ValueError("cannot vote own post")
        prev = self.votes.get(user)
        self.votes[user] = t
        delta = vote_delta(self, prev, t)            # bumps author rep
        self.author.reputation += delta

Reputation deltas (SO defaults):

Action Author Δ Voter Δ
Upvote question +5 0
Upvote answer +10 0
Downvote answer -2 -1
Accepted answer +15 +2

Accept answer#

class Question:
    def accept(self, answer, by):
        if by != self.author: raise PermissionError
        if answer.question != self: raise ValueError
        if self.accepted_answer:
            self.accepted_answer.accepted = False
            self.accepted_answer.author.reputation -= 15
        answer.accepted = True
        answer.author.reputation += 15

In LLD, model an in-memory tag→questions inverted index; in HLD this is Elasticsearch.

class Repo:
    def __init__(self):
        self.tag_index: dict[Tag, set[Question]] = defaultdict(set)
    def index(self, q: Question):
        for tag in q.tags:
            self.tag_index[tag].add(q)
    def search(self, tags: list[Tag]) -> list[Question]:
        return sorted(set.intersection(*(self.tag_index[t] for t in tags)),
                      key=lambda q: q.score(), reverse=True)

Badge engine (Observer / event-driven)#

flowchart LR
  Event[VoteCast / AnswerAccepted] --> Bus[(event bus)]
  Bus --> BadgeRule1[Rule: 10 upvotes -> Popular Question]
  Bus --> BadgeRule2[Rule: accepted answer -> Enlightened]

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class Event,BadgeRule1,BadgeRule2 service;
    class Bus queue;

Each domain event publishes to an event bus; badge-awarding rules subscribe.

Moderation#

  • Posts can be flagged.
  • A ModerationQueue holds flagged posts pending review.
  • Moderator actions: hide, edit, delete, restore.
  • Threshold reputation to gain moderation privileges.

Concurrency#

  • Vote counts: use atomic increments or row-level locks.
  • Reputation: same.
  • Posting: optimistic locking by version.

Extensions#

  • Bounty system (offered reputation locked until awarded).
  • Edit history (immutable revisions).
  • Markdown rendering layer.
  • Notifications (your post received an answer).

Where Stack Overflow LLD fits#

flowchart TB
  SO((Stack Overflow<br/>LLD))
  OOP[OOP pillars<br/>Post inheritance]
  BPL[Behavioral patterns<br/>Observer / badges]
  RQ["Reddit Q&A (HLD)<br/>system-design sibling"]
  POL[Pessimistic / optimistic locking<br/>concurrent voting]
  OOP --> SO
  BPL --> SO
  SO -. complement .- RQ
  POL --> SO

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class SO service;
    class OOP,BPL,RQ,POL datastore;

Glossary & fundamentals#

Tag Concept What it is Page
LLD OOP pillars Post inheritance oop-pillars
LLD Behavioral patterns Observer for badge events behavioral-patterns
HLD Reddit Q&A (HLD) the system-design sibling reddit-qa
LLD Pessimistic vs optimistic locking concurrent voting pessimistic-optimistic-locking

Quick reference#

Classes#

  • User (reputation, badges)
  • Post (abstract: votes, comments)
  • Question (title, tags, answers, acceptedAnswer)
  • Answer (question, accepted)
  • Comment, Tag, Badge

Reputation deltas#

Action Author Δ
Upvote Q +5
Upvote A +10
Downvote A -2 (voter -1)
Accepted A +15

Accept rules#

  • Only Q's author can accept
  • Switching: previous accepted loses +15
  • Question's acceptedAnswer updated atomically

In-memory inverted index: tag → set; intersect for multi-tag.

Badge engine#

Domain events → bus → rule evaluators. Decouples awarding from posting.

Moderation#

Flag → ModerationQueue → moderator actions (hide/edit/delete/restore).

Concurrency#

  • Atomic vote counters / row locks
  • Optimistic version per post for edits

Extensions#

  • Bounties
  • Edit history (immutable revisions)
  • Notifications
  • Markdown rendering

Refs#

  • Stack Exchange API docs
  • Nick Craver - SO architecture posts

FAQ#

How is reputation calculated?#

Each vote on a user's post applies a fixed delta: question upvote plus 5, answer upvote plus 10, downvote minus 2, accepted answer plus 15. The sum is the user's reputation.

How do you model questions and answers?#

A common Post superclass with subclasses Question and Answer. Comments attach to any Post. Question owns an optional accepted_answer_id and a list of Answer references.

How is tag-based search implemented?#

Tags are a many-to-many relation with Question. A secondary index from tag to question id list lets queries fetch posts by tag in O(1) per tag.

How is voting kept idempotent?#

Each Vote row is keyed (user_id, post_id) so a user can vote at most once per post. Re-voting updates the existing row, recomputing the reputation delta atomically.

How are badges awarded?#

Define BadgeRule objects with predicates that check post or user events; an event bus fires changes and a worker evaluates the rules, granting badges idempotently.

Further reading#