X (Twitter) Interview Prep

Interview Profile

  • Format: 4–5 rounds — typically 1 online assessment, 2–3 coding rounds, 1 system design, 1 behavioral
  • Coding focus: Graph/BFS heavy (social graph traversal), sliding window for analytics streams, heap-based ranking and feed construction
  • System design focus: High-throughput write-heavy systems; fan-out on write vs. fan-out on read for timeline construction
  • Senior bar: Expected to discuss write amplification tradeoffs for celebrity accounts (100M+ followers), timeline cache invalidation strategies, and horizontal partitioning of the social graph
  • What X cares about: Real-time systems at scale, distributed data structures, social graph traversal, and ranking/trending computation pipelines

Pattern Frequency Breakdown

PatternEstimated FrequencyWhy X Asks
Graphs — BFS/DFS30%Social graph traversal, follower clusters, connected components
Sliding Window / Arrays25%Engagement analytics, spam detection, rate limiting
Heap / Priority Queue20%Feed ranking, trending computation, k-way merge for timelines
HashMap / Design15%Tweet indexing, random sampling, O(1) data structures
Dynamic Programming10%Trending score sequences, recommendation relevance

Problems From This Repo

The following problems from completed sessions directly map to X interview patterns. Study them with the “Why X/Twitter” lens in mind.

ProblemSession LinkWhy X/Twitter
Number of Islands (LC 200)[[Session-08-Graphs|Number of Islands]]Follower cluster detection — each island maps to an isolated engagement cluster
Clone Graph (LC 133)[[Session-08-Graphs|Clone Graph]]Social graph replication for data center mirroring / shadow reads
Find Median from Data Stream (LC 295)[[Session-07-Heap|Find Median from Data Stream]]Real-time engagement percentile tracking (P50 impressions, P99 latency)
Insert Delete GetRandom O(1) (LC 380)[[Session-04-HashMap|Insert Delete GetRandom O(1)]]Random tweet sampling for abuse detection / A-B test randomization
Longest Increasing Subsequence (LC 300)[[Session-09-DP1D|Longest Increasing Subsequence]]Trending score sequences — detecting monotonically rising engagement runs
Number of Connected Components (LC 323)[[Session-14-AdvancedGraphs|Number of Connected Components]]Cluster analysis on the follower graph; isolating disconnected communities
Non-overlapping Intervals (LC 435)[[Session-12-Greedy|Non-overlapping Intervals]]Content de-duplication windows — removing redundant notification batches
Binary Tree Level Order Traversal (LC 102)[[Session-06-Trees|Binary Tree Level Order Traversal]]BFS social graph levels — “friends of friends” at hop distance k
Permutation in String (LC 567)[[Session-02-SlidingWindow|Permutation in String]]Spam pattern detection in sliding tweet windows
Top K Frequent Elements (LC 347)[[Session-04-HashMap|Top K Frequent Elements]]Trending hashtag computation — top-k by frequency in real-time window

New Problems (X-Specific)

The five problems below are not yet in the repo and are high-signal for X interviews. Each includes a brute-force approach and the optimal approach with full Java code.


Problem 1: Design Twitter (LC 355) — Medium

Link: LeetCode 355

Quick Summary: Implement a simplified Twitter with postTweet, getNewsFeed (most recent 10 tweets from followed users), follow, and unfollow.

Industry Context: This is literally the Twitter product as a coding problem. Interviewers at X will ask it and then immediately escalate: “What if a user has 100 million followers — how does postTweet change?” This forces the fan-out on write vs. fan-out on read discussion. In production, X uses a hybrid: fan-out on write for users with < ~10K followers (pre-populate timelines), and fan-out on read for celebrity accounts (inject at read time to avoid write amplification).

Approach 1: Brute Force — Linear scan per feed request

// Time: postTweet O(1), getNewsFeed O(U*T) where U=followed users, T=tweets per user
// Space: O(total tweets)
import java.util.*;
 
class TwitterBrute {
    private final Map<Integer, List<int[]>> tweets = new HashMap<>();  // userId -> [[tweetId, timestamp]]
    private final Map<Integer, Set<Integer>> following = new HashMap<>();
    private int time = 0;
 
    public void postTweet(int userId, int tweetId) {
        tweets.computeIfAbsent(userId, k -> new ArrayList<>()).add(new int[]{tweetId, time++});
    }
 
