Machine Coding Problem

News Feed Ranker

maco60macoAllsocialscoring-algorithms
Commonly Asked By:MetaByteDanceTwitterLinkedIn

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.

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());
        }
    }
}