Bird
Raised Fist0

Design Stack Overflow in OOP - Question, Answer, Vote, Tag, Reputation, Bounty

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Design Stack Overflow in OOP - Question, Answer, Vote, Tag, Reputation, Bounty
40 min ยท hard
hard

Design a simplified Stack Overflow system where users can post questions and answers, vote on them, tag questions, earn reputation, and offer bounties. The system must handle concurrent voting, reputation updates, and bounty expiration.

  • Questions and Answers can be voted up or down by users; votes affect user reputation.
  • Tags categorize questions; each question can have multiple tags.
  • Users earn reputation points based on votes and accepted answers.
  • Bounties can be placed on questions to attract answers; bounty expires after a fixed time.
  • Concurrent votes and reputation updates must be thread-safe to avoid inconsistent state.
  • Votes are immutable once cast; users cannot vote multiple times on the same post.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Design core entities: Question, Answer, and Vote
Define classes for Question, Answer, and Vote capturing their state and behavior. Consider how to represent votes and their effect on posts.
๐Ÿ’ก Hint
Use abstract class Post for shared state (id, content, author, votes) since interface cannot hold instance fields in Java.
abstract class Post(id, content, author, List<Vote>) - abstract because it holds shared instance STATE; interface cannot hold fields in Java
Mid: Uses inheritance for shared code. Senior: Explains why abstract class is chosen over interface due to instance state and method implementations.
โš ๏ธCommon Wrong Answer
Using interface Post with fields leads to compilation errors since interfaces cannot hold instance state in Java.
Step 2
Implement thread-safe voting mechanism
Design how votes are cast on posts ensuring thread-safe updates to vote counts and user reputation.
๐Ÿ’ก Hint
Use synchronized methods or locks to atomically check if user has voted and update vote count and reputation.
public synchronized boolean castVote(User user, VoteType type) - synchronized to atomically check-and-set vote existence and update counts
Mid: Uses synchronized to avoid race conditions. Senior: Traces thread interleaving where two threads simultaneously check vote existence leading to double voting without synchronization.
โš ๏ธCommon Wrong Answer
Not synchronizing castVote allows race condition: two threads read no vote, both add vote, causing double counting and inconsistent reputation.
Step 3
Model Tags and their relationship with Questions
Design Tag class and associate it with Question. Decide relationship type and lifecycle ownership.
๐Ÿ’ก Hint
Use aggregation: Questions reference Tags but Tags exist independently; Tags can be reused across questions.
Aggregation relationship: Question holds List<Tag> references; Tag lifecycle independent of Question
Mid: Correctly models many-to-many association. Senior: Explains aggregation vs composition and why Tag should not be composed inside Question to allow reuse.
โš ๏ธCommon Wrong Answer
Using composition for Tag inside Question causes Tags to be destroyed with Questions, preventing reuse and violating single responsibility.
Step 4
Design Reputation system with concurrency control
Implement Reputation class to track user points and update them safely when votes occur.
๐Ÿ’ก Hint
Use AtomicInteger for reputation points to allow lock-free thread-safe increments and decrements.
AtomicInteger reputationPoints - allows atomic increments/decrements without explicit synchronization
Mid: Uses AtomicInteger for thread-safe updates. Senior: Explains how AtomicInteger prevents lost updates compared to non-atomic int with synchronization overhead.
โš ๏ธCommon Wrong Answer
Using int without synchronization causes lost updates when multiple threads update reputation concurrently.
Step 5
Implement Bounty with expiration and lifecycle management
Design Bounty class linked to Question with expiration time. Ensure bounty lifecycle is managed correctly.
๐Ÿ’ก Hint
Use composition: Question owns Bounty instance; bounty expires after fixed time using volatile boolean flag and scheduled executor.
volatile boolean isExpired in Bounty to ensure visibility across threads; composition relationship: Question owns Bounty lifecycle
Mid: Uses volatile to ensure visibility of expiration flag. Senior: Explains how without volatile, one thread may not see updated expiration state causing stale bounty acceptance.
โš ๏ธCommon Wrong Answer
Not using volatile for isExpired causes threads to see stale value, allowing bounty to be accepted after expiration.
๐Ÿ”Œ
API Design Challenge
Design RESTful APIs to support posting questions and answers, casting votes, tagging questions, managing reputation, and handling bounties with expiration.
  • Use resource-oriented endpoints like /questions, /answers, /votes, /tags, /users/reputation, /bounties.
  • Ensure idempotency and concurrency control in vote casting APIs to prevent double voting.
  • Use asynchronous processing or scheduled tasks to handle bounty expiration and reputation updates.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class Post with instance state for shared fields
  • synchronized method for thread-safe vote casting
  • AtomicInteger for thread-safe reputation updates
  • Aggregation relationship for Question-Tag association
  • volatile flag for bounty expiration visibility