    public List<Integer> getNewsFeed(int userId) {
        // Collect all tweets from self + followed, sort by timestamp desc
        List<int[]> all = new ArrayList<>();
        Set<Integer> sources = new HashSet<>(following.getOrDefault(userId, Collections.emptySet()));
        sources.add(userId);
        for (int uid : sources) {
            all.addAll(tweets.getOrDefault(uid, Collections.emptyList()));
        }
        all.sort((a, b) -> b[1] - a[1]);  // sort by timestamp descending
        List<Integer> feed = new ArrayList<>();
        for (int i = 0; i < Math.min(10, all.size()); i++) feed.add(all.get(i)[0]);
        return feed;
    }
 
    public void follow(int followerId, int followeeId) {
        following.computeIfAbsent(followerId, k -> new HashSet<>()).add(followeeId);
    }
 
    public void unfollow(int followerId, int followeeId) {
        following.getOrDefault(followerId, Collections.emptySet()).remove(followeeId);
    }
}
  • Time: getNewsFeed O(U * T + UT * log(UT)) — collecting and sorting all tweets from all followed users
  • Space: O(total tweets stored)
  • Tradeoff: Correct but blows up when a user follows thousands of accounts each with thousands of tweets. The sort over all collected tweets is the bottleneck.

Approach 2: Optimal — Min-heap k-way merge (fan-out on read)

// Time: postTweet O(1), getNewsFeed O(U log U + 10 log U) ≈ O(U log U)
// Space: O(U) for the heap at any given getNewsFeed call
import java.util.*;
 
class TwitterOptimal {
    // Each tweet: [tweetId, timestamp]
    private final Map<Integer, Deque<int[]>> tweets = new HashMap<>();
    private final Map<Integer, Set<Integer>> following = new HashMap<>();
    private int time = 0;
 
    public void postTweet(int userId, int tweetId) {
        tweets.computeIfAbsent(userId, k -> new ArrayDeque<>()).addFirst(new int[]{tweetId, time++});
    }
 
    public List<Integer> getNewsFeed(int userId) {
        // Max-heap by timestamp — always grab the freshest tweet across all followed users
        // [timestamp, tweetId, iterator over that user's tweet deque]
        PriorityQueue<int[]> maxHeap = new PriorityQueue<>((a, b) -> b[0] - a[0]);
 
        Set<Integer> sources = new HashSet<>(following.getOrDefault(userId, Collections.emptySet()));
        sources.add(userId);
 
        // Seed: push the head (most recent) tweet of each source into the heap
        Map<Integer, Iterator<int[]>> iters = new HashMap<>();
        for (int uid : sources) {
            Deque<int[]> userTweets = tweets.getOrDefault(uid, new ArrayDeque<>());
            if (!userTweets.isEmpty()) {
                Iterator<int[]> it = userTweets.iterator();
                int[] head = it.next();
                maxHeap.offer(new int[]{head[1], head[0], uid});  // [ts, tweetId, uid]
                iters.put(uid, it);
            }
        }
 
        List<Integer> feed = new ArrayList<>();
        while (!maxHeap.isEmpty() && feed.size() < 10) {
            int[] top = maxHeap.poll();
            feed.add(top[1]);  // tweetId
            int uid = top[2];
            Iterator<int[]> it = iters.get(uid);
            if (it != null && it.hasNext()) {
                int[] next = it.next();
                maxHeap.offer(new int[]{next[1], next[0], uid});
            }
        }
        return feed;
    }
 
    public void follow(int followerId, int followeeId) {
        following.computeIfAbsent(followerId, k -> new HashSet<>()).add(followeeId);
    }
 
    public void unfollow(int followerId, int followeeId) {
        following.getOrDefault(followerId, Collections.emptySet()).remove(followeeId);
    }
}
  • Time: getNewsFeed O(U log U) to build heap + O(10 log U) to extract 10 tweets = O(U log U)
  • Space: O(U) heap + O(total tweets) storage
  • Key Insight: This is the k-way merge pattern from LC 23 (Merge K Sorted Lists), applied to per-user tweet streams. Each user’s tweets are stored newest-first so a simple iterator walk gives descending order. The heap only ever holds one entry per followed user (the “frontier”), not all tweets — so heap size is bounded by U (number of followed users), not total tweet count. This is fan-out on read: the merge happens at read time, keeping writes O(1).

