Machine Coding Problem

Follower / Following System

macoAllsocialasymmetric-relation-modeling
Commonly Asked By:MetaTwitterLinkedInPinterest

Functional Scope (In-Scope)

  • Asymmetric Follow Mechanics: Models unidirectional follow actions cleanly (following someone does not force a reciprocal connection).
  • Dual Adjacency Graph Sets: Maintains separate follower and following directories for O(1) read checks.
  • Mutual Follow Intersects: Intersects follower lists to verify mutual status (critical for locking direct messaging permissions).
  • Count Cache Cache updates: Increments and decrements counter blocks atomically during transactions.

Explicit Boundaries (Out-of-Scope)

  • Direct Activity timeline generators: Simplifies actual news feed timeline building, query pipelines, and caches.
  • User Profile Assets / Block lists: Blocks user avatar renders, profile descriptions, and user reporting endpoints.

Production reference implementations demonstrating asymmetric relationships, mutual follow intersections, and atomic count caching in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class FollowRelation {
    private final String followerId;
    private final String followeeId;
    private final long createdAtMs;

    public FollowRelation(String followerId, String followeeId) {
        this.followerId = followerId;
        this.followeeId = followeeId;
        this.createdAtMs = System.currentTimeMillis();
    }

    public String getFollowerId() { return followerId; }
    public String getFolloweeId() { return followeeId; }
    public long getCreatedAtMs() { return createdAtMs; }
}

class FollowService {
    // Bidirectional Adjacency Sets
    private final ConcurrentHashMap<String, Set<String>> followersMap = new ConcurrentHashMap<>(); // userId -> Set of Follower IDs
    private final ConcurrentHashMap<String, Set<String>> followingMap = new ConcurrentHashMap<>(); // userId -> Set of Followee IDs
    
    // Count Caches with Atomic Updates
    private final ConcurrentHashMap<String, AtomicInteger> followerCounts = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, AtomicInteger> followingCounts = new ConcurrentHashMap<>();

    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    // O(1) follow relation insertion with full sync lock
    public boolean follow(String followerId, String followeeId) {
        if (followerId.equals(followeeId)) {
            throw new IllegalArgumentException("Users cannot follow themselves.");
        }

        rwLock.writeLock().lock();
        try {
            // Add to following set of the follower
            Set<String> following = followingMap.computeIfAbsent(followerId, k -> ConcurrentHashMap.newKeySet());
            boolean isNew = following.add(followeeId);
            
            if (!isNew) {
                return false; // Relation already exists
            }

            // Add to followers set of the followee
            Set<String> followers = followersMap.computeIfAbsent(followeeId, k -> ConcurrentHashMap.newKeySet());
            followers.add(followerId);

            // Increment Count Caches atomically
            followingCounts.computeIfAbsent(followerId, k -> new AtomicInteger(0)).incrementAndGet();
            followerCounts.computeIfAbsent(followeeId, k -> new AtomicInteger(0)).incrementAndGet();

            return true;
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    // O(1) follow relation removal
    public boolean unfollow(String followerId, String followeeId) {
        rwLock.writeLock().lock();
        try {
            Set<String> following = followingMap.get(followerId);
            if (following == null || !following.contains(followeeId)) {
                return false; // Relation doesn't exist
            }

            following.remove(followeeId);
            
            Set<String> followers = followersMap.get(followeeId);
            if (followers != null) {
                followers.remove(followerId);
            }

            // Decrement Count Caches atomically
            decrementCount(followingCounts, followerId);
            decrementCount(followerCounts, followeeId);

            return true;
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    public boolean isFollowing(String followerId, String followeeId) {
        rwLock.readLock().lock();
        try {
            Set<String> following = followingMap.get(followerId);
            return following != null && following.contains(followeeId);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    // Mutual follow check
    public boolean isMutual(String userA, String userB) {
        rwLock.readLock().lock();
        try {
            return isFollowing(userA, userB) && isFollowing(userB, userA);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    // Intersection of followers and followees to return mutuals
    public Set<String> getMutualFollows(String userId) {
        rwLock.readLock().lock();
        try {
            Set<String> following = followingMap.get(userId);
            Set<String> followers = followersMap.get(userId);

            if (following == null || followers == null || following.isEmpty() || followers.isEmpty()) {
                return Collections.emptySet();
            }

            // Intersect sets
            Set<String> mutuals = new HashSet<>(following);
            mutuals.retainAll(followers);
            return mutuals;
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public int getFollowerCount(String userId) {
        rwLock.readLock().lock();
        try {
            AtomicInteger count = followerCounts.get(userId);
            return count != null ? count.get() : 0;
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public int getFollowingCount(String userId) {
        rwLock.readLock().lock();
        try {
            AtomicInteger count = followingCounts.get(userId);
            return count != null ? count.get() : 0;
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public Set<String> getFollowers(String userId) {
        rwLock.readLock().lock();
        try {
            Set<String> followers = followersMap.get(userId);
            return followers != null ? new HashSet<>(followers) : Collections.emptySet();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public Set<String> getFollowing(String userId) {
        rwLock.readLock().lock();
        try {
            Set<String> following = followingMap.get(userId);
            return following != null ? new HashSet<>(following) : Collections.emptySet();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    private void decrementCount(ConcurrentHashMap<String, AtomicInteger> map, String key) {
        AtomicInteger count = map.get(key);
        if (count != null) {
            count.updateAndGet(val -> Math.max(0, val - 1));
        }
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("=== JAVA FOLLOWER SYSTEM DEMO ===");
        FollowService service = new FollowService();

        // Establish connections
        service.follow("Alice", "Bob");
        service.follow("Alice", "Charlie");
        service.follow("Bob", "Alice"); // Bob also follows Alice (Mutual)
        
        System.out.println("Alice follows Bob: " + service.isFollowing("Alice", "Bob"));
        System.out.println("Bob follows Alice: " + service.isFollowing("Bob", "Alice"));
        System.out.println("Alice and Bob mutual? " + service.isMutual("Alice", "Bob"));
        System.out.println("Alice and Charlie mutual? " + service.isMutual("Alice", "Charlie"));

        System.out.println("Alice follower count: " + service.getFollowerCount("Alice"));
        System.out.println("Alice following count: " + service.getFollowingCount("Alice"));

        System.out.println("Alice mutual follows list: " + service.getMutualFollows("Alice"));

        // Test concurrent follows
        System.out.println("Simulating concurrent follower actions...");
        ExecutorService executor = Executors.newFixedThreadPool(4);
        for (int i = 1; i <= 10; i++) {
            final String followerName = "User_" + i;
            executor.submit(() -> {
                service.follow(followerName, "Bob");
            });
        }

        executor.shutdown();
        executor.awaitTermination(2, TimeUnit.SECONDS);

        System.out.println("Bob total followers (should be 11: Alice + 10 concurrent): " + service.getFollowerCount("Bob"));

        // Unfollow test
        service.unfollow("Alice", "Bob");
        System.out.println("Alice unfollowed Bob. Alice following Bob? " + service.isFollowing("Alice", "Bob"));
        System.out.println("Bob total followers now: " + service.getFollowerCount("Bob"));

        System.out.println("=== END OF JAVA DEMO ===");
    }
}