Requirements & System Scope
Functional Requirements (In-Scope)
- Topic Taxonomy Hierarchy: A parent-child topic taxonomy where a user's expertise on subtopics recursively trickles up to parent topics.
- Smoothing Expertise Scoring: Promotes answer quality over volume using the formula
upvotes / (answers + smoothing). - Urgent Personalized Feeds: Compiles and ranks unanswered questions using a blend of interested topic alignment, upvotes velocity, and views/followers recency decay.
- Soliciting Target Experts: Scores and ranks active community writers based on topic expertise to route solicited questions to them.
- Duplicate Merging Engine: Compares title similarities using Jaccard matching to identify and merge duplicates.
System Boundaries (Out-of-Scope)
- Rich Text Processing: Out-of-scope; we model content as sanitized raw string formats.
- Real-time Live WebSocket Feeds: Replaced by stateful periodic pagination fetches to focus on LLD scoring calculations.
Class Diagram & Entity Relationships
The entity model mapping questions, recursive taxonomy levels, and personalized scores:
Loading...
- TopicTaxonomy: Holds hierarchical trees. Children cascade child activity metrics up to parents with a 50% decay modifier.
- ExpertiseService: Aggregates direct and recursive upvote counts divided by answers plus smoothing factor.
- DuplicateQuestionDetector: Pre-scans matching titles to prevent redundant feed insertions.
Design Patterns & SOLID Principles
- Composite Pattern: Treats single topics and parent topic sub-graphs uniformly when calculating expertise and collecting descendant tags.
- Strategy Pattern: Encapsulates title matching similarity algorithms (e.g. Jaccard vs Cosine) behind a clean strategy interface for flexible scoring swaps.
- Single Responsibility Principle (SRP): Isolates tokenization, graph hierarchy traversal, upvote calculations, and feed priority logic into separate service modules.
Core Execution Workflows
Expertise Trickling & Personalized Feed Workflows
- Cascaded Score Calculations:
- A reader upvotes an Answer in child topic
TypeScript. - The system adds to the author's direct upvote counts for
TypeScript. - When computing expertise for parent topic
Programmingor grandparentTechnology, the system queries child statistics recursively, applying a 50% decay factor per hop:childScore * 0.5.
- A reader upvotes an Answer in child topic
- Feed Ranking Score:
- Load all active questions matching user's interested topics.
- Calculate urgency weight:
(followers * 3.0 + views * 0.2) / (hoursElapsed + 2.0)^1.2. - Fetch maximum user expertise across all topics tagged on the question.
- Compute final rank:
(maxExpertise * 10) + urgency.
Concurrency & Thread Safety Strategy
Ensuring high-concurrency read/write operations when generating feeds and upvoting:
- CopyOnWriteArrayList for Answers: Allows concurrent iteration of answers when checking if a user has answered a question, ensuring zero concurrent modification errors during writes.
- Atomic Vote Increments: Volatile counters and synchronized increments guarantee vote additions aren't lost when multiple users simultaneously upvote the same answer.
- Feed Caching & Pre-computing: Pre-aggregates topic upvote statistics so feeds can be assembled on-demand with minimal database joins.
Complete Clean Code Blueprint
Production reference implementations demonstrating topic hierarchy structures, trickled expertise scores, duplicate detection, and feed ranks in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
enum AnswerStatus {
DRAFT, PUBLISHED, FLAGGED
}
class User {
private final String userId;
private final String name;
public User(String userId, String name) {
this.userId = userId;
this.name = name;
}
public String getUserId() { return userId; }
public String getName() { return name; }
}
class Topic {
private final String topicId;
private final String name;
private final String parentTopicId; // Optional parent topic for hierarchy
public Topic(String topicId, String name, String parentTopicId) {
this.topicId = topicId;
this.name = name;
this.parentTopicId = parentTopicId;
}
public String getTopicId() { return topicId; }
public String getName() { return name; }
public String getParentTopicId() { return parentTopicId; }
}
class Answer {
private final String answerId;
private final String questionId;
private final String authorId;
private final String content;
private int upvotes;
private int downvotes;
private AnswerStatus status;
private final long createdAt;
public Answer(String answerId, String questionId, String authorId, String content) {
this.answerId = answerId;
this.questionId = questionId;
this.authorId = authorId;
this.content = content;
this.upvotes = 0;
this.downvotes = 0;
this.status = AnswerStatus.PUBLISHED;
this.createdAt = System.currentTimeMillis();
}
public String getAnswerId() { return answerId; }
public String getQuestionId() { return questionId; }
public String getAuthorId() { return authorId; }
public String getContent() { return content; }
public int getUpvotes() { return upvotes; }
public int getDownvotes() { return downvotes; }
public AnswerStatus getStatus() { return status; }
public long getCreatedAt() { return createdAt; }
public void incrementUpvotes() { this.upvotes++; }
public void incrementDownvotes() { this.downvotes++; }
public void setStatus(AnswerStatus status) { this.status = status; }
}
class Question {
private final String questionId;
private final String title;
private final String body;
private final String authorId;
private final List<String> topicIds;
private int views;
private int followerCount;
private final List<Answer> answers;
private final long createdAt;
private String duplicateOfId; // Pointer to primary question if duplicate
public Question(String questionId, String title, String body, String authorId, List<String> topicIds) {
this.questionId = questionId;
this.title = title;
this.body = body;
this.authorId = authorId;
this.topicIds = new ArrayList<>(topicIds);
this.views = 0;
this.followerCount = 1; // Author follows by default
this.answers = new CopyOnWriteArrayList<>();
this.createdAt = System.currentTimeMillis();
this.duplicateOfId = null;
}
public String getQuestionId() { return questionId; }
public String getTitle() { return title; }
public String getBody() { return body; }
public String getAuthorId() { return authorId; }
public List<String> getTopicIds() { return topicIds; }
public int getViews() { return views; }
public int getFollowerCount() { return followerCount; }
public List<Answer> getAnswers() { return answers; }
public long getCreatedAt() { return createdAt; }
public String getDuplicateOfId() { return duplicateOfId; }
public void incrementViews() { this.views++; }
public void incrementFollowers() { this.followerCount++; }
public void setDuplicateOf(String primaryId) { this.duplicateOfId = primaryId; }
public void addAnswer(Answer answer) { this.answers.add(answer); }
}
class TopicTaxonomy {
private final Map<String, Topic> topics = new ConcurrentHashMap<>();
private final Map<String, List<String>> childTopics = new ConcurrentHashMap<>();
public void addTopic(Topic topic) {
topics.put(topic.getTopicId(), topic);
if (topic.getParentTopicId() != null) {
childTopics.computeIfAbsent(topic.getParentTopicId(), k -> new CopyOnWriteArrayList<>())
.add(topic.getTopicId());
}
}
public Topic getTopic(String topicId) {
return topics.get(topicId);
}
public List<String> getChildren(String topicId) {
return childTopics.getOrDefault(topicId, Collections.emptyList());
}
// Get all descendant topic IDs recursively
public Set<String> getDescendants(String topicId) {
Set<String> descendants = new HashSet<>();
collectDescendants(topicId, descendants);
return descendants;
}
private void collectDescendants(String topicId, Set<String> accumulator) {
List<String> children = childTopics.get(topicId);
if (children != null) {
for (String child : children) {
accumulator.add(child);
collectDescendants(child, accumulator);
}
}
}
}
class ExpertiseService {
private final TopicTaxonomy taxonomy;
private final Map<String, Map<String, List<Answer>>> userAnswersByTopic = new ConcurrentHashMap<>();
private static final double DECAY_FACTOR = 0.5; // Expertise inherited from child topic decays by 50%
private static final double SMOOTHING = 2.0;
public ExpertiseService(TopicTaxonomy taxonomy) {
this.taxonomy = taxonomy;
}
public void recordAnswerSubmission(Answer answer, List<String> topicIds) {
String authorId = answer.getAuthorId();
userAnswersByTopic.computeIfAbsent(authorId, k -> new ConcurrentHashMap<>());
for (String topicId : topicIds) {
userAnswersByTopic.get(authorId)
.computeIfAbsent(topicId, k -> new CopyOnWriteArrayList<>())
.add(answer);
}
}
// Direct stats in a topic
private double[] getDirectStats(String userId, String topicId) {
Map<String, List<Answer>> topicAnswers = userAnswersByTopic.get(userId);
if (topicAnswers == null) return new double[]{0, 0};
List<Answer> answers = topicAnswers.get(topicId);
if (answers == null) return new double[]{0, 0};
int totalUpvotes = 0;
int answersCount = 0;
for (Answer ans : answers) {
if (ans.getStatus() == AnswerStatus.PUBLISHED) {
totalUpvotes += ans.getUpvotes();
answersCount++;
}
}
return new double[]{totalUpvotes, answersCount};
}
// Recursively aggregates upvotes and answer counts trickling up from subtopics
private double[] aggregateStatsRecursive(String userId, String topicId) {
double[] stats = getDirectStats(userId, topicId);
double upvotes = stats[0];
double answers = stats[1];
List<String> children = taxonomy.getChildren(topicId);
for (String childId : children) {
double[] childStats = aggregateStatsRecursive(userId, childId);
upvotes += childStats[0] * DECAY_FACTOR;
answers += childStats[1] * DECAY_FACTOR;
}
return new double[]{upvotes, answers};
}
// Promotes quality over quantity: votes / (answers + smoothing)
public double getExpertiseScore(String userId, String topicId) {
double[] stats = aggregateStatsRecursive(userId, topicId);
double upvotes = stats[0];
double answers = stats[1];
if (answers == 0) return 0.0;
return upvotes / (answers + SMOOTHING);
}
}
class DuplicateQuestionDetector {
private static final double SIMILARITY_THRESHOLD = 0.6;
public static boolean checkDuplicate(Question q1, Question q2) {
double JaccardSim = calculateJaccardSimilarity(q1.getTitle(), q2.getTitle());
return JaccardSim >= SIMILARITY_THRESHOLD;
}
private static double calculateJaccardSimilarity(String s1, String s2) {
Set<String> set1 = tokenize(s1.toLowerCase());
Set<String> set2 = tokenize(s2.toLowerCase());
if (set1.isEmpty() && set2.isEmpty()) return 1.0;
if (set1.isEmpty() || set2.isEmpty()) return 0.0;
Set<String> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
Set<String> union = new HashSet<>(set1);
union.addAll(set2);
return (double) intersection.size() / union.size();
}
private static Set<String> tokenize(String str) {
String cleaned = str.replaceAll("[^a-zA-Z0-9\\s]", "");
String[] tokens = cleaned.split("\\s+");
return Arrays.stream(tokens)
.filter(t -> !t.isEmpty())
.collect(Collectors.toSet());
}
}
class FeedService {
private final List<Question> questions = new CopyOnWriteArrayList<>();
private final ExpertiseService expertiseService;
public FeedService(ExpertiseService expertiseService) {
this.expertiseService = expertiseService;
}
public void addQuestion(Question question) {
questions.add(question);
}
// Personalized feed of unanswered questions sorted by expertise matching and urgency
public List<Question> getPersonalizedFeed(String userId, List<String> interestedTopicIds) {
long now = System.currentTimeMillis();
return questions.stream()
.filter(q -> q.getDuplicateOfId() == null) // Skip duplicates
.filter(q -> !q.getAuthorId().equals(userId)) // Skip own questions
.filter(q -> q.getAnswers().stream().noneMatch(a -> a.getAuthorId().equals(userId))) // Skip if already answered
.filter(q -> q.getTopicIds().stream().anyMatch(interestedTopicIds::contains)) // Match topics
.sorted((q1, q2) -> {
double score1 = calculateFeedScore(userId, q1, now);
double score2 = calculateFeedScore(userId, q2, now);
return Double.compare(score2, score1); // Descending order
})
.collect(Collectors.toList());
}
private double calculateFeedScore(String userId, Question question, long now) {
// Calculate max expertise score among question's topics
double maxExpertise = 0.0;
for (String topicId : question.getTopicIds()) {
maxExpertise = Math.max(maxExpertise, expertiseService.getExpertiseScore(userId, topicId));
}
// Urgency factor derived from followers, views and time elapsed
double timeDeltaHours = (now - question.getCreatedAt()) / (1000.0 * 3600.0);
double urgency = (question.getFollowerCount() * 3.0 + question.getViews() * 0.2) / Math.pow(timeDeltaHours + 2.0, 1.2);
// Blend expertise alignment with general urgency
return (maxExpertise * 10.0) + urgency;
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== JAVA QUORA LLD SIMULATION ===");
TopicTaxonomy taxonomy = new TopicTaxonomy();
taxonomy.addTopic(new Topic("science", "Science", null));
taxonomy.addTopic(new Topic("physics", "Physics", "science"));
ExpertiseService expertise = new ExpertiseService(taxonomy);
FeedService feed = new FeedService(expertise);
User author = new User("user1", "Alice");
User expert = new User("user2", "Bob");
Question q1 = new Question("q-101", "What is quantum entanglement?", "Can someone explain...", "user1", Arrays.asList("physics"));
q1.incrementViews();
q1.incrementFollowers();
feed.addQuestion(q1);
// Bob publishes a physics answer and gets upvotes
Answer a1 = new Answer("a-201", "q-101", "user2", "Entanglement is...");
a1.incrementUpvotes();
a1.incrementUpvotes(); // 2 upvotes
expertise.recordAnswerSubmission(a1, Arrays.asList("physics"));
q1.addAnswer(a1);
// Check Bob's expertise in Physics and Science
System.out.println("Bob's Physics Expertise: " + expertise.getExpertiseScore("user2", "physics"));
System.out.println("Bob's Science Expertise (recursive decay): " + expertise.getExpertiseScore("user2", "science"));
// Check duplicate detection
Question q2 = new Question("q-102", "What is quantum entanglement?", "Same topic...", "user3", Arrays.asList("physics"));
System.out.println("Duplicate Question Detected? " + DuplicateQuestionDetector.checkDuplicate(q1, q2));
System.out.println("=== END OF JAVA SIMULATION ===");
}
}
Interactive Simulator
❖ Quora Engine Interactive Simulator
Simulate real-time post creations, answers, recursive subtopic hierarchy updates, and Jaccard-based duplicates.
1. Add Topic to Taxonomy
Technology Programming (parent: Technology)TypeScript (parent: Programming)Java (parent: Programming)DevOps (parent: Technology)Kubernetes (parent: DevOps)
2. Calculated Topic Expertise Scores
Scores cascade recursively up the hierarchy tree. Votes on subtopics boost parents by 50% decay factor.
Alice (Typescript Expert)
Technology: 1.64Programming: 2.57TypeScript: 2.00
Bob (Java Developer)
Charlie (DevOps Engineer)
Dave (Novice)
3. Post a Question
4. Personalized Questions Feed
Select Feed User:
Age Feed (+ 0 hrs):
No matches in user's interested topics, or all questions are already answered.
5. Solicit Experts for Question
Suggested Experts ranked by cumulative expertise for selected question:
No matching experts with scores > 0.
6. Answer Submissions Panel
Answering: “What is Garbage Collection in Java?”
Published Answers:
No answers yet. Be the first to answer!
Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...