Problem 2: Top K Frequent Words (LC 692) — Medium

Link: LeetCode 692

Quick Summary: Given a list of words and integer k, return the k most frequent words sorted by frequency descending; ties broken alphabetically ascending.

Industry Context: Trending topics computation at X — the Trending tab must rank hashtags and keywords by frequency within a time window, and when two topics have identical tweet counts the alphabetical tie-breaking ensures deterministic ordering. This tests whether a candidate can construct a compound Comparator in Java, which trips up many engineers who only handle single-key comparisons.

Approach 1: Brute Force — Sort after counting

// Time: O(n + m log m)  Space: O(m)  where n = words.length, m = unique words
import java.util.*;
 
public static List<String> topKFrequentWordsBrute(String[] words, int k) {
    Map<String, Integer> freq = new HashMap<>();
    for (String w : words) freq.merge(w, 1, Integer::sum);
 
    List<String> candidates = new ArrayList<>(freq.keySet());
    // Sort: higher frequency first; ties → alphabetical
    candidates.sort((a, b) -> {
        int cmp = freq.get(b) - freq.get(a);  // descending frequency
        return cmp != 0 ? cmp : a.compareTo(b);  // ascending lexicographic on tie
    });
    return candidates.subList(0, k);
}
  • Time: O(n) to count + O(m log m) to sort all unique words — even words outside the top-k are sorted
  • Space: O(m) for the frequency map and candidate list
  • Tradeoff: Simple and readable. Acceptable when m is small. For large vocabularies (trending across all of X globally, m could be tens of millions), sorting the entire vocabulary to find k results is wasteful.

Approach 2: Optimal — Min-heap of size k with compound Comparator

// Time: O(n + m log k)  Space: O(m + k)
import java.util.*;
 
public static List<String> topKFrequentWordsOptimal(String[] words, int k) {
    Map<String, Integer> freq = new HashMap<>();
    for (String w : words) freq.merge(w, 1, Integer::sum);
 
    // Min-heap: evict the "least desirable" element when size exceeds k.
    // "Least desirable" = lowest frequency; on tie = lexicographically largest
    // (because we want to keep lexicographically smallest on ties in the result)
    PriorityQueue<String> minHeap = new PriorityQueue<>((a, b) -> {
        int cmp = freq.get(a) - freq.get(b);  // ascending frequency → min on top
        return cmp != 0 ? cmp : b.compareTo(a);  // descending lex → larger word on top (evict it)
    });
 
    for (String word : freq.keySet()) {
        minHeap.offer(word);
        if (minHeap.size() > k) minHeap.poll();  // evict least desirable
    }
 
    // Drain heap into result; heap gives ascending order, so build list in reverse
    List<String> result = new ArrayList<>();
    while (!minHeap.isEmpty()) result.add(minHeap.poll());
    Collections.reverse(result);
    return result;
}
  • Time: O(n) counting + O(m log k) heap maintenance — only k entries in heap at once
  • Space: O(m) map + O(k) heap
  • Key Insight: The comparator direction inversion is the senior-level trap here. The heap’s natural order determines what gets evicted (polled) when size exceeds k — so we want the “least wanted” element at the top. For frequency that means lowest frequency on top (ascending), so freq.get(a) - freq.get(b). For lexicographic tie-breaking we want to keep the alphabetically smaller word, so we evict the alphabetically larger one — put it on top with b.compareTo(a) (descending). When draining for the result, reverse to get descending frequency order.

Problem 3: Longest Consecutive Sequence (LC 128) — Medium

Link: LeetCode 128

Quick Summary: Given an unsorted array of integers, return the length of the longest consecutive elements sequence in O(n) time.

Industry Context: Streak detection in engagement analytics — X tracks consecutive active days (daily tweet streaks), consecutive trending hours for a hashtag, or consecutive active sessions for a creator. The O(n) requirement maps to the constraint that this check runs on every metrics ingestion event, so sorting is too slow for the ingestion hot path.

Approach 1: Brute Force — Sort

// Time: O(n log n)  Space: O(1) if sorting in-place (or O(n) copy)
import java.util.*;
 
