Stack Overflow is one of the most widely used question-and-answer platforms for software developers. It enables users to ask technical programming questions, receive answers from the community, and collaboratively improve the quality of information through voting and editing.
Loading simulation...
Although its popularity has declined since the rise of AI tools like ChatGPT, Stack Overflow remains a valuable resource especially for well-structured, peer-reviewed solutions and niche programming discussions.
Stack Overflow is used by:
In this chapter, we will explore the low-level design of stack overflow like system in detail.
Let's start by clarifying the 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:
Candidate: Should users be able to comment on both questions and answers? And do we need to support nested comments?
Interviewer: Yes, comments are allowed on both questions and answers. However, to keep things simple, we’ll support only flat (non-nested) comments for now.
Candidate: Do we need to implement a user reputation system that changes based on actions like upvotes, downvotes, and accepted answers?
Interviewer: Yes, users should earn or lose reputation based on votes and whether their answer is accepted. The reputation impact may vary depending on whether the vote is on a question or an answer.
Candidate: Should we support features like tagging, searching, and filtering based on tags or keywords?
Interviewer: Yes. Each question can have one or more tags, and the system should support keyword-based search and tag-based filtering.
After gathering the details, we can summarize the key system requirements.
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:
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).
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.
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.
We need a Tag entity, and the Question entity should maintain a list of tags and a reference to the accepted Answer.
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.
User: Represents a registered user of the platform. Holds attributes like user ID, display name, and reputation. Responsible for posting content, voting, and earning points.Question: Represents a question posted by a user. Includes title, body, tags, creation timestamp, list of answers, list of comments, votes, and a reference to an accepted answer.Answer: Represents an answer posted to a question. Includes body, author, timestamp, list of comments, and votes. Can be marked as accepted by the original question author.Comment: Represents a comment made on a question or answer. Includes content, author, timestamp, and a reference to the parent post (either a question or answer).Vote: Represents a single upvote or downvote by a user on a question or answer. Includes voter, vote type (up or down), and the target post.Tag: Represents a tag used to categorize questions. Each tag has a name.Reputation: Represents a user’s score based on community interactions. May be modeled as a field in the User class or as a separate entity if detailed reputation history is needed.SearchService (optional): Provides methods to perform keyword search and tag-based filtering over the list of questions. May not be an entity per se but a key component of the system.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.
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.
While listing class methods, we will skip trivial getters and setters to keep the walkthrough focused on core behaviors
The system is broken down into several types of classes, each with a distinct role.
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.These classes primarily act as data containers with minimal logic.
UserRepresents a platform user with a unique ID, name, and a thread-safe reputation score.
TagA simple class representing a topic tag (e.g., "java") that can be associated with questions.
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.
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.StackOverflowServiceActs 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.
The design leverages several standard design patterns to create a modular, flexible, and maintainable system.
This pattern is used to decouple actions from their consequences, such as updating reputation after a vote.
Post class. It maintains a list of observers and notifies them when an event (like a vote) occurs via its notifyObservers method.PostObserver interface.ReputationManager class. It registers with posts and updates user reputations whenever it receives an event notification.BadgeManager class that implements PostObserver without changing any of the existing Post or ReputationManager code.This pattern is used to define a family of algorithms (search strategies), encapsulate each one, and make them interchangeable.
StackOverflowService. Its searchQuestions method accepts a list of strategies.SearchStrategy interface, which defines the common filter operation.KeywordSearchStrategy, TagSearchStrategy, and UserSearchStrategy. Each encapsulates a specific filtering algorithm.SearchStrategy interface.The StackOverflowService class acts as a Facade.
StackOverflowService.User, Question, ReputationManager, etc.StackOverflowDemo class) interacts only with the StackOverflowService to perform high-level operations, hiding the internal complexity of object creation, observer registration, and data management.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The following diagram illustrates what happens when a user posts an answer and the question author later accepts it:
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.
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.
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.
vote()voteCount as 10, computes 10 + 1 = 11voteCount as 10 too, because A has not written its result back yet, computes 10 + 1 = 11voteCount = 11voteCount = 11, overwriting A's updatevoters and fire their observer eventsTwo 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.
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.
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.
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.
If reputation were a plain integer updated as reputation = reputation + change, the five threads interleave their read-modify-write steps.
200 + 10 = 210 and writes it200 + 10 = 210 too and writes it, overwriting Thread 1The 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.
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.
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.
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.
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.
acceptedAnswer as null, passes the checkacceptedAnswer as null too, passes the checkacceptedAnswer = X, marks X accepted, fires ACCEPT_ANSWERacceptedAnswer = X again, fires a second ACCEPT_ANSWERThe answer author receives the accepted-answer bonus twice for a single acceptance, leaving the reputation too high.
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.
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.
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.
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.
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.
20 quizzes