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.
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 ===");
}
}