Good to Have - 30%
  • Immutable Vote objects
  • Enum VoteType for compile-time safety
  • Composition for Question-Bounty lifecycle
  • ScheduledExecutorService for bounty expiration
  • User vote tracking to prevent duplicate votes
Bonus - 10%
  • Explain exact thread interleaving causing race conditions without synchronization
  • Use double-checked locking with volatile for singleton pattern if applicable
  • Explain SOLID principles enforced by design choices
Using interface with instance fields causes compilation errors in Java because interfaces cannot hold instance state, leading to compile-time failure.
Not synchronizing vote casting leads to race conditions where multiple threads can simultaneously add votes, causing double counting and inconsistent vote counts and reputation.
Using String instead of enum for VoteType causes runtime typo bugs and poor IDE support, increasing risk of invalid vote types and harder maintenance.
Using composition for Tag inside Question prevents tag reuse and violates single responsibility principle, as Tags get destroyed with Questions and cannot be shared.
Not using AtomicInteger for reputation causes lost updates under concurrency, resulting in inaccurate reputation scores due to race conditions.
Not using volatile for bounty expiration flag causes stale reads and incorrect bounty acceptance, as threads may not see updated expiration state promptly.
โœ…
Model Answer
โ–ผ Reveal

The design uses an abstract class Post to hold shared state and behavior for Question and Answer, since interfaces cannot hold instance fields in Java. Voting is handled via a synchronized method castVote to atomically check if a user has voted and update vote counts and reputation safely. Tags are associated with Questions via aggregation to allow tag reuse and independent lifecycle. Reputation is managed using AtomicInteger to allow lock-free thread-safe updates. Bounty lifecycle is managed by composition with Question, using a volatile boolean flag isExpired and ScheduledExecutorService to handle expiration asynchronously.

  • Use abstract class Post instead of interface to hold shared instance state and behavior
  • Synchronize castVote method to prevent race conditions and double voting
  • Use AtomicInteger for reputation points to allow thread-safe increments/decrements without locks
  • Model Question-Tag relationship as aggregation to allow tag reuse and independent lifecycle
  • Use volatile boolean isExpired in Bounty to ensure visibility of expiration state across threads
  • Use composition for Question owning Bounty lifecycle to tightly couple bounty expiration with question
  • Make Vote immutable to prevent modification after casting
  • Use enum VoteType to prevent runtime errors and enable exhaustive switch statements
  • Track user votes in User class to prevent multiple votes on same post
  • Use ScheduledExecutorService to handle bounty expiration asynchronously and reliably
  • abstract class Post with shared fields (id, content, author, votes) because interface cannot hold instance state in Java
  • synchronized castVote method to atomically check if user has voted and update vote counts and reputation
  • Aggregation relationship between Question and Tag to allow tag reuse and independent lifecycle
  • AtomicInteger for Reputation points to allow lock-free thread-safe increments and decrements
  • volatile boolean isExpired in Bounty to guarantee visibility of expiration state across threads
  • Composition relationship: Question owns Bounty lifecycle; bounty expires after fixed time using scheduled executor
  • Vote as immutable object to prevent vote modification after casting
  • Use enum VoteType {UPVOTE, DOWNVOTE} to prevent runtime typo bugs and enable exhaustive switch
  • User class maintains map of voted posts to prevent multiple votes on same post
  • Use ScheduledExecutorService to handle bounty expiration asynchronously

Practice

(1/5)
1. In designing a logging framework, should the LogFormatter be an abstract class or an interface? Consider that formatters have no shared state but must implement a format(LogRecord) method. Why?
easy
A. Interface, because formatters only define behavior without shared state.
B. Abstract class, to allow shared state and default method implementations.
C. Abstract class, because Java interfaces cannot have methods with parameters.
D. Interface, to enforce singleton instances of formatters.

