AlgoMaster Logo

Design Stack Overflow

High Prioritymedium29 min readUpdated June 27, 2026
Listen to this chapter
Unlock Audio

In this chapter, we will explore the low-level design of stack overflow like system in detail.

Let's start by clarifying the requirements:

1. Clarifying Requirements

Before starting the design, it is important to ask thoughtful questions to uncover hidden assumptions and better define the scope of the system.

Here is an example of how a discussion between the candidate and the interviewer might unfold:

After gathering the details, we can summarize the key system requirements.

Functional Requirements
  • Users can post questions, answers, and comments on both questions and answers
  • Users can upvote or downvote questions and answers. A user can only vote once per post
  • The original poster of a question can accept one answer as the solution
  • A question can have one or more tags
  • Users earn or lose reputation points based on upvotes/downvotes on their content and whether their answer is accepted
  • Support searching for questions by keywords in the title or body and filtering questions by tags
Non-Functional Requirements
  • Consistency: Voting actions and reputation updates should be strongly consistent and reflected immediately.
  • Concurrency: The system must gracefully handle high-concurrency scenarios, such as multiple users voting on the same post simultaneously.
  • Scalability: The design should be scalable to accommodate a growing number of users, questions, and answers.

2. Identifying Core Entities

Core entities are the fundamental building blocks of our system. We identify them by analyzing the functional requirements and highlighting the key nouns and responsibilities that naturally map to object-oriented abstractions such as classes, enums, or interfaces.

Let’s walk through the functional requirements and extract the relevant entities:

1. Users can post questions, answers, and comments.

This clearly suggests the need for a User entity to represent participants of the system, along with Question, Answer, and Comment entities. Each of these entities will hold content, metadata (like creation time), and relationships (such as author and parent post).

2. Comments can be added to both questions and answers.

This implies that Comment should be a standalone entity with a reference to its parent post (which could be a Question or an Answer). Since we’re only supporting flat comments, we don’t need to model nested replies.

3. Users can upvote or downvote questions and answers, and voting affects reputation.

We need a Vote entity to track who voted on what and in what direction (up or down). The User entity should maintain a Reputation field that is updated based on voting outcomes and accepted answers.

4. A question can have multiple tags and one accepted answer.

We need a Tag entity, and the Question entity should maintain a list of tags and a reference to the accepted Answer.

5. The system should support search and filtering.

To support searching by keyword and filtering by tag, Question should expose fields like title, body, and tags in a searchable format. While we may use indexing or search service integrations in real implementation, at the design level this implies the need for search-related APIs and utility methods.

These core entities define the key abstractions of the stack overflow system and will guide the structure of our low-level design and class diagrams.

3. Designing Classes and Relationships

This section outlines the classes that form the core of the Stack Overflow system, their responsibilities, the relationships between them, and the key design patterns employed.

3.1 Class Definitions

The system is broken down into several types of classes, each with a distinct role.

Enums

Simple enumerations that define a fixed set of constants.

  • VoteType: Represents the two types of votes a user can cast: UPVOTE and DOWNVOTE.
  • EventType: Defines all actions that can trigger a reputation change, such as UPVOTE_QUESTION, DOWNVOTE_ANSWER, and ACCEPT_ANSWER.

Data Classes

These classes primarily act as data containers with minimal logic.

User

Represents a platform user with a unique ID, name, and a thread-safe reputation score.

Tag

A simple class representing a topic tag (e.g., "java") that can be associated with questions.

Event

A data-transfer object used in the Observer pattern. It encapsulates the details of an action, including its EventType, the actor (user) who performed it, and the targetPost.

Core Classes

These classes contain the main business logic and structure of the application.

Content (Abstract)

The base class for all user-generated content.

It holds common attributes like an id, body, author, and creationTime.

  • Post (Abstract): Extends Content. It's the foundation for content that can be voted on, like questions and answers. It manages vote counts, tracks voters to prevent duplicates, holds a list of comments, and implements the "Subject" part of the Observer pattern.
  • Question: A subclass of Post that represents a user's question. It includes a title, a set of Tags, a list of Answers, and can have one acceptedAnswer.
  • Answer: A subclass of Post that represents a reply to a Question. It has a flag to indicate if it's the accepted answer.
  • Comment: A simple subclass of Content. It represents a comment on a Post and doesn't have voting or reputation features.

StackOverflowService

Acts as the central Facade for the entire system.

It provides a simple, unified API for clients to perform all major actions (e.g., creating users, posting questions, voting, searching) and orchestrates the interactions between the other components.

3.2 Key Design Patterns

The design leverages several standard design patterns to create a modular, flexible, and maintainable system.

