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