Machine Coding Problem

Quora (LLD)

macoAllsearchtopic-graphexpertise-score
Commonly Asked By:QuoraPinterestGoogle

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

  1. Cascaded Score Calculations:
    1. A reader upvotes an Answer in child topic TypeScript.
    2. The system adds to the author's direct upvote counts for TypeScript.
    3. When computing expertise for parent topic Programming or grandparent Technology, the system queries child statistics recursively, applying a 50% decay factor per hop: childScore * 0.5.
  2. Feed Ranking Score:
    1. Load all active questions matching user's interested topics.
    2. Calculate urgency weight: (followers * 3.0 + views * 0.2) / (hoursElapsed + 2.0)^1.2.
    3. Fetch maximum user expertise across all topics tagged on the question.
    4. 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
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...