public static int longestConsecutiveBrute(int[] nums) {
    if (nums.length == 0) return 0;
    Arrays.sort(nums);
    int best = 1, current = 1;
    for (int i = 1; i < nums.length; i++) {
        if (nums[i] == nums[i - 1]) continue;           // skip duplicate
        if (nums[i] == nums[i - 1] + 1) {
            current++;
            best = Math.max(best, current);
        } else {
            current = 1;
        }
    }
    return best;
}
  • Time: O(n log n) — dominated by sort
  • Space: O(1) extra if in-place sort is allowed; O(n) if a copy is needed
  • Tradeoff: Straightforward. Violates the O(n) requirement in the problem statement. In a live ingestion pipeline at X scale, the sort cost per event is prohibitive.

Approach 2: Optimal — HashSet O(n) boundary trick

// Time: O(n)  Space: O(n)
import java.util.*;
 
public static int longestConsecutiveOptimal(int[] nums) {
    Set<Integer> numSet = new HashSet<>();
    for (int n : nums) numSet.add(n);
 
    int best = 0;
    for (int n : numSet) {
        // Only start counting from the beginning of a sequence.
        // n is a sequence start if (n-1) is not in the set.
        if (!numSet.contains(n - 1)) {
            int current = n;
            int length = 1;
            while (numSet.contains(current + 1)) {
                current++;
                length++;
            }
            best = Math.max(best, length);
        }
    }
    return best;
}
  • Time: O(n) — each element is visited at most twice: once in the outer loop and once in the inner while
  • Space: O(n) for the HashSet
  • Key Insight: The !numSet.contains(n - 1) guard is the entire trick — it ensures we only start counting from the left boundary of a sequence. Without it, we’d re-scan every element in every sequence, giving O(n²) worst case. With it, the inner while only runs in total O(n) steps across all outer loop iterations (each number is the “current” in the while loop at most once). This pattern — “only start work at a boundary, never in the middle” — is a general O(n) streak detection template. Tests that candidates understand HashSet is O(1) lookup, not a sorted structure.

Problem 4: The Maze (LC 490) — Medium

Link: LeetCode 490

Quick Summary: A ball starts at a position in a maze. It rolls in a direction until hitting a wall, then stops. Determine if it can reach the destination.

Industry Context: Content propagation through constrained graphs — routing rules where a message propagates along a channel until it hits a “wall” (rate limiter, policy boundary, or dead-end shard). The non-standard neighbor-generation rule (roll until wall, not step-by-step) is the senior-level signal: if a candidate applies standard BFS with single-step neighbors, they get a wrong answer without realizing it.

Approach 1: Brute Force — DFS without visited tracking

// Time: O((m*n)^2) worst case without visited set  Space: O(m*n) recursion
// WARNING: This TLEs or produces wrong answers without visited set
import java.util.*;
 
public static boolean hasPathDFS(int[][] maze, int[] start, int[] destination) {
    boolean[][] visited = new boolean[maze.length][maze[0].length];
    return dfs(maze, start[0], start[1], destination, visited);
}
 
private static boolean dfs(int[][] maze, int r, int c, int[] dest, boolean[][] visited) {
    if (r == dest[0] && c == dest[1]) return true;
    if (visited[r][c]) return false;
    visited[r][c] = true;
 
    int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
    for (int[] d : dirs) {
        // Roll until hitting a wall
        int nr = r, nc = c;
        while (nr + d[0] >= 0 && nr + d[0] < maze.length
            && nc + d[1] >= 0 && nc + d[1] < maze[0].length
            && maze[nr + d[0]][nc + d[1]] == 0) {
            nr += d[0];
            nc += d[1];
        }
        if (dfs(maze, nr, nc, dest, visited)) return true;
    }
    return false;
}
  • Time: O(m * n * (m + n)) — each cell is a node; rolling in each direction costs O(m) or O(n)
  • Space: O(m * n) visited array + O(m * n) recursion depth worst case
  • Tradeoff: DFS with visited is actually correct and not “brute force” in the traditional sense here — the key mistake candidates make is forgetting the visited array entirely. This is the standard approach; BFS below is preferred for shortest-path variants.

Approach 2: Optimal — BFS with roll-to-wall neighbor generation

// Time: O(m * n * (m + n))  Space: O(m * n)
import java.util.*;
 