Observer Pattern

This pattern is used to decouple actions from their consequences, such as updating reputation after a vote.

  • Subject: The Post class. It maintains a list of observers and notifies them when an event (like a vote) occurs via its notifyObservers method.
  • Observer: The PostObserver interface.
  • Concrete Observer: The ReputationManager class. It registers with posts and updates user reputations whenever it receives an event notification.
  • Benefit: If we want to add new functionality, like awarding badges for upvotes, we can simply create a new BadgeManager class that implements PostObserver without changing any of the existing Post or ReputationManager code.

Strategy Pattern

This pattern is used to define a family of algorithms (search strategies), encapsulate each one, and make them interchangeable.

  • Context: The StackOverflowService. Its searchQuestions method accepts a list of strategies.
  • Strategy: The SearchStrategy interface, which defines the common filter operation.
  • Concrete Strategies: KeywordSearchStrategy, TagSearchStrategy, and UserSearchStrategy. Each encapsulates a specific filtering algorithm.
  • Benefit: The client can compose complex search queries by combining different strategy objects. New search methods can be added easily by creating new classes that implement the SearchStrategy interface.

Facade Pattern

The StackOverflowService class acts as a Facade.

  • Facade: StackOverflowService.
  • Subsystem: The network of classes including User, Question, ReputationManager, etc.
  • Benefit: It provides a simple, unified interface to a complex subsystem. A client (like the StackOverflowDemo class) interacts only with the StackOverflowService to perform high-level operations, hiding the internal complexity of object creation, observer registration, and data management.

3.3 Full Class Diagram

Try It Yourself (Exercise)

Before reading the full implementation, try building this yourself. Below are template stubs for the classes from the design. The enums, data classes, and the demo are already written for you. The job is to fill in the core logic where the TODO comments appear.

Your task: Implement every TODO using the design from the previous sections. When you are done, run the demo. A correct implementation produces the expected output.

Loading editor...

4. Code Implementation

Now we translate the design into working code. We build bottom-up: the enums and core entities first, then the abstract Content base and its Post hierarchy, then the observer and reputation pieces, the search strategies, and finally the service that ties everything together. This order matters because each layer depends on the ones below it. Pick your language below to see a full walkthrough.

4.1 VoteType Enum

A Java enum gives us a fixed, type-safe set of constants. We use it so the rest of the code switches over UPVOTE and DOWNVOTE instead of comparing raw strings.

Because the enum is a real type, an invalid vote value cannot exist anywhere in the system.

4.2 EventType Enum

This enum names every action that can change reputation. The observer switches over these constants to decide how many points to add or remove.

Listing the reputation-affecting events in one enum makes it clear what the system reacts to, and adding a new event later is a one-line change.

4.3 User

User holds an id, a name, and a reputation score. Reputation changes from concurrent votes, so we store it in an AtomicInteger and update it with addAndGet.

The atomic counter lets several threads adjust reputation at once without losing updates, and we never expose a setter that could put the score in an inconsistent state.

4.4 Content (Abstract Class)

Content is an abstract base for anything a user writes: a Post or a Comment. It captures the shared fields, the body text, the author, and the creation time, so subclasses do not repeat them.

Marking the class abstract prevents anyone from creating a bare Content; only concrete subclasses can be instantiated.

4.5 Post

Post extends Content and adds voting. It records who voted with a Map from user id to vote type so a user cannot vote twice, and it notifies observers whenever the vote total changes.

Tracking votes per user is what prevents double voting: a second vote from the same user either flips the direction or is rejected, never counted twice.

4.6 Tag

Tag is a small immutable value object holding a name. Questions hold a set of tags, and search filters by them.

Making Tag immutable with final fields means the same tag instance can be shared safely across many questions.

4.7 Question

Question extends Post, so it inherits voting, and adds a title, a set of tags, a list of answers, and an optional accepted answer. The author can accept exactly one answer.

Keeping the accepted answer as a single reference, rather than a flag on every answer, makes the rule that a question has at most one accepted answer easy to enforce.

4.8 Answer

Answer also extends Post, so answers can be voted on like questions. It adds an isAccepted flag the question sets when the author accepts it.

Because Answer reuses Post, the voting and observer logic is written once and shared by both questions and answers.

4.9 Comment

Comment extends Content but not Post, because comments cannot be voted on and carry no reputation. It is the simplest piece of content: body, author, and timestamp.

Choosing Content rather than Post as the base is a deliberate modeling decision: it makes voting impossible on a comment by construction.

4.10 Event

Event is a small record of something that happened on a post: which EventType occurred and which user triggered it. The post passes it to observers so they can react.

