Machine Coding Problem

News Feed Ranker

maco60macoAllsocialscoring-algorithms
Commonly Asked By:MetaByteDanceTwitterLinkedIn

Requirements & System Scope

Functional Scope (In-Scope)

  • Social Graph Integration: Pulls candidate posts from users followed by the active observer user.
  • Engagement Signal Computation: Extracts explicit engagement indicators (likes, shares, comments) to weight baseline interest.
  • Recency Time Decay: Penalizes candidate posts exponentially according to their absolute age: score * e^(-ฮป * ageInHours).
  • Author Diversity Controls: Implements strict deduplication capping candidate results from a single author to ensure user variety.

Explicit Boundaries (Out-of-Scope)

  • Deep-Neural Ranking Pipelines: Real-time inference of complex transformer recommendation models is out-of-scope.
  • Real-time Graph Sockets: Bypasses live transport socket subscriptions for immediate post delivery.

Class Diagram & Entity Relationships

Structural layout modeling scoring extractors and generation pipelines:

Loading...
  • FeedCandidate Wrapper: Attaches temporary evaluation scores to a base \Post\ entity for clean, mutable sorting.
  • Affinity Affinity Edge Map: Tracks bidirectional closeness metrics between users to boost close friends.

Design Patterns & SOLID Principles

  • Strategy Pattern: Enables swappable rank evaluation models, supporting switching from manual linear weights to machine-learned heuristics cleanly.
  • Single Responsibility Principle (SRP): Isolates scoring maths (\`SignalExtractor\`) entirely from graph queries (\`FeedService\`).
  • Open-Closed Principle (OCP): Adding new signals (e.g. hashtags, media types) requires no architectural redesign of the core traverser.

Core Execution Workflows

News Feed Generation Lifecycle

  1. Client invokes getFeed(userId, page, pageSize).
  2. Fetch followed user IDs matching active relationships.
  3. Load posts from followed authors published within the active temporal window (e.g., last 48 hours).
  4. For each post, compute recency decay, baseline engagement score, and author affinity edge weight.
  5. Calculate final score, sort candidate array descending.
  6. Traverse sorted candidates and filter out items violating the diversity cap limits.
  7. Slice and return the resulting array matching page offsets.

Concurrency & Thread Safety Strategy

Ensuring high-speed read/write performance during feed loading:

  • Concurrent Collections: Utilize read-heavy concurrency tools (e.g., Java's ConcurrentHashMap or locking structures) to support concurrent writes from posting and follower additions.
  • Precomputation Decoupling: Decouple intensive signal computation from the checkout flow, storing historical scores in fast redis structures.

Complete Clean Code Blueprint

Clean reference designs demonstrating personalized feed ranking in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class Post {
    private final String id;
    private final String authorId;
    private final String content;
    private final long timestamp;
    private final int likes;
    private final int comments;
    private final int shares;

    public Post(String id, String authorId, String content, long timestamp, int likes, int comments, int shares) {
        this.id = id;
        this.authorId = authorId;
        this.content = content;
        this.timestamp = timestamp;
        this.likes = likes;
        this.comments = comments;
        this.shares = shares;
    }

    public String getId() { return id; }
    public String getAuthorId() { return authorId; }
    public String getContent() { return content; }
    public long getTimestamp() { return timestamp; }
    public int getLikes() { return likes; }
    public int getComments() { return comments; }
    public int getShares() { return shares; }
}

class FeedCandidate implements Comparable<FeedCandidate> {
    private final Post post;
    private final double score;

    public FeedCandidate(Post post, double score) {
        this.post = post;
        this.score = score;
    }

    public Post getPost() { return post; }
    public double getScore() { return score; }

    @Override
    public int compareTo(FeedCandidate o) {
        return Double.compare(o.score, this.score); // Descending score sorting
    }
}

class SignalExtractor {
    public static double computeRecencyDecay(Post post, long currentMillis, double decayRate) {
        double ageInHours = (currentMillis - post.getTimestamp()) / (1000.0 * 60.0 * 60.0);
        return Math.exp(-decayRate * Math.max(0.0, ageInHours));
    }

    public static double computeEngagementScore(Post post) {
        return (post.getLikes() * 1.0) + (post.getComments() * 5.0) + (post.getShares() * 10.0);
    }

    public static double computeEdgeWeight(String userId, String authorId, Map<String, Double> userAffinities) {
        return userAffinities.getOrDefault(authorId, 0.1);
    }
}

class FeedService {
    private final Map<String, List<Post>> authorPosts = new ConcurrentHashMap<>();
    private final Map<String, Set<String>> userFollows = new ConcurrentHashMap<>();
    private final Map<String, Map<String, Double>> userAffinities = new ConcurrentHashMap<>();
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void addPost(Post post) {
        lock.writeLock().lock();
        try {
            authorPosts.computeIfAbsent(post.getAuthorId(), k -> new CopyOnWriteArrayList<>()).add(post);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void followUser(String followerId, String followeeId) {
        lock.writeLock().lock();
        try {
            userFollows.computeIfAbsent(followerId, k -> ConcurrentHashMap.newKeySet()).add(followeeId);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public void setAffinity(String userId, String authorId, double score) {
        lock.writeLock().lock();
        try {
            userAffinities.computeIfAbsent(userId, k -> new ConcurrentHashMap<>()).put(authorId, score);
        } finally {
            lock.writeLock().unlock();
        }
    }

    public List<Post> getFeed(String userId, int page, int pageSize, int diversityCap, double decayRate) {
        lock.readLock().lock();
        try {
            long currentMillis = System.currentTimeMillis();
            Set<String> followed = userFollows.getOrDefault(userId, Collections.emptySet());
            Map<String, Double> affinities = userAffinities.getOrDefault(userId, Collections.emptyMap());

            List<FeedCandidate> candidates = new ArrayList<>();
            for (String followee : followed) {
                List<Post> posts = authorPosts.getOrDefault(followee, Collections.emptyList());
                for (Post post : posts) {
                    double recency = SignalExtractor.computeRecencyDecay(post, currentMillis, decayRate);
                    double engagement = SignalExtractor.computeEngagementScore(post);
                    double edge = SignalExtractor.computeEdgeWeight(userId, post.getAuthorId(), affinities);

                    // Composite Score formula
                    double score = (engagement + 1.0) * recency * edge;
                    candidates.add(new FeedCandidate(post, score));
                }
            }

            Collections.sort(candidates);

            // Apply author diversity filtering
            List<Post> filteredResult = new ArrayList<>();
            Map<String, Integer> authorCounts = new HashMap<>();
            for (FeedCandidate candidate : candidates) {
                String authorId = candidate.getPost().getAuthorId();
                int currentCount = authorCounts.getOrDefault(authorId, 0);
                if (currentCount < diversityCap) {
                    filteredResult.add(candidate.getPost());
                    authorCounts.put(authorId, currentCount + 1);
                }
            }

            // Slice list for pagination
            int startIndex = (page - 1) * pageSize;
            if (startIndex >= filteredResult.size()) {
                return Collections.emptyList();
            }
            int endIndex = Math.min(startIndex + pageSize, filteredResult.size());
            return filteredResult.subList(startIndex, endIndex);
        } finally {
            lock.readLock().unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== INITIALIZING NEWS FEED RANKER ===");
        FeedService feedService = new FeedService();

        // Setup users & follows
        feedService.followUser("Alice", "Bob");
        feedService.followUser("Alice", "Charlie");

        // Set higher affinity towards Charlie
        feedService.setAffinity("Alice", "Charlie", 2.5);

        // Add posts with different ages and engagement counts
        long now = System.currentTimeMillis();
        
        // Bob's posts (Low affinity, high recency vs low recency)
        feedService.addPost(new Post("p1", "Bob", "Hello world from Bob!", now - (30 * 60 * 1000), 10, 2, 0)); // 30m ago
        feedService.addPost(new Post("p2", "Bob", "Another Bob post!", now - (3 * 3600 * 1000), 50, 10, 2)); // 3h ago
        
        // Charlie's posts (High affinity)
        feedService.addPost(new Post("p3", "Charlie", "Stunning landscapes here!", now - (60 * 60 * 1000), 5, 1, 0)); // 1h ago
        feedService.addPost(new Post("p4", "Charlie", "A redundant post from Charlie", now - (10 * 60 * 1000), 0, 0, 0)); // 10m ago

        System.out.println("\\n=== 1. FETCHING FEED FOR ALICE (PAGE 1) ===");
        List<Post> feedPage1 = feedService.getFeed("Alice", 1, 2, 2, 0.5);
        for (int i = 0; i < feedPage1.size(); i++) {
            Post post = feedPage1.get(i);
            System.out.println((i + 1) + ". [" + post.getAuthorId() + "] " + post.getContent() + " (Likes: " + post.getLikes() + ")");
        }

        System.out.println("\\n=== 2. FETCHING FEED FOR ALICE (PAGE 2) ===");
        List<Post> feedPage2 = feedService.getFeed("Alice", 2, 2, 2, 0.5);
        for (int i = 0; i < feedPage2.size(); i++) {
            Post post = feedPage2.get(i);
            System.out.println((i + 3) + ". [" + post.getAuthorId() + "] " + post.getContent());
        }
    }
}

๐Ÿ’ฌ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...