public static boolean hasPathBFS(int[][] maze, int[] start, int[] destination) {
    int m = maze.length, n = maze[0].length;
    boolean[][] visited = new boolean[m][n];
    Queue<int[]> queue = new LinkedList<>();
    queue.offer(start);
    visited[start[0]][start[1]] = true;
 
    int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
    while (!queue.isEmpty()) {
        int[] cur = queue.poll();
        if (cur[0] == destination[0] && cur[1] == destination[1]) return true;
 
        for (int[] d : dirs) {
            int nr = cur[0], nc = cur[1];
            // Roll until hitting a wall — this is the non-standard neighbor rule
            while (nr + d[0] >= 0 && nr + d[0] < m
                && nc + d[1] >= 0 && nc + d[1] < n
                && maze[nr + d[0]][nc + d[1]] == 0) {
                nr += d[0];
                nc += d[1];
            }
            if (!visited[nr][nc]) {
                visited[nr][nc] = true;
                queue.offer(new int[]{nr, nc});
            }
        }
    }
    return false;
}
  • Time: O(m * n * (m + n)) — each cell enqueued at most once; rolling costs O(m+n) per direction per cell
  • Space: O(m * n) for visited array and queue
  • Key Insight: The graph here has stop positions (where the ball rests after rolling) as nodes, not every cell. The edge between two nodes requires simulating the roll — that inner while loop IS the edge-generation logic. Standard BFS/DFS structure is unchanged; only the neighbor-generation is non-standard. BFS is preferred over DFS when LC 505 (Maze II — shortest path) is the follow-up, as BFS naturally finds minimum-distance paths. Mark visited before enqueuing, not after polling, to avoid the same stop-position being enqueued multiple times from different directions.

Problem 5: Minimum Number of Vertices to Reach All Nodes (LC 1557) — Medium

Link: LeetCode 1557

Quick Summary: In a DAG with n nodes, find the minimum set of vertices from which all nodes are reachable.

Industry Context: Follower graph root identification — finding the minimal set of accounts from which the entire follow graph is reachable (e.g., identifying original content sources vs. amplifiers in a retweet cascade). This is a senior-level “trap” problem: it appears complex but reduces trivially once you recognize the invariant.

Approach 1: Brute Force — BFS/DFS reachability check

// Time: O(V * (V + E))  Space: O(V + E)
// Tries every possible source set — naive and far too slow for any real graph
import java.util.*;
 
// NOTE: This naive approach is impractical. Included to show the contrast with the trivial insight.
public static List<Integer> findSmallestSetOfVerticesBrute(int n, List<List<Integer>> edges) {
    // Build adjacency list
    Map<Integer, List<Integer>> adj = new HashMap<>();
    Set<Integer> hasIncoming = new HashSet<>();
    for (List<Integer> edge : edges) {
        adj.computeIfAbsent(edge.get(0), k -> new ArrayList<>()).add(edge.get(1));
        hasIncoming.add(edge.get(1));
    }
    // Any node with in-degree 0 MUST be in the source set (nothing points to it)
    // Any node with in-degree > 0 CAN be reached from its predecessors
    // Therefore: source set = all nodes with in-degree 0
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        if (!hasIncoming.contains(i)) result.add(i);
    }
    return result;
}
  • Time: O(V + E) — the “brute force” framing collapses immediately to the trivial solution
  • Space: O(V + E)
  • Tradeoff: There is no meaningful brute-force for this problem once you see the invariant. The trap is spending time on BFS reachability. The senior signal is recognizing the O(V+E) solution in under two minutes.

Approach 2: Optimal — In-degree 0 collection (O(V+E))

// Time: O(V + E)  Space: O(V)
import java.util.*;
 
public static List<Integer> findSmallestSetOfVerticesOptimal(int n, List<List<Integer>> edges) {
    // A node with in-degree > 0 is always reachable from some other node.
    // A node with in-degree 0 has no predecessors — it MUST be included as a source.
    // These two facts together mean the answer is exactly the set of nodes with in-degree 0.
    boolean[] hasIncoming = new boolean[n];
    for (List<Integer> edge : edges) {
        hasIncoming[edge.get(1)] = true;  // edge.get(1) has at least one incoming edge
    }
    List<Integer> result = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        if (!hasIncoming[i]) result.add(i);
    }
    return result;
}
  • Time: O(V + E) — single pass over edges + single pass over vertices
  • Space: O(V) for the boolean array
  • Key Insight: Every node with in-degree > 0 is reachable from at least one other node — so it does not need to be in the source set. Every node with in-degree 0 is unreachable from any other node — so it must be in the source set. These two facts together prove that the answer is exactly and only the nodes with in-degree 0, with no further reasoning needed. When X interviewers use this as a “trap”, they are testing whether a senior candidate can resist over-engineering (BFS reachability simulation) and instead reason from graph-theoretic invariants to a one-liner solution. State the insight out loud before coding — that is the senior signal.