Bundling the type and the actor into one object keeps the observer interface simple and gives each observer everything it needs to decide how to respond.

4.11 PostObserver (Observer Pattern)

The Observer pattern decouples an action, such as a vote, from its consequence, such as a reputation change. Post keeps a list of PostObservers and notifies them on every event, without knowing what they do.

This keeps the core voting code clean: to add badges or notifications later, we add a new observer instead of editing Post.

4.12 ReputationManager

ReputationManager is the concrete observer. It implements PostObserver, listens for events, and applies the reputation rules: an accepted answer is worth more than an upvote, a downvote subtracts points, and so on.

Centralizing the point values here means the reputation policy lives in one class, separate from the voting code that triggers it.

4.13 SearchStrategy and Implementations

The Strategy pattern lets us define a family of search algorithms, wrap each in its own class, and swap them freely. We define a SearchStrategy interface and implementations that filter by tag, by user, or by keyword.

Because the service depends on the interface, adding a new way to search means writing one more strategy class, with no change to the service.

4.14 StackOverflowService (Facade)

StackOverflowService is the Facade. It exposes a simple API, posting a question, answering, voting, accepting, searching, and hides the wiring between users, posts, the reputation manager, and search strategies.

The Facade gives clients one place to call and keeps the object graph and its dependencies out of their concern.

Post and Accept Answer Sequence Diagram

The following diagram illustrates what happens when a user posts an answer and the question author later accepts it:

postAnswer(userId, questionId, body)new Answer(body, author)addAnswer(answer)answeracceptAnswer(questionId, answerId)acceptAnswer(answer)setAccepted(true)update(ACCEPT_ANSWER, author)apply reputation rulesdoneClientStackOverflowServiceQuestionAnswerReputationManagerClientStackOverflowServiceQuestionAnswerReputationManager
10 / 10
algomaster.io

5. Run and Test

Loading editor...

6. Concurrency and Thread Safety

A busy question-and-answer site serves many requests at once. Each request runs on its own thread, but they share the same in-memory Question, Answer, and User objects. When two threads touch the same post or reputation counter at the same time, the single-threaded reasoning behind the vote and accept logic breaks down.

The shared mutable state is small and specific: voteCount and the voters map on each Post, reputation on each User, and acceptedAnswer on each Question. The design already guards it: vote() and acceptAnswer() run under a lock, and each user's reputation is an atomic counter. The races below explain why those guards are needed.

Concern 1: Two Users Voting on the Same Post at Once (High Risk)

Two users upvote the same answer at the same instant. Both calls run vote() on the same Post, and both read voteCount, add to it, and write it back.

Setup

An answer has a voteCount of 10. User A and User B are both new voters and upvote at the same instant. Each call adds 1, so the correct final count is 12.

Without a lock on vote()

  1. Thread A reads voteCount as 10, computes 10 + 1 = 11
  2. Thread B reads voteCount as 10 too, because A has not written its result back yet, computes 10 + 1 = 11
  3. Thread A writes voteCount = 11
  4. Thread B writes voteCount = 11, overwriting A's update
  5. Both threads put their entry into voters and fire their observer events

Result

Two upvotes happened, but voteCount ends at 11 instead of 12. One vote is lost, and the count no longer matches the two entries recorded in voters.

With a lock

A lock on the post makes the read, the update, the write, and the notification run as one unit. Thread A takes the lock and finishes the sequence; Thread B then takes the lock, reads the current count of 11, and writes 12. Both votes count, and voteCount stays consistent with voters.

The lock belongs on the Post, which owns its count and voter set. A thread-safe map for voters alone would not help, because the race is not in any single map operation. It is in the gap between reading the count and writing it back, and only the lock closes that gap.

Concern 2: Reputation Drifting Under Concurrent Votes (Medium Risk)

Every vote fires an event, and the ReputationManager updates the post author's reputation. When many users vote on the same answer at once, that author's reputation is updated from many threads at the same time.

Setup

Five users upvote the same answer at the same instant. Each fires UPVOTE_ANSWER, which adds 10 to the author's reputation, so it should rise by 50.

Without an atomic reputation field

If reputation were a plain integer updated as reputation = reputation + change, the five threads interleave their read-modify-write steps.

  1. Threads 1 and 2 both read the author's reputation as 200
  2. Thread 1 computes 200 + 10 = 210 and writes it
  3. Thread 2 computes 200 + 10 = 210 too and writes it, overwriting Thread 1
  4. The remaining threads interleave the same way

Result

The author earned 50 reputation, but the stored value rises by far less because the updates overwrite each other, leaving the score silently too low. That is a correctness bug, not a display glitch.

