OOD - Stack Overflow
Problem Statement
Stack Overflow revolutionized technical knowledge sharing by creating a gamified Q&A platform where developers collaboratively solve programming challenges. The system must support millions of users posting questions tagged with relevant technologies, providing detailed answers with code snippets and images, and engaging through upvotes, comments, and acceptance mechanisms. Unlike traditional forums, Stack Overflow employs a reputation system that rewards quality contributions with privileges (editing, moderation, bounties), fostering a self-policing community that maintains high content standards.
The platform serves diverse user personas: guest visitors searching for solutions without accounts, registered members asking questions and contributing answers to build reputation, moderators who close duplicate/off-topic questions and manage flags, admins who handle account suspensions and system-wide policies, and automated systems that award badges, track tag popularity trends, expire bounties, and send notifications. A critical challenge is preventing spam and low-quality content through progressive trust levelsβnew users have limited privileges until they demonstrate expertise through upvoted contributions.
Key technical challenges include: preventing duplicate votes on the same post by the same user, managing tag ecosystems with daily/weekly frequency tracking for trending technologies, implementing bounty mechanics where question authors sacrifice reputation points (non-refundable, expires after deadline) to attract expert attention, coordinating answer acceptance (only question owner can accept, only one answer accepted per question), enforcing reputation-based privileges (e.g., 15 reputation to upvote, 3,000 to vote to close), tracking badge eligibility through complex rules (e.g., "Nice Answer" for 10+ upvotes, "Guru" for accepted answer with 40+ score), and scaling search across millions of questions with full-text indexing on titles, descriptions, tags, and code blocks.
Solution
1. Requirements Analysis
Functional Requirements
User Management & Authentication
- Guest users can browse and search questions without authentication
- Registration requires email verification and profile creation (name, location, bio)
- Members have reputation scores tracking contribution quality
- Account statuses: Active, Suspended, Blocked, Deleted with audit trails
- Reputation-based privilege system (15 for upvote, 50 for comment, 3000 for close vote)
Question & Answer Operations 6. Members can post questions with title (150 char max), body (markdown + code blocks), and 1-5 tags 7. Questions support optional bounties (minimum 50 reputation, doubles on renewal, expires in 7 days) 8. Members can answer any open question with markdown formatting and inline images 9. Question owners can accept exactly one answer (grants 15 reputation to answerer, 2 to acceptor) 10. Questions have statuses: Open, Closed, On Hold, Deleted, Locked 11. Close reasons: Duplicate, Off-Topic, Too Broad, Primarily Opinion-Based, Unclear
Voting & Engagement 12. Members can upvote questions (+10 to author, -0 to voter) and answers (+10 to author, -0 to voter) 13. Members can downvote questions/answers (-2 to author, -1 to voter) 14. One vote per user per post, votes are anonymous, users can change votes within grace period 15. Members can comment on questions and answers (requires 50 reputation) 16. Flagging system for spam, offensive content, moderator attention (requires 15 reputation)
Tagging & Discovery 17. Tags have names, descriptions, and wiki pages managed by high-reputation users 18. Daily and weekly tag usage frequencies tracked for trending analysis 19. Tag synonyms managed by moderators (e.g., "javascript" β "js") 20. Search supports filters: tags, dates, vote ranges, accepted answers, closed status
Gamification & Reputation 21. Badges awarded for achievements: bronze (easy), silver (medium), gold (hard) 22. Badge types: tag badges (100+ upvotes in specific tag), participation badges (Supporter, Critic), quality badges (Nice Answer, Great Question) 23. Reputation changes logged with event attribution (upvote, accepted answer, bounty awarded) 24. Reputation caps: +200 per day from upvotes (excluding accepts and bounties)
Moderation & Administration 25. Moderators can close, reopen, delete, undelete, lock any question 26. Close votes require 5 community members (3000+ reputation) or 1 moderator 27. Delete votes require 3 members with 10,000+ reputation 28. Admins can suspend accounts, merge duplicate accounts, destroy spam accounts 29. Edit history tracked for all posts with revision diff visualization
Notifications 30. Real-time notifications for: answer to your question, comment on your post, badge awarded, bounty expiring soon, edit to your post, moderator action 31. Email digest options: daily, weekly, real-time, never
Non-Functional Requirements
Performance
- Search results return within 200ms for 95th percentile queries
- Page load time <1 second for questions with <50 answers
- Vote registration acknowledged within 100ms
- Support 10,000 concurrent users
Scalability 5. Scale to 50+ million questions, 100+ million answers 6. Handle 1000+ questions posted per day 7. Support 50,000+ tags with synonym chains
Availability & Reliability 8. 99.9% uptime SLA (max 43 minutes downtime/month) 9. Zero data loss for accepted answers and reputation events 10. Graceful degradation during search index rebuilds
Security 11. Prevent duplicate voting through client + server-side validation 12. Rate limiting: 10 questions/day for new users, 50 for established users 13. CAPTCHA for suspicious activity patterns 14. SQL injection and XSS protection in markdown rendering
Auditability 15. All moderation actions logged with moderator ID and timestamp 16. Reputation change audit trail with event source 17. Vote fraud detection through anomaly patterns
Extensibility 18. Plugin system for new badge award rules 19. Customizable close reasons per site (Stack Overflow vs. Math Stack Exchange) 20. Internationalization support for 10+ languages
2. Use Case Diagram
graph TB %% Actors Guest["π₯ Guest User"] Member["π€ Member"] Moderator["π‘οΈ Moderator"] Admin["π§ Admin"] System["π€ System"] %% Content Discovery (Blue) subgraph Discovery["π Content Discovery"] style Discovery fill:#E3F2FD UC1["Search Questions"] UC2["Browse by Tag"] UC3["View Question Details"] UC4["View User Profile"] UC5["Subscribe to Tag"] end %% Content Creation (Green) subgraph Creation["βοΈ Content Creation"] style Creation fill:#E8F5E9 UC6["Post Question"] UC7["Add Answer"] UC8["Add Comment"] UC9["Edit Own Post"] UC10["Delete Own Post"] UC11["Attach Code Snippet"] UC12["Upload Image"] end %% Voting & Engagement (Orange) subgraph Engagement["β Voting & Engagement"] style Engagement fill:#FFF3E0 UC13["Upvote Question/Answer"] UC14["Downvote Question/Answer"] UC15["Accept Answer"] UC16["Flag Post"] UC17["Bookmark Question"] UC18["Share Question"] end %% Gamification (Purple) subgraph Gamification["π Gamification & Reputation"] style Gamification fill:#F3E5F5 UC19["Add Bounty"] UC20["Award Bounty"] UC21["Earn Badge"] UC22["Track Reputation"] UC23["View Privileges"] end %% Tagging (Teal) subgraph Tagging["π·οΈ Tag Management"] style Tagging fill:#E0F2F1 UC24["Create Tag"] UC25["Edit Tag Wiki"] UC26["Create Tag Synonym"] UC27["Track Tag Frequency"] UC28["View Trending Tags"] end %% Moderation (Red) subgraph Moderation["π¨ Moderation & Administration"] style Moderation fill:#FFEBEE UC29["Close Question"] UC30["Reopen Question"] UC31["Delete Question"] UC32["Undelete Question"] UC33["Lock Post"] UC34["Merge Duplicate Questions"] UC35["Review Flag Queue"] UC36["Suspend Account"] UC37["Destroy Spam Account"] UC38["View Audit Logs"] end %% Notifications (Yellow) subgraph Notifications["π Notifications"] style Notifications fill:#FFFDE7 UC39["Send Answer Notification"] UC40["Send Badge Award"] UC41["Send Bounty Expiry Warning"] UC42["Send Comment Reply"] UC43["Send Moderator Action Notice"] end %% Guest Connections Guest --> UC1 Guest --> UC2 Guest --> UC3 Guest --> UC4 %% Member Connections - Discovery Member --> UC1 Member --> UC2 Member --> UC3 Member --> UC4 Member --> UC5 %% Member Connections - Creation Member --> UC6 Member --> UC7 Member --> UC8 Member --> UC9 Member --> UC10 Member --> UC11 Member --> UC12 %% Member Connections - Engagement Member --> UC13 Member --> UC14 Member --> UC15 Member --> UC16 Member --> UC17 Member --> UC18 %% Member Connections - Gamification Member --> UC19 Member --> UC20 Member --> UC22 Member --> UC23 %% Member Connections - Tagging Member --> UC24 Member --> UC25 %% Moderator Connections (inherits Member) Moderator --> UC26 Moderator --> UC29 Moderator --> UC30 Moderator --> UC31 Moderator --> UC32 Moderator --> UC33 Moderator --> UC34 Moderator --> UC35 %% Admin Connections Admin --> UC36 Admin --> UC37 Admin --> UC38 %% System Connections System --> UC21 System --> UC27 System --> UC28 System --> UC39 System --> UC40 System --> UC41 System --> UC42 System --> UC43 %% Styling classDef guestStyle fill:#B0BEC5,stroke:#546E7A,stroke-width:2px classDef memberStyle fill:#81C784,stroke:#388E3C,stroke-width:2px classDef modStyle fill:#FFB74D,stroke:#F57C00,stroke-width:2px classDef adminStyle fill:#E57373,stroke:#C62828,stroke-width:2px classDef systemStyle fill:#9575CD,stroke:#512DA8,stroke-width:2px class Guest guestStyle class Member memberStyle class Moderator modStyle class Admin adminStyle class System systemStyle
Key Actors:
- π₯ Guest User: Unauthenticated visitors with read-only access
- π€ Member: Registered users who can create content and earn reputation
- π‘οΈ Moderator: Elected community members with moderation privileges (inherits Member capabilities)
- π§ Admin: Platform administrators with full system control
- π€ System: Automated processes for badges, notifications, and analytics
Use Case Categories:
- π Discovery (5 use cases): Search, browse, and explore content
- βοΈ Creation (7 use cases): Post questions, answers, comments with rich media
- β Engagement (6 use cases): Vote, accept, flag, bookmark content
- π Gamification (5 use cases): Bounties, badges, reputation tracking
- π·οΈ Tagging (5 use cases): Tag creation, wiki editing, synonym management
- π¨ Moderation (10 use cases): Content management, user discipline, audit trails
- π Notifications (5 use cases): Real-time and email notifications for events
3. Class Diagram
classDiagram %% ==================== %% User Management %% ==================== class Account { <<abstract>> -String accountId -String email -String passwordHash -String name -String location -String bio -AccountStatus status -LocalDateTime createdAt -LocalDateTime lastLoginAt +authenticate(password) bool +resetPassword(newPassword) void +suspend(reason) void } class Guest { -String sessionId -LocalDateTime sessionStart +browseQuestions() List~Question~ +search(query) List~Question~ } class Member { -String memberId -int reputation -LocalDate memberSince -List~Badge~ badges -List~Privilege~ privileges -NotificationSettings notificationSettings +postQuestion(question) Question +addAnswer(questionId, answer) Answer +addComment(postId, comment) Comment +upvote(postId) bool +downvote(postId) bool +acceptAnswer(answerId) bool +addBounty(questionId, amount) Bounty +createTag(tag) Tag +hasPrivilege(privilegeType) bool +gainReputation(amount, reason) void +loseReputation(amount, reason) void } class Moderator { -LocalDate electedAt -List~ModerationAction~ actions +closeQuestion(questionId, reason) void +reopenQuestion(questionId) void +deletePost(postId) void +undeletePost(postId) void +lockPost(postId) void +mergeDuplicates(originalId, duplicateId) void +handleFlag(flagId) void } class Admin { -List~String~ permissions +suspendAccount(accountId, duration, reason) void +destroySpamAccount(accountId) void +viewAuditLogs(filters) List~AuditLog~ +managePrivileges(memberId, privilege) void +configureCloseReasons(siteId, reasons) void } class AccountStatus { <<enumeration>> ACTIVE SUSPENDED BLOCKED DELETED } class Privilege { -String name -int requiredReputation -String description +check(memberReputation) bool } class NotificationSettings { -bool emailOnAnswer -bool emailOnComment -bool emailOnBadge -DigestFrequency digestFrequency +update(settings) void } %% ==================== %% Content Models %% ==================== class Question { -String questionId -String title -String body -int viewCount -int voteCount -QuestionStatus status -LocalDateTime createdAt -LocalDateTime updatedAt -LocalDateTime closedAt -Member author -List~Tag~ tags -List~Answer~ answers -List~Comment~ comments -List~Photo~ photos -Bounty bounty -CloseReason closeReason -List~Edit~ editHistory +addAnswer(answer) void +addComment(comment) void +addTag(tag) void +close(reason, moderator) void +reopen(moderator) void +acceptAnswer(answerId) bool +addBounty(bounty) void +incrementViews() void +calculateScore() int } class Answer { -String answerId -String body -int voteCount -bool isAccepted -LocalDateTime acceptedAt -LocalDateTime createdAt -Member author -Question question -List~Comment~ comments -List~Photo~ photos -List~Edit~ editHistory +accept(questionOwner) bool +addComment(comment) void +calculateScore() int } class Comment { -String commentId -String text -int voteCount -LocalDateTime createdAt -Member author -Post parent +upvote(member) bool +flag(member, reason) void } class Post { <<interface>> +vote(member, voteType) bool +flag(member, reason) void +edit(member, newContent) void +delete(member) void } class QuestionStatus { <<enumeration>> OPEN CLOSED ON_HOLD DELETED LOCKED } class CloseReason { <<enumeration>> DUPLICATE OFF_TOPIC TOO_BROAD PRIMARILY_OPINION_BASED UNCLEAR NOT_REPRODUCIBLE } class Photo { -String photoId -String url -String caption -long fileSize -LocalDateTime uploadedAt -Member uploader +delete() void } class Edit { -String editId -Member editor -String oldContent -String newContent -String editSummary -LocalDateTime editedAt +getDiff() String +revert() void } %% ==================== %% Tagging System %% ==================== class Tag { -String tagId -String name -String description -String wikiContent -int dailyFrequency -int weeklyFrequency -int totalQuestions -LocalDateTime createdAt -List~TagSynonym~ synonyms +incrementFrequency() void +updateWiki(content, editor) void +addSynonym(synonym) void } class TagSynonym { -String synonymId -String originalTag -String synonymTag -Member createdBy -LocalDateTime createdAt -int voteCount +approve() void } class TagSubscription { -Member member -Tag tag -bool emailNotifications -LocalDateTime subscribedAt +unsubscribe() void } %% ==================== %% Voting System %% ==================== class Vote { -String voteId -Member voter -Post post -VoteType type -LocalDateTime votedAt +reverse() void } class VoteType { <<enumeration>> UPVOTE DOWNVOTE } %% ==================== %% Gamification %% ==================== class Bounty { -String bountyId -int amount -LocalDateTime startedAt -LocalDateTime expiresAt -Member offeredBy -Question question -Answer awardedTo -BountyStatus status +award(answer) void +expire() void +renew(additionalAmount) void } class BountyStatus { <<enumeration>> ACTIVE AWARDED EXPIRED CANCELED } class Badge { -String badgeId -String name -String description -BadgeType type -BadgeLevel level -String iconUrl +award(member) void } class BadgeType { <<enumeration>> TAG_BASED PARTICIPATION MODERATION QUESTION_QUALITY ANSWER_QUALITY } class BadgeLevel { <<enumeration>> BRONZE SILVER GOLD } class BadgeAward { -String awardId -Badge badge -Member recipient -LocalDateTime awardedAt -String reason } class ReputationEvent { -String eventId -Member member -int delta -ReputationEventType type -LocalDateTime occurredAt -String sourceId +apply() void +rollback() void } class ReputationEventType { <<enumeration>> UPVOTE_QUESTION UPVOTE_ANSWER DOWNVOTE_QUESTION DOWNVOTE_ANSWER ACCEPTED_ANSWER ACCEPT_ANSWER BOUNTY_AWARDED BOUNTY_OFFERED PENALTY_SPAM } %% ==================== %% Flagging & Moderation %% ==================== class Flag { -String flagId -Member flagger -Post flaggedPost -FlagReason reason -String description -FlagStatus status -LocalDateTime flaggedAt -LocalDateTime resolvedAt -Moderator resolvedBy +resolve(action) void +dismiss() void } class FlagReason { <<enumeration>> SPAM OFFENSIVE LOW_QUALITY DUPLICATE NEEDS_MODERATOR_ATTENTION } class FlagStatus { <<enumeration>> PENDING HELPFUL DECLINED DISPUTED } class ModerationAction { -String actionId -Moderator moderator -String targetId -ActionType actionType -String reason -LocalDateTime performedAt +log() void } class ActionType { <<enumeration>> CLOSE_QUESTION REOPEN_QUESTION DELETE_POST UNDELETE_POST LOCK_POST UNLOCK_POST MERGE_DUPLICATES } %% ==================== %% Notifications %% ==================== class Notification { -String notificationId -Member recipient -NotificationType type -String content -String linkUrl -bool isRead -LocalDateTime createdAt +markAsRead() void +send() void } class NotificationType { <<enumeration>> ANSWER_POSTED COMMENT_REPLY BADGE_AWARDED BOUNTY_EXPIRING QUESTION_CLOSED EDIT_SUGGESTED MENTION } %% ==================== %% Search & Analytics %% ==================== class SearchEngine { <<interface>> +search(query, filters) SearchResult +indexQuestion(question) void +indexAnswer(answer) void } class SearchResult { -List~Question~ questions -int totalCount -int page -Map~String,int~ facets +getNextPage() SearchResult } class SearchFilter { -List~String~ tags -DateRange dateRange -VoteRange voteRange -bool hasAcceptedAnswer -bool isClosed +apply(questions) List~Question~ } class Analytics { -Map~String,int~ tagFrequencies -List~TrendingTag~ trendingTags +updateTagFrequency(tag) void +calculateTrending() List~TrendingTag~ +getUserStatistics(memberId) UserStats } class TrendingTag { -Tag tag -int dailyCount -int weeklyCount -double growthRate -int rank } %% ==================== %% Relationships %% ==================== Account <|-- Guest Account <|-- Member Member <|-- Moderator Member <|-- Admin Account --> AccountStatus Member --> Privilege Member --> NotificationSettings Member --> Badge : earns Member --> ReputationEvent : has Question --> QuestionStatus Question --> CloseReason Question --> Tag Question --> Answer Question --> Comment Question --> Photo Question --> Edit Question --> Bounty Question ..|> Post Answer --> Comment Answer --> Photo Answer --> Edit Answer ..|> Post Comment ..|> Post Tag --> TagSynonym Tag --> TagSubscription Vote --> VoteType Vote --> Post Vote --> Member Bounty --> BountyStatus Bounty --> Member : offered by Bounty --> Answer : awarded to Badge --> BadgeType Badge --> BadgeLevel BadgeAward --> Badge BadgeAward --> Member ReputationEvent --> ReputationEventType ReputationEvent --> Member Flag --> FlagReason Flag --> FlagStatus Flag --> Post Flag --> Member : flagger Flag --> Moderator : resolver ModerationAction --> ActionType ModerationAction --> Moderator Notification --> NotificationType Notification --> Member SearchResult --> Question SearchFilter --> SearchResult Analytics --> TrendingTag TrendingTag --> Tag
Key Design Modules:
- User Management (8 classes): Account hierarchy, privileges, notification settings
- Content Models (7 classes): Question, Answer, Comment with rich editing and media
- Tagging System (3 classes): Tags with synonyms and subscriptions
- Voting System (2 classes): Vote tracking with type differentiation
- Gamification (7 classes): Bounties, badges, reputation events
- Moderation (4 classes): Flags, moderation actions, audit trails
- Notifications (2 classes): Event-driven notifications with preferences
- Search & Analytics (5 classes): Full-text search, trending analysis
Total Classes: 50+ classes demonstrating comprehensive domain modeling
4. Activity Diagrams
Activity: Post New Question with Bounty
flowchart TD Start([Member clicks 'Ask Question']) --> CheckPriv{Has 'Ask' privilege?<br/>Reputation β₯ 1} CheckPriv -- No --> ShowError1[Show error:<br/>'Need 1 reputation'] ShowError1 --> End1([End]) CheckPriv -- Yes --> EnterTitle[Enter title<br/>Max 150 characters] EnterTitle --> EnterBody[Enter body with<br/>markdown + code blocks] EnterBody --> SelectTags[Select 1-5 tags] SelectTags --> ValidateTags{Tags valid?} ValidateTags -- Invalid tag --> CreateTag{Create new tag?<br/>Requires 1500 reputation} CreateTag -- No --> SelectTags CreateTag -- Yes --> ValidateTags ValidateTags -- Valid --> AddBounty{Add bounty?} AddBounty -- No --> ReviewPreview[Review preview] AddBounty -- Yes --> CheckBountyRep{Reputation β₯ 75?<br/>Minimum bounty = 50} CheckBountyRep -- No --> ShowError2[Show error:<br/>'Need 75 reputation'] ShowError2 --> ReviewPreview CheckBountyRep -- Yes --> EnterBounty[Enter bounty amount<br/>50, 100, 200, 300, 400, 500] EnterBounty --> SetExpiry[Set expiry:<br/>7 days from now] SetExpiry --> DeductRep[Deduct reputation<br/>immediately] DeductRep --> ReviewPreview ReviewPreview --> Submit[Submit question] Submit --> Validate{Passes spam<br/>detection?} Validate -- Failed --> ShowError3[Reject: Rate limit<br/>or spam detected] ShowError3 --> End2([End]) Validate -- Passed --> CreateQuestion[Create Question entity<br/>Status = OPEN] CreateQuestion --> IndexQuestion[Index for search<br/>Title + body + tags] IndexQuestion --> UpdateTagFreq[Update tag<br/>daily/weekly frequency] UpdateTagFreq --> CreateNotif[Create notifications<br/>for tag subscribers] CreateNotif --> CheckBountyCreated{Bounty created?} CheckBountyCreated -- Yes --> ScheduleExpiry[Schedule bounty<br/>expiry job for 7 days] ScheduleExpiry --> RecordRepEvent[Record reputation event:<br/>BOUNTY_OFFERED] RecordRepEvent --> ShowSuccess[Show success:<br/>Redirect to question] CheckBountyCreated -- No --> ShowSuccess ShowSuccess --> End3([End])
Activity: Vote on Question or Answer
flowchart TD Start([Member clicks upvote/downvote]) --> CheckPriv{Has 'Vote Up'<br/>privilege?<br/>Rep β₯ 15} CheckPriv -- No --> ShowError1[Show error:<br/>'Need 15 reputation to upvote'] ShowError1 --> End1([End]) CheckPriv -- Yes --> CheckDownvote{Is downvote?} CheckDownvote -- Yes --> CheckDownPriv{Has 'Vote Down'<br/>privilege?<br/>Rep β₯ 125} CheckDownPriv -- No --> ShowError2[Show error:<br/>'Need 125 reputation to downvote'] ShowError2 --> End2([End]) CheckDownPriv -- Yes --> ProceedVote1[Proceed] CheckDownvote -- No --> ProceedVote1 ProceedVote1 --> CheckDuplicate{Already voted<br/>on this post?} CheckDuplicate -- Yes --> CheckReverse{Same vote type?} CheckReverse -- Yes --> RemoveVote[Remove vote:<br/>Reverse reputation] RemoveVote --> UpdateScore1[Update post<br/>vote count] UpdateScore1 --> RecordRepEvent1[Record reputation<br/>event reversal] RecordRepEvent1 --> ShowSuccess1[Show updated count] ShowSuccess1 --> End3([End]) CheckReverse -- No --> ChangeVote[Change vote:<br/>Adjust reputation] ChangeVote --> UpdateScore2[Update post<br/>vote count by Β±2] UpdateScore2 --> RecordRepEvent2[Record reputation<br/>event change] RecordRepEvent2 --> ShowSuccess2[Show updated count] ShowSuccess2 --> End4([End]) CheckDuplicate -- No --> CheckDailyLimit{Daily reputation<br/>cap reached?<br/>+200 max} CheckDailyLimit -- Yes --> WarnLimit[Warn: Vote counts<br/>but no reputation gain] CheckDailyLimit -- No --> ProceedVote2[Proceed] WarnLimit --> ProceedVote2 ProceedVote2 --> CreateVote[Create Vote entity:<br/>Member + Post + VoteType] CreateVote --> UpdateScore3[Update post<br/>vote count] UpdateScore3 --> VoteType{Vote type?} VoteType -- Upvote Question --> AuthorGain1[Author gains<br/>+10 reputation] VoteType -- Upvote Answer --> AuthorGain2[Author gains<br/>+10 reputation] VoteType -- Downvote Question --> AuthorLoss1[Author loses<br/>-2 reputation] VoteType -- Downvote Answer --> AuthorLoss2[Author loses<br/>-2 reputation] AuthorGain1 --> VoterCost{Downvote?} AuthorGain2 --> VoterCost AuthorLoss1 --> VoterCost AuthorLoss2 --> VoterCost VoterCost -- Yes --> VoterLoss[Voter loses<br/>-1 reputation] VoterCost -- No --> RecordRepEvent3[Record reputation<br/>events] VoterLoss --> RecordRepEvent3 RecordRepEvent3 --> CheckBadge{Check badge<br/>eligibility?} CheckBadge -- Post score β₯ 10 --> AwardBadge[Award 'Nice Answer/<br/>Question' badge] CheckBadge -- Post score β₯ 25 --> AwardBadge AwardBadge --> ShowSuccess3[Show updated count] CheckBadge -- No --> ShowSuccess3 ShowSuccess3 --> End5([End])
Activity: Accept Answer (Question Owner)
flowchart TD Start([Question owner clicks<br/>'Accept Answer']) --> CheckOwner{Is question owner?} CheckOwner -- No --> ShowError1[Show error:<br/>'Only question owner can accept'] ShowError1 --> End1([End]) CheckOwner -- Yes --> CheckStatus{Question status?} CheckStatus -- CLOSED/DELETED --> ShowError2[Show error:<br/>'Cannot accept on closed question'] ShowError2 --> End2([End]) CheckStatus -- OPEN --> CheckPrevious{Already has<br/>accepted answer?} CheckPrevious -- Yes --> UnacceptPrevious[Unaccept previous answer:<br/>isAccepted = false] UnacceptPrevious --> ReversePrevRep[Reverse reputation:<br/>-15 to previous answerer<br/>-2 to question owner] ReversePrevRep --> RecordUnaccept[Record reputation events:<br/>UNACCEPT_ANSWER] RecordUnaccept --> ProceedAccept[Proceed to accept new] CheckPrevious -- No --> ProceedAccept ProceedAccept --> AcceptAnswer[Set answer.isAccepted = true<br/>Set acceptedAt timestamp] AcceptAnswer --> GrantAnswererRep[Answerer gains<br/>+15 reputation] GrantAnswererRep --> GrantOwnerRep[Question owner gains<br/>+2 reputation] GrantOwnerRep --> RecordRepEvents[Record reputation events:<br/>ACCEPTED_ANSWER,<br/>ACCEPT_ANSWER] RecordRepEvents --> CheckBounty{Question has<br/>active bounty?} CheckBounty -- Yes --> AwardBounty[Award bounty to answerer:<br/>Add bounty amount to rep] AwardBounty --> MarkBountyAwarded[Mark bounty.status<br/>= AWARDED] MarkBountyAwarded --> RecordBountyEvent[Record reputation event:<br/>BOUNTY_AWARDED] RecordBountyEvent --> NotifyAnswerer1[Notify answerer:<br/>'Answer accepted + Bounty'] CheckBounty -- No --> NotifyAnswerer2[Notify answerer:<br/>'Answer accepted'] NotifyAnswerer1 --> CheckBadges{Check badge<br/>eligibility} NotifyAnswerer2 --> CheckBadges CheckBadges --> AnswererScore{Answer score β₯ 40?} AnswererScore -- Yes --> AwardGuru[Award 'Guru'<br/>gold badge] AwardGuru --> ShowSuccess[Show 'Accepted' checkmark<br/>in green] AnswererScore -- No --> ShowSuccess ShowSuccess --> End3([End])
Key Activity Flows:
-
Post Question with Bounty (32 nodes): Validates privileges, creates question with optional bounty, indexes for search, schedules expiry job, updates tag frequencies, creates notifications
-
Vote on Question/Answer (24 nodes): Checks vote privileges, prevents duplicate votes, enforces daily reputation cap, updates scores, applies reputation changes to both author and voter, triggers badge awards
-
Accept Answer (22 nodes): Validates ownership, handles previous acceptance reversal, grants reputation, automatically awards bounty if active, triggers badge checks (Guru badge), sends notifications
5. Java Implementation
Below is a comprehensive Java implementation demonstrating design patterns, SOLID principles, and production-ready code.
Enumerations and Value Objects
// Status enumerations
public enum AccountStatus {
ACTIVE, SUSPENDED, BLOCKED, DELETED
}
public enum QuestionStatus {
OPEN, CLOSED, ON_HOLD, DELETED, LOCKED
}
public enum CloseReason {
DUPLICATE("This question has been asked before"),
OFF_TOPIC("This question doesn't appear to be about programming"),
TOO_BROAD("This question is too broad to answer"),
PRIMARILY_OPINION_BASED("This question is likely to solicit opinion"),
UNCLEAR("This question is unclear or not useful"),
NOT_REPRODUCIBLE("Unable to reproduce the problem");
private final String description;
CloseReason(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
public enum VoteType {
UPVOTE(1), DOWNVOTE(-1);
private final int value;
VoteType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public enum BountyStatus {
ACTIVE, AWARDED, EXPIRED, CANCELED
}
public enum BadgeLevel {
BRONZE(1), SILVER(2), GOLD(3);
private final int tier;
BadgeLevel(int tier) {
this.tier = tier;
}
public int getTier() {
return tier;
}
}
public enum BadgeType {
TAG_BASED, // e.g., "Java Expert"
PARTICIPATION, // e.g., "Supporter", "Critic"
MODERATION, // e.g., "Deputy", "Marshal"
QUESTION_QUALITY, // e.g., "Nice Question", "Great Question"
ANSWER_QUALITY // e.g., "Nice Answer", "Guru"
}
public enum ReputationEventType {
UPVOTE_QUESTION(10, 0),
UPVOTE_ANSWER(10, 0),
DOWNVOTE_QUESTION(-2, -1),
DOWNVOTE_ANSWER(-2, -1),
ACCEPTED_ANSWER(15, 0), // Answerer gains
ACCEPT_ANSWER(2, 0), // Question owner gains
BOUNTY_AWARDED(0, 0), // Variable amount
BOUNTY_OFFERED(0, 0), // Deducts bounty amount
PENALTY_SPAM(-100, 0);
private final int authorDelta;
private final int voterDelta;
ReputationEventType(int authorDelta, int voterDelta) {
this.authorDelta = authorDelta;
this.voterDelta = voterDelta;
}
public int getAuthorDelta() {
return authorDelta;
}
public int getVoterDelta() {
return voterDelta;
}
}
public enum FlagReason {
SPAM, OFFENSIVE, LOW_QUALITY, DUPLICATE, NEEDS_MODERATOR_ATTENTION
}
public enum FlagStatus {
PENDING, HELPFUL, DECLINED, DISPUTED
}
public enum NotificationType {
ANSWER_POSTED, COMMENT_REPLY, BADGE_AWARDED,
BOUNTY_EXPIRING, QUESTION_CLOSED, EDIT_SUGGESTED, MENTION
}
User Management with Strategy Pattern for Privileges
import java.time.*;
import java.util.*;
// Privilege checking with Strategy pattern
public class Privilege {
private final String name;
private final int requiredReputation;
private final String description;
public Privilege(String name, int requiredReputation, String description) {
this.name = name;
this.requiredReputation = requiredReputation;
this.description = description;
}
public boolean check(int memberReputation) {
return memberReputation >= requiredReputation;
}
// Common privileges as constants
public static final Privilege UPVOTE = new Privilege("Upvote", 15, "Vote up questions and answers");
public static final Privilege DOWNVOTE = new Privilege("Downvote", 125, "Vote down questions and answers");
public static final Privilege COMMENT = new Privilege("Comment", 50, "Leave comments on any post");
public static final Privilege CREATE_TAG = new Privilege("Create Tag", 1500, "Create new tags");
public static final Privilege VOTE_TO_CLOSE = new Privilege("Vote to Close", 3000, "Vote to close questions");
public static final Privilege DELETE_VOTE = new Privilege("Delete Vote", 10000, "Vote to delete questions");
}
public abstract class Account {
protected final String accountId;
protected final String email;
protected String passwordHash;
protected String name;
protected String location;
protected String bio;
protected AccountStatus status;
protected final LocalDateTime createdAt;
protected LocalDateTime lastLoginAt;
public Account(String accountId, String email, String passwordHash, String name) {
this.accountId = accountId;
this.email = email;
this.passwordHash = passwordHash;
this.name = name;
this.status = AccountStatus.ACTIVE;
this.createdAt = LocalDateTime.now();
}
public boolean authenticate(String password) {
return this.passwordHash.equals(hashPassword(password));
}
private String hashPassword(String password) {
// Use BCrypt in production
return password; // Simplified
}
public void resetPassword(String newPassword) {
this.passwordHash = hashPassword(newPassword);
}
public void suspend(String reason) {
this.status = AccountStatus.SUSPENDED;
}
public String getAccountId() { return accountId; }
public AccountStatus getStatus() { return status; }
}
public class Member extends Account {
private final String memberId;
private int reputation;
private final LocalDate memberSince;
private final List<Badge> badges;
private final List<Vote> votes;
private final List<ReputationEvent> reputationHistory;
public Member(String accountId, String email, String passwordHash,
String name, String memberId) {
super(accountId, email, passwordHash, name);
this.memberId = memberId;
this.reputation = 1; // Starting reputation
this.memberSince = LocalDate.now();
this.badges = new ArrayList<>();
this.votes = new ArrayList<>();
this.reputationHistory = new ArrayList<>();
}
public boolean hasPrivilege(Privilege privilege) {
return privilege.check(this.reputation);
}
public void gainReputation(int amount, ReputationEventType type, String sourceId) {
this.reputation += amount;
ReputationEvent event = new ReputationEvent(this, amount, type, sourceId);
this.reputationHistory.add(event);
}
public void loseReputation(int amount, ReputationEventType type, String sourceId) {
this.reputation = Math.max(1, this.reputation - amount); // Never go below 1
ReputationEvent event = new ReputationEvent(this, -amount, type, sourceId);
this.reputationHistory.add(event);
}
public void awardBadge(Badge badge) {
if (!badges.contains(badge)) {
badges.add(badge);
}
}
public int getReputation() { return reputation; }
public String getMemberId() { return memberId; }
public List<Badge> getBadges() { return Collections.unmodifiableList(badges); }
}
public class Moderator extends Member {
private final LocalDate electedAt;
private final List<ModerationAction> actions;
public Moderator(String accountId, String email, String passwordHash,
String name, String memberId) {
super(accountId, email, passwordHash, name, memberId);
this.electedAt = LocalDate.now();
this.actions = new ArrayList<>();
}
public void closeQuestion(Question question, CloseReason reason) {
question.close(reason, this);
logAction(new ModerationAction(this, question.getQuestionId(),
ModerationActionType.CLOSE_QUESTION, reason.getDescription()));
}
public void reopenQuestion(Question question) {
question.reopen(this);
logAction(new ModerationAction(this, question.getQuestionId(),
ModerationActionType.REOPEN_QUESTION, "Reopened by moderator"));
}
public void deletePost(Post post) {
post.delete(this);
logAction(new ModerationAction(this, post.getPostId(),
ModerationActionType.DELETE_POST, "Deleted by moderator"));
}
private void logAction(ModerationAction action) {
actions.add(action);
}
}
public class Admin extends Member {
private final Set<String> permissions;
public Admin(String accountId, String email, String passwordHash,
String name, String memberId) {
super(accountId, email, passwordHash, name, memberId);
this.permissions = new HashSet<>();
initializePermissions();
}
private void initializePermissions() {
permissions.add("SUSPEND_ACCOUNT");
permissions.add("DESTROY_ACCOUNT");
permissions.add("MANAGE_PRIVILEGES");
permissions.add("VIEW_AUDIT_LOGS");
}
public void suspendAccount(Account account, Duration duration, String reason) {
if (permissions.contains("SUSPEND_ACCOUNT")) {
account.suspend(reason);
}
}
public void destroySpamAccount(Account account) {
if (permissions.contains("DESTROY_ACCOUNT")) {
// Permanently delete account and all content
}
}
}
Content Models with Template Method Pattern
import java.util.concurrent.atomic.AtomicInteger;
// Post interface for polymorphism
public interface Post {
String getPostId();
Member getAuthor();
int getVoteCount();
void vote(Member voter, VoteType type);
void flag(Member flagger, FlagReason reason);
void delete(Moderator moderator);
}
public class Question implements Post {
private final String questionId;
private final String title;
private String body;
private final Member author;
private QuestionStatus status;
private CloseReason closeReason;
private final LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime closedAt;
private int viewCount;
private final AtomicInteger voteCount;
private final List<Tag> tags;
private final List<Answer> answers;
private final List<Comment> comments;
private final List<Photo> photos;
private Bounty bounty;
private final List<Edit> editHistory;
public Question(String questionId, String title, String body, Member author) {
this.questionId = questionId;
this.title = title;
this.body = body;
this.author = author;
this.status = QuestionStatus.OPEN;
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
this.viewCount = 0;
this.voteCount = new AtomicInteger(0);
this.tags = new ArrayList<>();
this.answers = new ArrayList<>();
this.comments = new ArrayList<>();
this.photos = new ArrayList<>();
this.editHistory = new ArrayList<>();
}
public void addAnswer(Answer answer) {
if (status == QuestionStatus.OPEN) {
answers.add(answer);
} else {
throw new IllegalStateException("Cannot add answer to non-open question");
}
}
public void addComment(Comment comment) {
comments.add(comment);
}
public void addTag(Tag tag) {
if (tags.size() < 5) {
tags.add(tag);
tag.incrementFrequency();
}
}
public void close(CloseReason reason, Moderator moderator) {
this.status = QuestionStatus.CLOSED;
this.closeReason = reason;
this.closedAt = LocalDateTime.now();
}
public void reopen(Moderator moderator) {
if (status == QuestionStatus.CLOSED) {
this.status = QuestionStatus.OPEN;
this.closedAt = null;
}
}
public boolean acceptAnswer(String answerId, Member acceptor) {
if (!acceptor.equals(author)) {
return false;
}
// Unaccept previous answer if exists
answers.stream()
.filter(Answer::isAccepted)
.forEach(a -> a.unaccept());
// Accept new answer
answers.stream()
.filter(a -> a.getAnswerId().equals(answerId))
.findFirst()
.ifPresent(Answer::accept);
return true;
}
public void addBounty(Bounty bounty) {
if (this.bounty != null && this.bounty.getStatus() == BountyStatus.ACTIVE) {
throw new IllegalStateException("Question already has active bounty");
}
this.bounty = bounty;
}
public void incrementViews() {
this.viewCount++;
}
@Override
public void vote(Member voter, VoteType type) {
// Check if already voted
// Create Vote entity
// Update vote count
voteCount.addAndGet(type.getValue());
}
@Override
public void flag(Member flagger, FlagReason reason) {
// Create Flag entity for moderator review
}
@Override
public void delete(Moderator moderator) {
this.status = QuestionStatus.DELETED;
}
public int calculateScore() {
return voteCount.get();
}
@Override
public String getPostId() { return questionId; }
@Override
public Member getAuthor() { return author; }
@Override
public int getVoteCount() { return voteCount.get(); }
public String getQuestionId() { return questionId; }
public QuestionStatus getStatus() { return status; }
public List<Answer> getAnswers() { return Collections.unmodifiableList(answers); }
}
public class Answer implements Post {
private final String answerId;
private String body;
private final Member author;
private final Question question;
private boolean isAccepted;
private LocalDateTime acceptedAt;
private final LocalDateTime createdAt;
private final AtomicInteger voteCount;
private final List<Comment> comments;
private final List<Photo> photos;
private final List<Edit> editHistory;
public Answer(String answerId, String body, Member author, Question question) {
this.answerId = answerId;
this.body = body;
this.author = author;
this.question = question;
this.isAccepted = false;
this.createdAt = LocalDateTime.now();
this.voteCount = new AtomicInteger(0);
this.comments = new ArrayList<>();
this.photos = new ArrayList<>();
this.editHistory = new ArrayList<>();
}
public void accept() {
this.isAccepted = true;
this.acceptedAt = LocalDateTime.now();
// Award reputation
author.gainReputation(15, ReputationEventType.ACCEPTED_ANSWER, answerId);
question.getAuthor().gainReputation(2, ReputationEventType.ACCEPT_ANSWER, answerId);
// Award bounty if exists
if (question.bounty != null && question.bounty.getStatus() == BountyStatus.ACTIVE) {
question.bounty.award(this);
}
}
public void unaccept() {
if (isAccepted) {
this.isAccepted = false;
this.acceptedAt = null;
// Reverse reputation
author.loseReputation(15, ReputationEventType.ACCEPTED_ANSWER, answerId);
question.getAuthor().loseReputation(2, ReputationEventType.ACCEPT_ANSWER, answerId);
}
}
public void addComment(Comment comment) {
comments.add(comment);
}
@Override
public void vote(Member voter, VoteType type) {
voteCount.addAndGet(type.getValue());
}
@Override
public void flag(Member flagger, FlagReason reason) {
// Create Flag entity
}
@Override
public void delete(Moderator moderator) {
// Mark as deleted
}
public int calculateScore() {
return voteCount.get();
}
@Override
public String getPostId() { return answerId; }
@Override
public Member getAuthor() { return author; }
@Override
public int getVoteCount() { return voteCount.get(); }
public String getAnswerId() { return answerId; }
public boolean isAccepted() { return isAccepted; }
}
public class Comment {
private final String commentId;
private final String text;
private final Member author;
private final Post parent;
private final LocalDateTime createdAt;
private final AtomicInteger voteCount;
public Comment(String commentId, String text, Member author, Post parent) {
this.commentId = commentId;
this.text = text;
this.author = author;
this.parent = parent;
this.createdAt = LocalDateTime.now();
this.voteCount = new AtomicInteger(0);
}
public void upvote(Member voter) {
voteCount.incrementAndGet();
}
public String getCommentId() { return commentId; }
}
Gamification System
public class Badge {
private final String badgeId;
private final String name;
private final String description;
private final BadgeType type;
private final BadgeLevel level;
private final String iconUrl;
public Badge(String badgeId, String name, String description,
BadgeType type, BadgeLevel level) {
this.badgeId = badgeId;
this.name = name;
this.description = description;
this.type = type;
this.level = level;
this.iconUrl = "/badges/" + badgeId + ".png";
}
public void award(Member recipient) {
recipient.awardBadge(this);
}
// Predefined badges
public static final Badge NICE_ANSWER = new Badge(
"nice-answer", "Nice Answer", "Answer score of 10 or more",
BadgeType.ANSWER_QUALITY, BadgeLevel.BRONZE
);
public static final Badge GURU = new Badge(
"guru", "Guru", "Accepted answer with score of 40 or more",
BadgeType.ANSWER_QUALITY, BadgeLevel.GOLD
);
}
public class Bounty {
private final String bountyId;
private final int amount;
private final LocalDateTime startedAt;
private final LocalDateTime expiresAt;
private final Member offeredBy;
private final Question question;
private Answer awardedTo;
private BountyStatus status;
public Bounty(String bountyId, int amount, Member offeredBy, Question question) {
if (amount < 50) {
throw new IllegalArgumentException("Minimum bounty is 50 reputation");
}
this.bountyId = bountyId;
this.amount = amount;
this.offeredBy = offeredBy;
this.question = question;
this.startedAt = LocalDateTime.now();
this.expiresAt = startedAt.plusDays(7);
this.status = BountyStatus.ACTIVE;
// Deduct reputation immediately
offeredBy.loseReputation(amount, ReputationEventType.BOUNTY_OFFERED, bountyId);
}
public void award(Answer answer) {
if (status != BountyStatus.ACTIVE) {
throw new IllegalStateException("Bounty is not active");
}
this.awardedTo = answer;
this.status = BountyStatus.AWARDED;
// Grant reputation to answerer
answer.getAuthor().gainReputation(amount, ReputationEventType.BOUNTY_AWARDED, bountyId);
}
public void expire() {
if (status == BountyStatus.ACTIVE) {
this.status = BountyStatus.EXPIRED;
// Reputation is not refunded
}
}
public BountyStatus getStatus() { return status; }
}
public class ReputationEvent {
private final String eventId;
private final Member member;
private final int delta;
private final ReputationEventType type;
private final LocalDateTime occurredAt;
private final String sourceId; // Question/Answer/Bounty ID
public ReputationEvent(Member member, int delta,
ReputationEventType type, String sourceId) {
this.eventId = UUID.randomUUID().toString();
this.member = member;
this.delta = delta;
this.type = type;
this.sourceId = sourceId;
this.occurredAt = LocalDateTime.now();
}
}
Supporting Classes
public class Tag {
private final String tagId;
private final String name;
private String description;
private String wikiContent;
private int dailyFrequency;
private int weeklyFrequency;
private int totalQuestions;
private final LocalDateTime createdAt;
public Tag(String tagId, String name, String description) {
this.tagId = tagId;
this.name = name;
this.description = description;
this.createdAt = LocalDateTime.now();
this.dailyFrequency = 0;
this.weeklyFrequency = 0;
this.totalQuestions = 0;
}
public void incrementFrequency() {
this.dailyFrequency++;
this.weeklyFrequency++;
this.totalQuestions++;
}
public String getName() { return name; }
}
public class Photo {
private final String photoId;
private final String url;
private final String caption;
private final long fileSize;
private final LocalDateTime uploadedAt;
private final Member uploader;
public Photo(String photoId, String url, Member uploader) {
this.photoId = photoId;
this.url = url;
this.uploader = uploader;
this.caption = "";
this.fileSize = 0;
this.uploadedAt = LocalDateTime.now();
}
public void delete() {
// Delete from storage
}
}
public class Edit {
private final String editId;
private final Member editor;
private final String oldContent;
private final String newContent;
private final String editSummary;
private final LocalDateTime editedAt;
public Edit(Member editor, String oldContent, String newContent, String editSummary) {
this.editId = UUID.randomUUID().toString();
this.editor = editor;
this.oldContent = oldContent;
this.newContent = newContent;
this.editSummary = editSummary;
this.editedAt = LocalDateTime.now();
}
public String getDiff() {
// Return diff between old and new content
return "Diff implementation";
}
}
public class Vote {
private final String voteId;
private final Member voter;
private final Post post;
private final VoteType type;
private final LocalDateTime votedAt;
public Vote(Member voter, Post post, VoteType type) {
this.voteId = UUID.randomUUID().toString();
this.voter = voter;
this.post = post;
this.type = type;
this.votedAt = LocalDateTime.now();
}
}
public enum ModerationActionType {
CLOSE_QUESTION, REOPEN_QUESTION, DELETE_POST,
UNDELETE_POST, LOCK_POST, UNLOCK_POST, MERGE_DUPLICATES
}
public class ModerationAction {
private final String actionId;
private final Moderator moderator;
private final String targetId;
private final ModerationActionType actionType;
private final String reason;
private final LocalDateTime performedAt;
public ModerationAction(Moderator moderator, String targetId,
ModerationActionType actionType, String reason) {
this.actionId = UUID.randomUUID().toString();
this.moderator = moderator;
this.targetId = targetId;
this.actionType = actionType;
this.reason = reason;
this.performedAt = LocalDateTime.now();
}
}
This Java implementation demonstrates:
- Strategy Pattern: Privilege checking system
- Template Method: Post interface with polymorphic behavior
- State Pattern: Question status lifecycle
- Observer Pattern: Reputation events and notifications
- SOLID Principles: Single responsibility, open-closed, dependency inversion
6. Python Implementation
Below is a comprehensive Python implementation with type hints, dataclasses, and Pythonic patterns.
from enum import Enum, auto
from typing import List, Dict, Optional, Protocol, Set
from dataclasses import dataclass, field
from abc import ABC, abstractmethod
from datetime import datetime, timedelta, date
import uuid
from threading import Lock
# =====================
# Enumerations
# =====================
class AccountStatus(Enum):
ACTIVE = auto()
SUSPENDED = auto()
BLOCKED = auto()
DELETED = auto()
class QuestionStatus(Enum):
OPEN = auto()
CLOSED = auto()
ON_HOLD = auto()
DELETED = auto()
LOCKED = auto()
class CloseReason(Enum):
DUPLICATE = ("This question has been asked before",)
OFF_TOPIC = ("This question doesn't appear to be about programming",)
TOO_BROAD = ("This question is too broad to answer",)
PRIMARILY_OPINION_BASED = ("This question is likely to solicit opinion",)
UNCLEAR = ("This question is unclear or not useful",)
NOT_REPRODUCIBLE = ("Unable to reproduce the problem",)
def __init__(self, description: str):
self.description = description
class VoteType(Enum):
UPVOTE = 1
DOWNVOTE = -1
@property
def value_delta(self) -> int:
return self.value
class BountyStatus(Enum):
ACTIVE = auto()
AWARDED = auto()
EXPIRED = auto()
CANCELED = auto()
class BadgeLevel(Enum):
BRONZE = 1
SILVER = 2
GOLD = 3
class BadgeType(Enum):
TAG_BASED = auto()
PARTICIPATION = auto()
MODERATION = auto()
QUESTION_QUALITY = auto()
ANSWER_QUALITY = auto()
class ReputationEventType(Enum):
"""Reputation event types with author and voter deltas."""
UPVOTE_QUESTION = (10, 0)
UPVOTE_ANSWER = (10, 0)
DOWNVOTE_QUESTION = (-2, -1)
DOWNVOTE_ANSWER = (-2, -1)
ACCEPTED_ANSWER = (15, 0)
ACCEPT_ANSWER = (2, 0)
BOUNTY_AWARDED = (0, 0) # Variable
BOUNTY_OFFERED = (0, 0) # Variable
PENALTY_SPAM = (-100, 0)
def __init__(self, author_delta: int, voter_delta: int):
self.author_delta = author_delta
self.voter_delta = voter_delta
class FlagReason(Enum):
SPAM = auto()
OFFENSIVE = auto()
LOW_QUALITY = auto()
DUPLICATE = auto()
NEEDS_MODERATOR_ATTENTION = auto()
class FlagStatus(Enum):
PENDING = auto()
HELPFUL = auto()
DECLINED = auto()
DISPUTED = auto()
class NotificationType(Enum):
ANSWER_POSTED = auto()
COMMENT_REPLY = auto()
BADGE_AWARDED = auto()
BOUNTY_EXPIRING = auto()
QUESTION_CLOSED = auto()
EDIT_SUGGESTED = auto()
MENTION = auto()
class ModerationActionType(Enum):
CLOSE_QUESTION = auto()
REOPEN_QUESTION = auto()
DELETE_POST = auto()
UNDELETE_POST = auto()
LOCK_POST = auto()
UNLOCK_POST = auto()
MERGE_DUPLICATES = auto()
# =====================
# Value Objects & DTOs
# =====================
@dataclass(frozen=True)
class Privilege:
"""Privilege with reputation requirement."""
name: str
required_reputation: int
description: str
def check(self, member_reputation: int) -> bool:
"""Check if member has sufficient reputation."""
return member_reputation >= self.required_reputation
# Common privileges as constants
PRIVILEGE_UPVOTE = Privilege("Upvote", 15, "Vote up questions and answers")
PRIVILEGE_DOWNVOTE = Privilege("Downvote", 125, "Vote down questions and answers")
PRIVILEGE_COMMENT = Privilege("Comment", 50, "Leave comments on any post")
PRIVILEGE_CREATE_TAG = Privilege("Create Tag", 1500, "Create new tags")
PRIVILEGE_VOTE_TO_CLOSE = Privilege("Vote to Close", 3000, "Vote to close questions")
PRIVILEGE_DELETE_VOTE = Privilege("Delete Vote", 10000, "Vote to delete questions")
@dataclass
class NotificationSettings:
"""User notification preferences."""
email_on_answer: bool = True
email_on_comment: bool = True
email_on_badge: bool = True
digest_frequency: str = "daily" # daily, weekly, real-time, never
def update(self, **kwargs) -> None:
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
# =====================
# User Management
# =====================
class Account(ABC):
"""Base account with authentication."""
def __init__(self, account_id: str, email: str, password_hash: str, name: str):
self.account_id = account_id
self.email = email
self.password_hash = password_hash
self.name = name
self.location: Optional[str] = None
self.bio: Optional[str] = None
self.status = AccountStatus.ACTIVE
self.created_at = datetime.now()
self.last_login_at: Optional[datetime] = None
def authenticate(self, password: str) -> bool:
"""Verify password."""
return self.password_hash == self._hash_password(password)
def _hash_password(self, password: str) -> str:
"""Hash password (use bcrypt in production)."""
return password # Simplified
def reset_password(self, new_password: str) -> None:
"""Reset password."""
self.password_hash = self._hash_password(new_password)
def suspend(self, reason: str) -> None:
"""Suspend account."""
self.status = AccountStatus.SUSPENDED
class Member(Account):
"""Registered member with reputation and privileges."""
def __init__(self, account_id: str, email: str, password_hash: str,
name: str, member_id: str):
super().__init__(account_id, email, password_hash, name)
self.member_id = member_id
self.reputation = 1 # Starting reputation
self.member_since = date.today()
self.badges: List['Badge'] = []
self.votes: List['Vote'] = []
self.reputation_history: List['ReputationEvent'] = []
self.notification_settings = NotificationSettings()
self._reputation_lock = Lock()
def has_privilege(self, privilege: Privilege) -> bool:
"""Check if member has required privilege."""
return privilege.check(self.reputation)
def gain_reputation(self, amount: int, event_type: ReputationEventType,
source_id: str) -> None:
"""Gain reputation points."""
with self._reputation_lock:
self.reputation += amount
event = ReputationEvent(self, amount, event_type, source_id)
self.reputation_history.append(event)
def lose_reputation(self, amount: int, event_type: ReputationEventType,
source_id: str) -> None:
"""Lose reputation points (minimum 1)."""
with self._reputation_lock:
self.reputation = max(1, self.reputation - amount)
event = ReputationEvent(self, -amount, event_type, source_id)
self.reputation_history.append(event)
def award_badge(self, badge: 'Badge') -> None:
"""Award badge to member."""
if badge not in self.badges:
self.badges.append(badge)
class Moderator(Member):
"""Moderator with additional privileges."""
def __init__(self, account_id: str, email: str, password_hash: str,
name: str, member_id: str):
super().__init__(account_id, email, password_hash, name, member_id)
self.elected_at = date.today()
self.actions: List['ModerationAction'] = []
def close_question(self, question: 'Question', reason: CloseReason) -> None:
"""Close question with reason."""
question.close(reason, self)
self._log_action(ModerationAction(
self, question.question_id, ModerationActionType.CLOSE_QUESTION,
reason.description
))
def reopen_question(self, question: 'Question') -> None:
"""Reopen closed question."""
question.reopen(self)
self._log_action(ModerationAction(
self, question.question_id, ModerationActionType.REOPEN_QUESTION,
"Reopened by moderator"
))
def delete_post(self, post: 'Post') -> None:
"""Delete post."""
post.delete(self)
self._log_action(ModerationAction(
self, post.post_id, ModerationActionType.DELETE_POST,
"Deleted by moderator"
))
def _log_action(self, action: 'ModerationAction') -> None:
"""Log moderation action."""
self.actions.append(action)
class Admin(Member):
"""Administrator with full system control."""
def __init__(self, account_id: str, email: str, password_hash: str,
name: str, member_id: str):
super().__init__(account_id, email, password_hash, name, member_id)
self.permissions: Set[str] = {
"SUSPEND_ACCOUNT",
"DESTROY_ACCOUNT",
"MANAGE_PRIVILEGES",
"VIEW_AUDIT_LOGS"
}
def suspend_account(self, account: Account, duration: timedelta,
reason: str) -> None:
"""Suspend account for duration."""
if "SUSPEND_ACCOUNT" in self.permissions:
account.suspend(reason)
def destroy_spam_account(self, account: Account) -> None:
"""Permanently destroy spam account."""
if "DESTROY_ACCOUNT" in self.permissions:
account.status = AccountStatus.DELETED
# =====================
# Content Models
# =====================
class Post(Protocol):
"""Protocol for votable, flaggable content."""
@property
def post_id(self) -> str: ...
@property
def author(self) -> Member: ...
@property
def vote_count(self) -> int: ...
def vote(self, voter: Member, vote_type: VoteType) -> None: ...
def flag(self, flagger: Member, reason: FlagReason) -> None: ...
def delete(self, moderator: Moderator) -> None: ...
class Question:
"""Question with answers, comments, tags."""
def __init__(self, question_id: str, title: str, body: str, author: Member):
self.question_id = question_id
self.title = title
self.body = body
self.author = author
self.status = QuestionStatus.OPEN
self.close_reason: Optional[CloseReason] = None
self.created_at = datetime.now()
self.updated_at = datetime.now()
self.closed_at: Optional[datetime] = None
self.view_count = 0
self._vote_count = 0
self._vote_lock = Lock()
self.tags: List['Tag'] = []
self.answers: List['Answer'] = []
self.comments: List['Comment'] = []
self.photos: List['Photo'] = []
self.bounty: Optional['Bounty'] = None
self.edit_history: List['Edit'] = []
@property
def post_id(self) -> str:
return self.question_id
@property
def vote_count(self) -> int:
return self._vote_count
def add_answer(self, answer: 'Answer') -> None:
"""Add answer to question."""
if self.status == QuestionStatus.OPEN:
self.answers.append(answer)
else:
raise ValueError("Cannot add answer to non-open question")
def add_comment(self, comment: 'Comment') -> None:
"""Add comment to question."""
self.comments.append(comment)
def add_tag(self, tag: 'Tag') -> None:
"""Add tag (max 5)."""
if len(self.tags) < 5:
self.tags.append(tag)
tag.increment_frequency()
def close(self, reason: CloseReason, moderator: Moderator) -> None:
"""Close question with reason."""
self.status = QuestionStatus.CLOSED
self.close_reason = reason
self.closed_at = datetime.now()
def reopen(self, moderator: Moderator) -> None:
"""Reopen closed question."""
if self.status == QuestionStatus.CLOSED:
self.status = QuestionStatus.OPEN
self.closed_at = None
def accept_answer(self, answer_id: str, acceptor: Member) -> bool:
"""Accept answer (only question owner)."""
if acceptor != self.author:
return False
# Unaccept previous
for answer in self.answers:
if answer.is_accepted:
answer.unaccept()
# Accept new
for answer in self.answers:
if answer.answer_id == answer_id:
answer.accept(self.bounty)
return True
return False
def add_bounty(self, bounty: 'Bounty') -> None:
"""Add bounty to question."""
if self.bounty and self.bounty.status == BountyStatus.ACTIVE:
raise ValueError("Question already has active bounty")
self.bounty = bounty
def increment_views(self) -> None:
"""Increment view count."""
self.view_count += 1
def vote(self, voter: Member, vote_type: VoteType) -> None:
"""Register vote."""
with self._vote_lock:
self._vote_count += vote_type.value_delta
def flag(self, flagger: Member, reason: FlagReason) -> None:
"""Flag for moderation."""
pass # Create Flag entity
def delete(self, moderator: Moderator) -> None:
"""Delete question."""
self.status = QuestionStatus.DELETED
def calculate_score(self) -> int:
"""Calculate question score."""
return self._vote_count
class Answer:
"""Answer to a question."""
def __init__(self, answer_id: str, body: str, author: Member,
question: Question):
self.answer_id = answer_id
self.body = body
self.author = author
self.question = question
self.is_accepted = False
self.accepted_at: Optional[datetime] = None
self.created_at = datetime.now()
self._vote_count = 0
self._vote_lock = Lock()
self.comments: List['Comment'] = []
self.photos: List['Photo'] = []
self.edit_history: List['Edit'] = []
@property
def post_id(self) -> str:
return self.answer_id
@property
def vote_count(self) -> int:
return self._vote_count
def accept(self, bounty: Optional['Bounty'] = None) -> None:
"""Accept this answer."""
self.is_accepted = True
self.accepted_at = datetime.now()
# Award reputation
self.author.gain_reputation(
15, ReputationEventType.ACCEPTED_ANSWER, self.answer_id
)
self.question.author.gain_reputation(
2, ReputationEventType.ACCEPT_ANSWER, self.answer_id
)
# Award bounty if exists
if bounty and bounty.status == BountyStatus.ACTIVE:
bounty.award(self)
def unaccept(self) -> None:
"""Unaccept answer."""
if self.is_accepted:
self.is_accepted = False
self.accepted_at = None
# Reverse reputation
self.author.lose_reputation(
15, ReputationEventType.ACCEPTED_ANSWER, self.answer_id
)
self.question.author.lose_reputation(
2, ReputationEventType.ACCEPT_ANSWER, self.answer_id
)
def add_comment(self, comment: 'Comment') -> None:
"""Add comment to answer."""
self.comments.append(comment)
def vote(self, voter: Member, vote_type: VoteType) -> None:
"""Register vote."""
with self._vote_lock:
self._vote_count += vote_type.value_delta
def flag(self, flagger: Member, reason: FlagReason) -> None:
"""Flag for moderation."""
pass
def delete(self, moderator: Moderator) -> None:
"""Delete answer."""
pass
def calculate_score(self) -> int:
"""Calculate answer score."""
return self._vote_count
@dataclass
class Comment:
"""Comment on question or answer."""
comment_id: str
text: str
author: Member
parent: Post
created_at: datetime = field(default_factory=datetime.now)
vote_count: int = 0
def upvote(self, voter: Member) -> None:
"""Upvote comment."""
self.vote_count += 1
# =====================
# Gamification
# =====================
@dataclass
class Badge:
"""Achievement badge."""
badge_id: str
name: str
description: str
badge_type: BadgeType
level: BadgeLevel
icon_url: str = field(init=False)
def __post_init__(self):
self.icon_url = f"/badges/{self.badge_id}.png"
def award(self, recipient: Member) -> None:
"""Award badge to member."""
recipient.award_badge(self)
# Predefined badges
BADGE_NICE_ANSWER = Badge(
"nice-answer", "Nice Answer", "Answer score of 10 or more",
BadgeType.ANSWER_QUALITY, BadgeLevel.BRONZE
)
BADGE_GURU = Badge(
"guru", "Guru", "Accepted answer with score of 40 or more",
BadgeType.ANSWER_QUALITY, BadgeLevel.GOLD
)
class Bounty:
"""Reputation bounty on question."""
def __init__(self, bounty_id: str, amount: int, offered_by: Member,
question: Question):
if amount < 50:
raise ValueError("Minimum bounty is 50 reputation")
self.bounty_id = bounty_id
self.amount = amount
self.offered_by = offered_by
self.question = question
self.started_at = datetime.now()
self.expires_at = self.started_at + timedelta(days=7)
self.awarded_to: Optional[Answer] = None
self.status = BountyStatus.ACTIVE
# Deduct reputation immediately
offered_by.lose_reputation(
amount, ReputationEventType.BOUNTY_OFFERED, bounty_id
)
def award(self, answer: Answer) -> None:
"""Award bounty to answer."""
if self.status != BountyStatus.ACTIVE:
raise ValueError("Bounty is not active")
self.awarded_to = answer
self.status = BountyStatus.AWARDED
# Grant reputation
answer.author.gain_reputation(
self.amount, ReputationEventType.BOUNTY_AWARDED, self.bounty_id
)
def expire(self) -> None:
"""Expire bounty (no refund)."""
if self.status == BountyStatus.ACTIVE:
self.status = BountyStatus.EXPIRED
@dataclass
class ReputationEvent:
"""Reputation change event."""
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
member: Member = field(repr=False)
delta: int
event_type: ReputationEventType
source_id: str
occurred_at: datetime = field(default_factory=datetime.now)
# =====================
# Supporting Classes
# =====================
class Tag:
"""Question tag."""
def __init__(self, tag_id: str, name: str, description: str):
self.tag_id = tag_id
self.name = name
self.description = description
self.wiki_content = ""
self.daily_frequency = 0
self.weekly_frequency = 0
self.total_questions = 0
self.created_at = datetime.now()
def increment_frequency(self) -> None:
"""Increment usage frequency."""
self.daily_frequency += 1
self.weekly_frequency += 1
self.total_questions += 1
@dataclass
class Photo:
"""Image attachment."""
photo_id: str
url: str
uploader: Member
caption: str = ""
file_size: int = 0
uploaded_at: datetime = field(default_factory=datetime.now)
def delete(self) -> None:
"""Delete photo from storage."""
pass
@dataclass
class Edit:
"""Edit history record."""
edit_id: str
editor: Member
old_content: str
new_content: str
edit_summary: str
edited_at: datetime = field(default_factory=datetime.now)
def get_diff(self) -> str:
"""Get diff between old and new."""
return "Diff implementation"
@dataclass
class Vote:
"""Vote on post."""
vote_id: str
voter: Member
post: Post
vote_type: VoteType
voted_at: datetime = field(default_factory=datetime.now)
@dataclass
class ModerationAction:
"""Moderation action log."""
action_id: str = field(default_factory=lambda: str(uuid.uuid4()))
moderator: Moderator
target_id: str
action_type: ModerationActionType
reason: str
performed_at: datetime = field(default_factory=datetime.now)
This Python implementation demonstrates:
- Type Hints: Full type annotations for IDE support and type checking
- Dataclasses: Reduced boilerplate for value objects
- Protocols: Structural subtyping for Post interface
- Enums: Type-safe constants with additional properties
- Thread Safety: Lock-based synchronization for vote counts and reputation
- Pythonic Patterns: Properties, context managers, clean interfaces
7. Key Design Decisions
1. Reputation-Based Privilege System (Strategy Pattern)
Decision: Use Strategy pattern for privilege checking rather than hardcoded if-statements.
Rationale: Stack Overflow has ~50 different privileges tied to reputation thresholds. Hardcoding checks (if reputation >= 15 for upvote, >= 125 for downvote, etc.) leads to duplicated logic and difficult maintenance when thresholds change.
Implementation:
@dataclass(frozen=True)
class Privilege:
name: str
required_reputation: int
description: str
def check(self, member_reputation: int) -> bool:
return member_reputation >= self.required_reputation
# Predefined privileges
PRIVILEGE_UPVOTE = Privilege("Upvote", 15, "Vote up questions and answers")
PRIVILEGE_DOWNVOTE = Privilege("Downvote", 125, "Vote down questions and answers")
# Usage
if member.has_privilege(PRIVILEGE_UPVOTE):
member.upvote(question)
Benefits: Centralized privilege definitions, easy to add new privileges, testable in isolation, privilege thresholds can be configured per site (Stack Overflow vs. Math Stack Exchange).
2. Atomic Vote Counting with Thread Safety
Decision: Use AtomicInteger in Java and Lock in Python for vote counts to prevent race conditions.
Rationale: Multiple users can vote on the same post simultaneously. Without synchronization, vote count updates could be lost (classic read-modify-write race condition). Using database-level locking would hurt performance at scale.
Implementation:
public class Question {
private final AtomicInteger voteCount = new AtomicInteger(0);
public void vote(Member voter, VoteType type) {
voteCount.addAndGet(type.getValue()); // Atomic increment/decrement
}
}
Benefits: Thread-safe without database locks, high concurrency, eventual consistency acceptable for vote counts (non-critical data).
3. Immutable Bounty Deduction (Event Sourcing)
Decision: Deduct bounty reputation immediately upon creation and never refund, even if bounty expires.
Rationale: Prevents gaming the system (posting bounty, getting attention, canceling before expiry). Matches Stack Overflow's actual policy: "Bounties are non-refundable."
Implementation:
class Bounty:
def __init__(self, amount: int, offered_by: Member, question: Question):
# Deduct reputation immediately
offered_by.lose_reputation(amount, ReputationEventType.BOUNTY_OFFERED, bounty_id)
def expire(self) -> None:
# No refund
self.status = BountyStatus.EXPIRED
Benefits: Incentivizes quality questions (users won't waste reputation), prevents abuse, simple auditing.
4. Single Answer Acceptance with Reversal
Decision: Allow only one accepted answer per question, with ability to change acceptance (reversing previous reputation).
Rationale: Accepted answer indicates "this solved my problem" - having multiple defeats the purpose. Allowing change accommodates better answers posted later.
Implementation:
public boolean acceptAnswer(String answerId, Member acceptor) {
if (!acceptor.equals(author)) return false;
// Unaccept previous
answers.stream()
.filter(Answer::isAccepted)
.forEach(a -> a.unaccept()); // Reverses reputation
// Accept new
answers.stream()
.filter(a -> a.getAnswerId().equals(answerId))
.findFirst()
.ifPresent(Answer::accept); // Grants reputation
return true;
}
Benefits: Clear "solution" indicator, reputation accuracy, prevents multiple acceptances.
5. Tag Frequency Tracking with Dual Metrics
Decision: Track both daily and weekly tag frequencies separately, reset on schedule.
Rationale: Daily frequency identifies trending technologies (e.g., new framework release causes spike). Weekly frequency smooths noise for stable trends.
Implementation:
class Tag:
def __init__(self, name: str):
self.daily_frequency = 0 # Reset daily
self.weekly_frequency = 0 # Reset weekly
self.total_questions = 0 # Never reset
def increment_frequency(self) -> None:
self.daily_frequency += 1
self.weekly_frequency += 1
self.total_questions += 1
# Scheduled job
def reset_daily_frequencies():
for tag in all_tags:
tag.daily_frequency = 0
Benefits: Detects trends quickly (daily), provides stable view (weekly), historical data (total).
6. Close Reason Enumeration with Descriptions
Decision: Model close reasons as enum with embedded descriptions rather than freeform text.
Rationale: Standardized close reasons improve categorization for analytics, provide consistent user messaging, prevent arbitrary moderator text.
Implementation:
public enum CloseReason {
DUPLICATE("This question has been asked before"),
OFF_TOPIC("This question doesn't appear to be about programming"),
TOO_BROAD("This question is too broad to answer");
private final String description;
CloseReason(String description) {
this.description = description;
}
}
Benefits: Standardized moderation, analytics on close reason distribution, localizable descriptions.
7. Edit History with Diff Tracking
Decision: Store complete edit history with old/new content for every post edit.
Rationale: Transparency requirement - users should see what changed. Anti-vandalism - rollback to any previous version. Regulatory compliance in some jurisdictions.
Implementation:
@dataclass
class Edit:
edit_id: str
editor: Member
old_content: str
new_content: str
edit_summary: str
edited_at: datetime
def get_diff(self) -> str:
# Return line-by-line diff
return diff_algorithm(self.old_content, self.new_content)
class Question:
def edit(self, editor: Member, new_body: str, summary: str) -> None:
edit = Edit(uuid4(), editor, self.body, new_body, summary)
self.edit_history.append(edit)
self.body = new_body
Benefits: Full audit trail, vandalism prevention, community trust, rollback capability.
8. Moderation Action Logging
Decision: Log all moderator actions (close, delete, lock) with moderator ID, target, reason, timestamp.
Rationale: Accountability for moderators, audit trail for admins, dispute resolution ("Why was my question closed?"), analytics on moderation patterns.
Implementation:
public class Moderator extends Member {
private final List<ModerationAction> actions = new ArrayList<>();
public void closeQuestion(Question question, CloseReason reason) {
question.close(reason, this);
logAction(new ModerationAction(
this,
question.getQuestionId(),
ModerationActionType.CLOSE_QUESTION,
reason.getDescription()
));
}
}
Benefits: Moderator accountability, dispute resolution data, abuse detection.
9. Badge Award Automation with Rule Engine
Decision: Decouple badge award logic from core domain models using rule engine pattern.
Rationale: Badge rules are complex and change frequently (e.g., "Nice Answer" requires answer score β₯ 10). Embedding logic in Answer class violates Single Responsibility Principle.
Implementation:
class BadgeRule(ABC):
@abstractmethod
def evaluate(self, context: Dict) -> Optional[Badge]:
pass
class NiceAnswerRule(BadgeRule):
def evaluate(self, context: Dict) -> Optional[Badge]:
answer = context.get('answer')
if answer and answer.calculate_score() >= 10:
return BADGE_NICE_ANSWER
return None
class BadgeEngine:
def __init__(self):
self.rules = [NiceAnswerRule(), GuruRule(), ...]
def check_badges(self, event: ReputationEvent) -> List[Badge]:
context = {'answer': event.source}
return [badge for rule in self.rules
if (badge := rule.evaluate(context))]
Benefits: Easy to add new badges, testable rules, separation of concerns, plugin architecture.
10. Daily Reputation Cap with Exclusions
Decision: Limit reputation gain from upvotes to +200 per day, but exclude accepted answers and bounties.
Rationale: Prevents reputation inflation from viral posts ("reached front page, got 1000 upvotes"). Excludes accepts/bounties to reward genuine problem-solving over popularity.
Implementation:
public class Member {
private static final int DAILY_REP_CAP = 200;
public void gainReputation(int amount, ReputationEventType type, String sourceId) {
if (type == UPVOTE_QUESTION || type == UPVOTE_ANSWER) {
int todayGained = calculateTodayGained();
if (todayGained >= DAILY_REP_CAP) {
// Log but don't award
return;
}
amount = Math.min(amount, DAILY_REP_CAP - todayGained);
}
// No cap for ACCEPTED_ANSWER, BOUNTY_AWARDED
this.reputation += amount;
}
}
Benefits: Prevents reputation inflation, rewards quality over quantity, incentivizes helping over self-promotion.