System Design Connections

Algorithm / PatternX System Design Topic
Graph BFS (LC 200, LC 323)Social graph BFS for follower/following traversal; connected component = engagement cluster
k-way merge heap (LC 355 feed)Fan-out on read timeline construction — merge k per-user tweet streams at read time
Top-K heap (LC 347, LC 692)Trending computation pipeline — count-min sketch + heap over sliding windows
HashSet streak (LC 128)Consecutive engagement analytics — streak detection on the hot metrics ingestion path
In-degree 0 (LC 1557)Content source identification in retweet cascades; root discovery in propagation DAG

Fan-Out Write vs. Fan-Out Read

This is the canonical X system design topic. Always be ready to discuss:

  • Fan-out on write: On postTweet, push the tweet ID to every follower’s timeline cache (Redis sorted set, score = timestamp). getNewsFeed is O(1) — just read the pre-populated cache. Write amplification: a single tweet from a 100M-follower account generates 100M cache writes. X uses this for normal users (< ~10K followers).
  • Fan-out on read: On postTweet, only store the tweet in the author’s own store. getNewsFeed pulls and merges tweets from all followed users at read time (k-way merge). Read amplification: every timeline load must merge k streams. X uses this for celebrity accounts to avoid write amplification.
  • Hybrid: X’s actual production system uses fan-out on write for most users and injects celebrity tweets into timelines at read time. The k-way merge in LC 355 Approach 2 above is the read-path merge logic.
  1. Ingest tweet stream into Kafka
  2. Flink/Storm windowed aggregation: count hashtag frequency in 1-hour tumbling windows
  3. Top-K extraction: maintain a min-heap of size K per partition, merge partition heaps at reduce step (LC 347 / LC 692 pattern)
  4. Serve trending list from Redis with TTL matching the window size

2-Week Sprint Plan

DayFocusProblems
1–2Graph BFS/DFS foundationLC 200 (repo), LC 133 (repo), LC 323 (repo)
3–4Feed design + k-way mergeLC 355 (this sheet — Design Twitter)
5–6Trending / Top-KLC 347 (repo), LC 692 (this sheet — Top K Frequent Words)
7Streak detectionLC 128 (this sheet — Longest Consecutive Sequence)
8–9Non-standard graph traversalLC 490 (this sheet — The Maze)
10Graph invariant reasoningLC 1557 (this sheet — Minimum Vertices)
11–12Mock interview — 2 problems in 45 minPick 1 graph + 1 heap/design
13–14System design deep diveFan-out write vs. read; trending pipeline end-to-end

Senior-Level Talking Points

These are the answers that separate a senior candidate from a mid-level candidate at X:

  • Write amplification: Always bring up fan-out write vs. fan-out read unprompted when any feed/social graph design comes up. State the threshold (~10K followers for hybrid at X) and the tradeoff. This signals production system awareness.
  • Timeline cache invalidation: When a user unfollows someone, their pre-populated timeline cache may contain stale tweets from the unfollowed account. Discuss lazy invalidation (TTL expiry) vs. eager invalidation (write-through delete on unfollow).
  • Hot vs. cold path: High-follower accounts (Elon Musk, Barack Obama) are the “hot path” — design decisions for them differ fundamentally from the median user. Always call out this asymmetry.
  • Graph partitioning: The social graph at X scale cannot fit on one machine. Discuss hash-based vs. locality-based partitioning of the follower graph; edges that cross partitions require cross-shard joins.
  • Sliding window for analytics: When asked about rate limiting or spam detection, default to sliding window counter (LC 567 pattern) over fixed-window counter — explain the boundary-burst problem with fixed windows.
  • Heap with compound Comparator: In Java, always verify the Comparator direction matches what gets evicted (min-heap evicts minimum). Articulate the comparator out loud before writing it.