With an atomic counter

Storing reputation as an atomic counter makes each increment one indivisible operation. The five +10 updates apply one on top of another, so the author ends at 250. No lock is needed, because the atomic counter serializes the increments itself.

The post lock guards the compound vote operation; the atomic counter guards the single reputation increment it triggers. Each piece of shared state is protected where it is mutated.

Concern 3: Accepting an Answer While It Is Being Voted On (Medium Risk)

Accepting an answer is several steps: check that no answer is accepted yet, set acceptedAnswer, flip the answer's isAccepted flag, and fire an ACCEPT_ANSWER event that grants reputation. The problem appears when two of these sequences run on the same question at once.

Setup

The accept request for Answer X is submitted twice at the same instant, for example from a double-click or a retried request. Both calls run acceptAnswer() on the same Question.

Without a lock

The check-then-set on acceptedAnswer interleaves: both calls read it as empty and pass the check before either writes. Both then set it and fire ACCEPT_ANSWER, granting the accepted-answer reputation twice.

  1. Accept request 1 reads acceptedAnswer as null, passes the check
  2. Accept request 2 reads acceptedAnswer as null too, passes the check
  3. Request 1 sets acceptedAnswer = X, marks X accepted, fires ACCEPT_ANSWER
  4. Request 2 sets acceptedAnswer = X again, fires a second ACCEPT_ANSWER

Result

The answer author receives the accepted-answer bonus twice for a single acceptance, leaving the reputation too high.

With a lock

A lock on the Question makes the check and the write one unit. The first accept wins and fires one event; the second takes the lock afterward, finds acceptedAnswer already set, and returns without firing again. A concurrent upvote on the answer is unaffected, because vote() locks the answer, not the question, so an accept and a vote can run in parallel.

Across all three concerns, the lock belongs at the level of the data being mutated. The post owns its vote count, so vote() locks the post. Each user owns its reputation, so the counter is atomic. The question owns its accepted-answer reference, so acceptAnswer() locks the question. Because each lock guards a different object, an accept on a question and an upvote on its answer run at the same time without blocking.

Observer notification runs inside these locks. The ReputationManager only does a fast atomic update, so holding the lock during notification is fine. An observer that did slow work, such as sending an email or writing to a database, should run off the locked path so it does not serialize every vote behind a slow listener.

7. Extensions

Because the Observer and Strategy patterns each isolate one concern, new requirements attach to the existing seams. A new consequence of an action becomes an observer; a new way to query questions becomes a strategy. Each extension below adds classes or fields without changing Post, Question, or the existing observers and strategies.

7.2 Badges and Gamification

Scenario: "Award badges when a user crosses milestones, such as a first accepted answer or ten upvotes on a single answer."

A badge is another consequence of activity, which is what the Observer pattern already handles. A new BadgeManager implements PostObserver and is added to posts alongside the ReputationManager. It reads the same events and grants a badge when a threshold is met. No existing observer changes, and vote() keeps firing the same events.

Registering it takes one line at setup: post.addObserver(badgeManager) next to the existing post.addObserver(reputationManager). Both observers receive every event and react independently, and the code that fires the events stays unchanged.

7.3 Full-Text Search Over Questions

Scenario: "The keyword search scans every question body on each query, which gets slow as the question count grows. Speed it up with an index."

Search is already a SearchStrategy, so a faster version is just another implementation of filter(). An IndexedSearchStrategy builds an inverted index from each term to the questions that contain it, then answers a keyword query from the index instead of scanning every body. The rest of the system calls filter() exactly as before.

The index is built once and reused, so each keyword lookup is a single map access instead of a full scan. Since it implements the same SearchStrategy interface as the other strategies, swapping it in is a one-line change at the call site, and the service code stays the same.

7.4 Edit History and Version Control of Posts

Scenario: "Posts can be edited. Keep every previous version so users can see the edit history and revert if needed."

Content currently stores a single body. Versioning records each edit as a PostRevision snapshot instead of overwriting it. A Post keeps a list of revisions; an edit appends a new one, and the latest revision is the current body. This adds one class and a method, and voting, accepting, and reputation are untouched, since none of them read the history.

Reverting pushes an older revision's body as a new revision, so the full history stays intact instead of being deleted. An edit takes the same lock as voting, so an edit and a concurrent vote on the same post do not interfere.

Each extension plugs into an existing seam: a new observer for badges, a new strategy for indexed search, or a new field and method on Post for revisions. None of them require editing the classes already written, which is the benefit of keeping each consequence and each query behind its own interface.

8. Quiz

Design Stack Overflow - Quiz

20 quizzes