Solution

  1. Step 1: Analyze formatter requirements

    Formatters define a behavior (format method) without shared state or common implementation, making interface a natural fit.
  2. Step 2: Why interface over abstract class

    Interfaces allow multiple implementations without forcing inheritance or shared state. Abstract classes imply shared state or default behavior, which is unnecessary here.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    No shared state + behavior only -> interface [check]
Hint: Behavior only, no state -> interface [check]
Common Mistakes:
2. In the Meeting Scheduler system, a MeetingManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here, and why is this the correct choice for managing meeting schedules?
easy
A. Singleton pattern, to ensure only one instance manages all meeting schedules and avoids conflicting updates.
B. Factory pattern, because it creates different meeting types dynamically.
C. Builder pattern, to construct complex meeting objects step-by-step.
D. Prototype pattern, to clone existing meeting instances for recurring events.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() ensures a single shared instance, matching Singleton pattern characteristics.
  2. Step 2: Reason why Singleton fits MeetingManager

    MeetingManager must coordinate all meetings centrally to avoid conflicting schedules; multiple instances could cause inconsistent state.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
3. In a multi-threaded parking system using the Decorator pattern, the parkVehicle() method checks if a spot is available and then marks it occupied. Without synchronization, which exact thread interleaving causes double-booking?
medium
A. Thread 1 checks spot available -> true, context switch; Thread 2 checks spot available -> true; both mark spot occupied concurrently.
B. Thread 1 marks spot occupied, Thread 2 waits and then parks safely.
C. Thread 1 parks vehicle, Thread 2 reads spot status after parking completes.
D. Thread 1 and Thread 2 both wait on a lock, preventing double-booking.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads spot as available (true) but before marking it occupied, context switches.
  2. Step 2: Trace Thread 2's actions

    Thread 2 also reads spot as available (true) and proceeds to mark it occupied. Both threads think the spot is free and park vehicles, causing double-booking.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check-then-act without sync allows both threads to park simultaneously [check]
Hint: Check-then-act gap causes double-booking without sync [check]
Common Mistakes:
4. In the Restaurant Management System, the TableManager class has a method reserveTable() that checks if a table is available and then marks it reserved. Without synchronizing reserveTable(), which exact sequence of thread interleaving causes double-booking?
medium
A. Thread 1 and Thread 2 both wait on a lock, preventing any reservation.
B. Thread 1 and Thread 2 both call reserveTable() simultaneously and both fail to reserve.
C. Thread 1 reserves a table, then Thread 2 cancels it immediately after.
D. Thread 1 checks isAvailable() -> true, context switch; Thread 2 checks isAvailable() -> true; both reserve the same table.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads table as available but before reserving, Thread 2 also reads it as available.
  2. Step 2: Both threads proceed to reserve the same table

    Because the reservation step is not atomic, both threads mark the table reserved, causing double-booking.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Check-then-act without lock -> double-booking [check]
Hint: Check-then-act gap -> double-booking race [check]
Common Mistakes:
5. You want to add a WeekendFeeStrategy to the existing pricing module of the in-memory SQL database's query cost estimation. Which minimum change respects the Open-Closed Principle (OCP)?
medium
A. Modify the existing FeeCalculator class to add weekend logic inside the calculateFee() method.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes.
C. Add a boolean isWeekend flag to the FeeStrategy interface and implement conditional logic in all strategies.
D. Create a subclass WeekendFeeCalculator extending FeeCalculator and override calculateFee().

Solution

  1. Step 1: Understand OCP requirements

    OCP requires software entities to be open for extension but closed for modification.
  2. Step 2: Evaluate options against OCP

    Modify the existing FeeCalculator class to add weekend logic inside the calculateFee() method. modifies existing class, violating OCP. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes. adds new strategy class without modifying existing code, respecting OCP. Add a boolean isWeekend flag to the FeeStrategy interface and implement conditional logic in all strategies. adds flag to interface, forcing all implementations to change, violating OCP. Create a subclass WeekendFeeCalculator extending FeeCalculator and override calculateFee(). subclasses but may require client changes, less flexible than strategy injection.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New strategy class + injection -> OCP respected [check]
Hint: Add new strategy class, don't modify existing code [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard