Salesforce Interview Prep

Interview Profile

  • Rounds: 4–5 (OOP design + graph/tree coding + system design for multi-tenant SaaS + behavioral)
  • Tone: Clean, extensible solutions. Salesforce interviewers penalize micro-optimization tricks that sacrifice readability.
  • Senior bar: Explicitly connect algorithmic choices to the multi-tenant SaaS data model. Discuss extensibility — how would you change this if the schema evolved? How would you add a new record type without rewriting the core logic?
  • What Salesforce cares about: OOP design patterns (Strategy, Composite, Observer), multi-tenancy isolation, “Ohana” culture (collaboration, transparency), and trust/security as a first-class concern.

Pattern Frequency Breakdown

PatternApproximate FrequencyNotes
Trees / Graphs30%Org charts, account hierarchies, workflow DAGs
HashMap / OOP Design25%Record metadata, dedup, in-memory stores
Dynamic Programming15%Field tokenization, formula evaluation
Greedy / Intervals15%Sales period scheduling, resource allocation
Stack10%SOQL expression parsing, workflow rule nesting
Sliding Window5%Report windowing, time-series aggregation

Problems From This Repo

These problems from existing sessions map directly to Salesforce engineering domains. Study them in the context of CRM data models, not just as algorithmic exercises.

ProblemSession LinkWhy Salesforce
Binary Tree Level Order Traversal (LC 102)[[Session-06-Trees|Session 6: Trees]]BFS on account hierarchy for roll-up reports — every parent account aggregates metrics from all child accounts level by level
Lowest Common Ancestor (LC 236)[[Session-06-Trees|Session 6: Trees]]Shared account parent resolution — given two contacts, find the closest shared account owner for escalation routing
Course Schedule (LC 207)[[Session-08-Graphs|Session 8: Graphs]]Workflow dependency ordering — Process Builder / Flow rules form a DAG; detect circular dependencies before deployment
Number of Connected Components (LC 323)[[Session-14-AdvancedGraphs|Session 14: Advanced Graphs]]Account cluster analysis — identify isolated groups of accounts with no cross-links for territory planning
Copy List with Random Pointer (LC 138)[[Session-04-HashMap|Session 4: HashMap]]Deep-copy a CRM record including all metadata pointers (related lookups, formula fields) without corrupting the original
Valid Parentheses (LC 20)[[Session-05-Stack|Session 5: Stack]]SOQL query expression validation — balanced parentheses, bracket matching in dynamic query builders
Word Break (LC 139)[[Session-09-DP1D|Session 9: DP 1D]]Salesforce field API name tokenization — parse compound field names into component tokens against a known dictionary
Non-overlapping Intervals (LC 435)[[Session-12-Greedy|Session 12: Greedy]]Sales period de-duplication — given overlapping opportunity close date windows, find the minimum set of periods to remove so none overlap
Insert Delete GetRandom O(1) (LC 380)[[Session-04-HashMap|Session 4: HashMap]]Record pool management — O(1) add/remove/random-sample from a set of active Salesforce object records
IPO / Maximum Capital (LC 502)[[Session-07-Heap|Session 7: Heap]]Sales deal prioritization — given capital constraints and projected revenue, greedily select deals to maximize cumulative closed revenue

New Problems (Salesforce-Specific)

These five problems do not appear in the core session files. Each has a direct Salesforce product mapping. Study both brute-force and optimal approaches — at the senior level, explaining why you moved from one to the other matters as much as the final solution.


Problem 1: Serialize and Deserialize N-ary Tree (LC 428) — Hard

Link: LeetCode 428

Quick Summary: Design an algorithm to serialize an N-ary tree (each node may have any number of children) to a string, and deserialize that string back to the original tree structure.

Why Salesforce: Salesforce’s core data model is a forest of N-ary trees — Account → Contacts → Cases → Activities, each with unbounded children. When exporting org chart snapshots or replicating account hierarchies across sandboxes, you need a reliable serialization format. Showing BFS level encoding vs. DFS parenthetical encoding maps directly to how Salesforce exports metadata bundles.


Approach 1: DFS Parenthetical Encoding (Brute Force / Recursive)

// Time: O(n)  Space: O(n) output + O(h) recursion stack
// N-ary tree node definition
class NaryNode {
    int val;
    List<NaryNode> children;
    NaryNode(int val) { this.val = val; this.children = new ArrayList<>(); }
}
 
public String serializeDFS(NaryNode root) {
    if (root == null) return "";
    StringBuilder sb = new StringBuilder();
    serializeDFSHelper(root, sb);
    return sb.toString();
}
 
// Format: val(child1(...)child2(...))
private void serializeDFSHelper(NaryNode node, StringBuilder sb) {
    sb.append(node.val);
    sb.append('(');
    for (NaryNode child : node.children) {
        serializeDFSHelper(child, sb);
    }
    sb.append(')');
}
 
public NaryNode deserializeDFS(String data) {
    if (data == null || data.isEmpty()) return null;
    int[] idx = {0};
    return deserializeDFSHelper(data, idx);
}
 
private NaryNode deserializeDFSHelper(String data, int[] idx) {
    // parse integer value (may be multi-digit)
    int start = idx[0];
    while (idx[0] < data.length() && data.charAt(idx[0]) != '(' && data.charAt(idx[0]) != ')') {
        idx[0]++;
    }
    int val = Integer.parseInt(data.substring(start, idx[0]));
    NaryNode node = new NaryNode(val);
    idx[0]++; // consume '('
    while (data.charAt(idx[0]) != ')') {
        node.children.add(deserializeDFSHelper(data, idx));
    }
    idx[0]++; // consume ')'
    return node;
}
  • Time: O(n) — visit each node once
  • Space: O(n) for output string + O(h) recursion stack; h can equal n for a degenerate chain
  • Tradeoff: Simple recursion, easy to reason about. Fails on very deep trees (JVM stack overflow) if account hierarchy is thousands of levels deep. The parenthetical format is human-readable but not the most compact.

Approach 2: BFS Level Encoding with Child Count (Optimal for Production)

// Time: O(n)  Space: O(n) — no recursion stack risk, ideal for large org hierarchies
public String serializeBFS(NaryNode root) {
    if (root == null) return "";
    StringBuilder sb = new StringBuilder();
    Queue<NaryNode> queue = new LinkedList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        NaryNode node = queue.poll();
        // encode: value + number of children (so deserializer knows how many to read)
        sb.append(node.val).append(',').append(node.children.size()).append(',');
        for (NaryNode child : node.children) {
            queue.offer(child);
        }
    }
    return sb.toString();
}
 
public NaryNode deserializeBFS(String data) {
    if (data == null || data.isEmpty()) return null;
    String[] tokens = data.split(",");
    int idx = 0;
    NaryNode root = new NaryNode(Integer.parseInt(tokens[idx++]));
    int rootChildCount = Integer.parseInt(tokens[idx++]);
    // Queue holds (node, remaining children to assign)
    Queue<NaryNode> parentQueue = new LinkedList<>();
    Queue<Integer> childCountQueue = new LinkedList<>();
    parentQueue.offer(root);
    childCountQueue.offer(rootChildCount);
 
    while (!parentQueue.isEmpty()) {
        NaryNode parent = parentQueue.poll();
        int count = childCountQueue.poll();
        for (int i = 0; i < count; i++) {
            NaryNode child = new NaryNode(Integer.parseInt(tokens[idx++]));
            int childCount = Integer.parseInt(tokens[idx++]);
            parent.children.add(child);
            parentQueue.offer(child);
            childCountQueue.offer(childCount);
        }
    }
    return root;
}
  • Time: O(n)
  • Space: O(w) queue — w is the maximum width of the tree (max children at any level); no recursion stack
  • Key Insight: Encoding (val, childCount) pairs in BFS order lets the deserializer reconstruct parent-child links without delimiter ambiguity. The two-queue trick — one for parent nodes, one for child counts — synchronizes the reconstruction. This is the pattern Salesforce’s metadata export actually resembles: a breadth-first snapshot where each record announces how many related records follow.

Problem 2: Number of Provinces (LC 547) — Medium

Link: LeetCode 547

Quick Summary: Given an n x n adjacency matrix isConnected where isConnected[i][j] = 1 means city i and city j are directly connected, return the number of connected components (provinces).

Why Salesforce: Similar to LC 200 (Islands, covered in Session 8 on grid adjacency lists), but the input is an adjacency matrix — a common format when the relationship data comes from a CRM join table or a many-to-many object. Account ownership clustering: given a matrix of account-to-account relationships (shared contacts, shared opportunities), count distinct ownership clusters. Tests your ability to apply the same Union-Find / DFS pattern to a different encoding.


Approach 1: DFS on Adjacency Matrix (Brute Force)

// Time: O(n²)  Space: O(n) visited array + O(n) recursion stack
public int findCircleNumDFS(int[][] isConnected) {
    int n = isConnected.length;
    boolean[] visited = new boolean[n];
    int provinces = 0;
    for (int i = 0; i < n; i++) {
        if (!visited[i]) {
            dfs(isConnected, visited, i);
            provinces++;
        }
    }
    return provinces;
}
 
private void dfs(int[][] isConnected, boolean[] visited, int city) {
    visited[city] = true;
    for (int neighbor = 0; neighbor < isConnected.length; neighbor++) {
        if (isConnected[city][neighbor] == 1 && !visited[neighbor]) {
            dfs(isConnected, visited, neighbor);
        }
    }
}
  • Time: O(n²) — must scan each row fully to find neighbors
  • Space: O(n) visited + O(n) recursion stack
  • Tradeoff: Simple and correct. The O(n²) is unavoidable given the adjacency matrix input — you must read all n² entries. For sparse graphs, an adjacency list representation would be O(n + e) but that’s an input format choice, not an algorithmic one.

Approach 2: Union-Find (Optimal — supports incremental merges)

// Time: O(n² · α(n)) ≈ O(n²)  Space: O(n)
// α(n) is the inverse Ackermann function — effectively constant
public int findCircleNumUnionFind(int[][] isConnected) {
    int n = isConnected.length;
    int[] parent = new int[n];
    int[] rank   = new int[n];
    for (int i = 0; i < n; i++) parent[i] = i;
 
    int components = n;
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {   // upper triangle only — matrix is symmetric
            if (isConnected[i][j] == 1) {
                if (union(parent, rank, i, j)) {
                    components--;
                }
            }
        }
    }
    return components;
}
 
private int find(int[] parent, int x) {
    if (parent[x] != x) parent[x] = find(parent, parent[x]); // path compression
    return parent[x];
}
 
private boolean union(int[] parent, int[] rank, int x, int y) {
    int px = find(parent, x), py = find(parent, y);
    if (px == py) return false; // already same component
    if (rank[px] < rank[py])      parent[px] = py;
    else if (rank[px] > rank[py]) parent[py] = px;
    else { parent[py] = px; rank[px]++; }
    return true;
}
  • Time: O(n² · α(n)) — iterating all pairs is O(n²); each union/find is nearly O(1) with path compression and union by rank
  • Space: O(n) — only the parent and rank arrays
  • Key Insight: Scanning only the upper triangle (j = i + 1) halves the work since the matrix is symmetric — a small but production-relevant optimization worth calling out. More importantly, Union-Find shines when relationships arrive as a stream (new account links added over time) — you can process each new connection in O(α(n)) without re-running DFS on the entire dataset. This is the right data structure for incremental CRM deduplication pipelines.

Problem 3: Accounts Merge (LC 721) — Medium

Link: LeetCode 721

Quick Summary: Given a list of accounts where each account is [name, email1, email2, ...], merge accounts that share at least one email address. Return merged accounts with emails sorted.

Why Salesforce: This is literally Salesforce’s CRM data quality problem — duplicate account/contact deduplication. Two Salesforce records should be merged if they share an email address. The Union-Find by email key pattern is the production-grade approach: as new records arrive, union them by shared email, then collect groups.


Approach 1: DFS on Email Graph (Brute Force)

// Time: O(n·k·log(n·k))  Space: O(n·k)
// n = number of accounts, k = average emails per account
public List<List<String>> accountsMergeDFS(List<List<String>> accounts) {
    // Build adjacency list: email → list of connected emails
    Map<String, Set<String>> graph = new HashMap<>();
    Map<String, String> emailToName = new HashMap<>();
 
    for (List<String> account : accounts) {
        String name = account.get(0);
        for (int i = 1; i < account.size(); i++) {
            emailToName.put(account.get(i), name);
            graph.putIfAbsent(account.get(i), new HashSet<>());
            if (i > 1) {
                // connect email i to email i-1 (chain within this account)
                graph.get(account.get(i)).add(account.get(i - 1));
                graph.get(account.get(i - 1)).add(account.get(i));
            }
        }
    }
 
    Set<String> visited = new HashSet<>();
    List<List<String>> result = new ArrayList<>();
    for (String email : graph.keySet()) {
        if (!visited.contains(email)) {
            List<String> component = new ArrayList<>();
            dfsEmail(graph, visited, email, component);
            Collections.sort(component);
            component.add(0, emailToName.get(email));
            result.add(component);
        }
    }
    return result;
}
 
private void dfsEmail(Map<String, Set<String>> graph, Set<String> visited,
                       String email, List<String> component) {
    visited.add(email);
    component.add(email);
    for (String neighbor : graph.get(email)) {
        if (!visited.contains(neighbor)) {
            dfsEmail(graph, visited, neighbor, component);
        }
    }
}
  • Time: O(n·k·log(n·k)) — dominated by sorting emails within each merged group
  • Space: O(n·k) — graph stores all email-to-email edges
  • Tradeoff: Correct. Building a full email graph makes the connectivity explicit but uses more memory than Union-Find. DFS can also hit stack limits if one account cluster has thousands of emails.

Approach 2: Union-Find by Email Key (Optimal)

// Time: O(n·k·α(n·k)·log(n·k)) ≈ O(n·k·log(n·k))  Space: O(n·k)
// The log factor comes from sorting; Union-Find operations are near O(1)
public List<List<String>> accountsMerge(List<List<String>> accounts) {
    Map<String, String> parent    = new HashMap<>();  // email → representative email
    Map<String, String> emailToName = new HashMap<>();
 
    // Initialize: each email is its own parent
    for (List<String> account : accounts) {
        String name = account.get(0);
        for (int i = 1; i < account.size(); i++) {
            parent.put(account.get(i), account.get(i));
            emailToName.put(account.get(i), name);
        }
    }
 
    // Union all emails within the same account
    for (List<String> account : accounts) {
        String first = account.get(1);
        for (int i = 2; i < account.size(); i++) {
            union(parent, first, account.get(i));
        }
    }
 
    // Group emails by their root representative
    Map<String, List<String>> groups = new HashMap<>();
    for (String email : parent.keySet()) {
        String root = find(parent, email);
        groups.computeIfAbsent(root, k -> new ArrayList<>()).add(email);
    }
 
    List<List<String>> result = new ArrayList<>();
    for (Map.Entry<String, List<String>> entry : groups.entrySet()) {
        List<String> emails = entry.getValue();
        Collections.sort(emails);
        emails.add(0, emailToName.get(entry.getKey()));
        result.add(emails);
    }
    return result;
}
 
private String find(Map<String, String> parent, String x) {
    if (!parent.get(x).equals(x)) {
        parent.put(x, find(parent, parent.get(x))); // path compression
    }
    return parent.get(x);
}
 
private void union(Map<String, String> parent, String x, String y) {
    String px = find(parent, x), py = find(parent, y);
    if (!px.equals(py)) parent.put(px, py);
}
  • Time: O(n·k·log(n·k)) — sorting dominates; Union-Find operations are nearly O(1)
  • Space: O(n·k) — parent map + groups map
  • Key Insight: Using the email string itself as the Union-Find key (rather than an integer index) makes the code directly readable as “merge these two email identities.” The two-pass structure — first union all same-account emails, then group by root — matches the production pattern for batch CRM deduplication jobs: process all known links first, then emit the merged records. Calling out the sort cost matters at Salesforce: n·k can be millions of emails in a large org.

Problem 4: Decode String (LC 394) — Medium

Link: LeetCode 394

Quick Summary: Given an encoded string like 3[a2[c]], decode it to accaccacc. The encoding format is k[encoded_string] where the encoded_string inside the brackets is repeated exactly k times. k is always a positive integer.

Why Salesforce: SOQL nested query expansion — Salesforce’s formula engine and workflow rule template renderer process nested expressions with multiplier semantics. When a Process Builder template has nested merge fields or repeated structures, the expansion algorithm is stack-based decode. Also directly applicable to SOQL sub-select parsing and Apex string template rendering.


Approach 1: Recursive (Brute Force)

// Time: O(max_k^depth · n)  Space: O(depth) recursion stack
// max_k = maximum multiplier; depth = nesting level
public String decodeStringRecursive(String s) {
    int[] idx = {0};
    return decode(s, idx);
}
 
private String decode(String s, int[] idx) {
    StringBuilder result = new StringBuilder();
    while (idx[0] < s.length() && s.charAt(idx[0]) != ']') {
        char c = s.charAt(idx[0]);
        if (Character.isLetter(c)) {
            result.append(c);
            idx[0]++;
        } else if (Character.isDigit(c)) {
            // parse full integer (may be multi-digit: "10[a]")
            int k = 0;
            while (Character.isDigit(s.charAt(idx[0]))) {
                k = k * 10 + (s.charAt(idx[0]++) - '0');
            }
            idx[0]++; // consume '['
            String inner = decode(s, idx);
            idx[0]++; // consume ']'
            result.append(inner.repeat(k)); // Java 11+
        }
    }
    return result.toString();
}
  • Time: O(output length) — each character in the final decoded string is produced once; output length can be exponential in the depth of nesting
  • Space: O(nesting depth) recursion stack
  • Tradeoff: Clean and readable. Stack overflow risk if nesting depth is large (unlikely for SOQL but possible for adversarial input). String.repeat(k) is Java 11+; note this in the interview.

Approach 2: Iterative Stack (Optimal — no recursion risk)

// Time: O(output length)  Space: O(nesting depth) stack
public String decodeString(String s) {
    Deque<Integer>       countStack  = new ArrayDeque<>();
    Deque<StringBuilder> stringStack = new ArrayDeque<>();
    StringBuilder current = new StringBuilder();
    int k = 0;
 
    for (char c : s.toCharArray()) {
        if (Character.isDigit(c)) {
            k = k * 10 + (c - '0');          // accumulate multi-digit numbers
        } else if (c == '[') {
            countStack.push(k);              // save repeat count
            stringStack.push(current);       // save string built so far
            current = new StringBuilder();   // start fresh for the inner segment
            k = 0;
        } else if (c == ']') {
            int repeatCount = countStack.pop();
            StringBuilder prev = stringStack.pop();
            String inner = current.toString();
            prev.append(inner.repeat(repeatCount)); // Java 11+
            current = prev;
        } else {
            current.append(c);
        }
    }
    return current.toString();
}
  • Time: O(output length) — same asymptotic as recursive; each char produced once
  • Space: O(d) where d is the maximum nesting depth — two stacks each holding at most d entries
  • Key Insight: The two-stack idiom — one stack for pending repeat counts, one for partially built strings — is the canonical pattern for any bracket-scoped multiplier problem. When [ is seen: push current state. When ] is seen: pop, multiply, and re-append. The current StringBuilder always holds “what I’ve built at this nesting level so far.” This same pattern applies to JSON parsing, nested macro expansion, and XML entity decoding — all relevant to Salesforce’s metadata processing.

Problem 5: LRU Cache (LC 146) — Medium

Link: LeetCode 146

Quick Summary: Design a data structure that supports get(key) and put(key, value) both in O(1) time, evicting the least recently used entry when capacity is exceeded.

Why Salesforce: Salesforce’s SOQL result caching — when 100,000 orgs share the same database cluster, query results are cached in a shared LRU cache to reduce DB load. The design question is more interesting than the coding: per-tenant LRU vs. shared LRU, what to do when a high-volume org evicts all cache entries for low-volume orgs (noisy neighbor problem), and whether a TTL should override LRU ordering.


Approach 1: LinkedHashMap (Brute Force / Java Library)

// Time: O(1) amortized for get/put  Space: O(capacity)
public class LRUCacheLinkedHashMap {
    private final int capacity;
    private final LinkedHashMap<Integer, Integer> cache;
 
    public LRUCacheLinkedHashMap(int capacity) {
        this.capacity = capacity;
        // accessOrder=true: iteration order = insertion order updated on access
        this.cache = new LinkedHashMap<>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                return size() > LRUCacheLinkedHashMap.this.capacity;
            }
        };
    }
 
    // Time: O(1)  Space: O(1)
    public int get(int key) {
        return cache.getOrDefault(key, -1);
    }
 
    // Time: O(1)  Space: O(1)
    public void put(int key, int value) {
        cache.put(key, value);
    }
}
  • Time: O(1) — LinkedHashMap with accessOrder=true maintains LRU order internally
  • Space: O(capacity)
  • Tradeoff: Extremely concise. Acceptable in a Java interview if you explain why it works (access-order LinkedHashMap). However, interviewers will immediately ask you to implement it without the library — so always be ready with the HashMap + DLL approach.

Approach 2: HashMap + Doubly Linked List (Optimal / Expected Implementation)

// Time: O(1) for get and put  Space: O(capacity)
public class LRUCache {
    private final int capacity;
    private final Map<Integer, DLLNode> map = new HashMap<>();
    // Sentinel head (LRU end) and tail (MRU end)
    private final DLLNode head = new DLLNode(0, 0);
    private final DLLNode tail = new DLLNode(0, 0);
 
    private static class DLLNode {
        int key, val;
        DLLNode prev, next;
        DLLNode(int key, int val) { this.key = key; this.val = val; }
    }
 
    public LRUCache(int capacity) {
        this.capacity = capacity;
        head.next = tail;
        tail.prev = head;
    }
 
    // Time: O(1)
    public int get(int key) {
        if (!map.containsKey(key)) return -1;
        DLLNode node = map.get(key);
        moveToTail(node);   // most recently used
        return node.val;
    }
 
    // Time: O(1)
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            DLLNode node = map.get(key);
            node.val = value;
            moveToTail(node);
        } else {
            if (map.size() == capacity) {
                // evict LRU node (just after head sentinel)
                DLLNode lru = head.next;
                removeNode(lru);
                map.remove(lru.key);    // key stored in node for O(1) map removal
            }
            DLLNode newNode = new DLLNode(key, value);
            addToTail(newNode);
            map.put(key, newNode);
        }
    }
 
    private void removeNode(DLLNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }
 
    private void addToTail(DLLNode node) {
        node.prev = tail.prev;
        node.next = tail;
        tail.prev.next = node;
        tail.prev = node;
    }
 
    private void moveToTail(DLLNode node) {
        removeNode(node);
        addToTail(node);
    }
}
  • Time: O(1) for both get and put — HashMap gives O(1) lookup; DLL gives O(1) insert/remove at any position given a pointer
  • Space: O(capacity) — map + list both bounded by capacity
  • Key Insight: The HashMap stores pointers to DLL nodes — this is what enables O(1) removal from the middle of the list. Without the map, finding a node in the list would be O(n). The sentinel head/tail nodes eliminate null checks on boundary conditions. Storing key in the node itself is the subtle trick that allows O(1) map removal when evicting the LRU entry from the list’s head.

Salesforce Senior Discussion — Multi-Tenant Cache Design:

When 100,000 Salesforce orgs share the same SOQL result cache:

  • Per-tenant LRU (separate cache per org): guarantees isolation but requires 100,000 small caches; memory overhead is high, and small orgs waste most of their capacity allocation.
  • Shared LRU: memory-efficient but a high-query org can evict all entries for low-query orgs (noisy neighbor). Salesforce solves this with capacity quotas per org tier.
  • TTL override: authorization holds must expire even if recently accessed — the put should accept an optional TTL; an eviction thread or lazy expiry on get handles TTL enforcement. LRU alone is insufficient for security-sensitive caches.
  • Consistency: in a distributed shared cache (multiple app servers), you need distributed LRU (e.g., Redis with LRU eviction policy) — a single JVM DLL is not distributed-safe.

CRM Domain Connections

AlgorithmSalesforce Product / Use Case
N-ary Tree SerializationOrg chart export, account hierarchy replication across sandboxes
Union-Find (Number of Provinces)Territory cluster analysis, identifying isolated account groups
Union-Find Merge (Accounts Merge)CRM duplicate detection, contact and account deduplication
Stack (Decode String)Workflow rule template engine, SOQL nested query expansion
LRU CacheSOQL query result caching, multi-tenant shared cache with per-org quotas
BFS Level OrderAccount roll-up reports, org chart level-by-level aggregation
Graph DFS / BFSProcess Builder dependency validation, Flow rule cycle detection

2-Week Sprint Plan

DayFocus
Day 1LC 102 (Level Order BFS), LC 236 (LCA) — solidify tree traversal
Day 2LC 428 (N-ary Serialize) — BFS + DFS both approaches
Day 3LC 547 (Provinces) + LC 323 (Connected Components) — Union-Find pattern
Day 4LC 721 (Accounts Merge) — Union-Find by string key
Day 5LC 207 (Course Schedule) — topological sort, cycle detection
Day 6LC 146 (LRU Cache) — HashMap + DLL; LinkedHashMap shortcut
Day 7LC 394 (Decode String) — stack-based multiplier expansion
Day 8LC 138 (Copy List with Random Pointer) — deep copy with HashMap
Day 9LC 380 (Insert Delete GetRandom) + LC 502 (IPO)
Day 10LC 139 (Word Break) + LC 435 (Non-overlapping Intervals)
Day 11Mock: OOP design — design a CRM record merge system
Day 12Mock: System design — multi-tenant SOQL query cache
Day 13Behavioral: “Ohana” culture questions, trust and security stories
Day 14Full mock interview simulation

Senior-Level Talking Points

Multi-Tenancy: The Always-Ask Question

For every problem, ask yourself: “How does this scale when 100,000 orgs share the same infrastructure?”

  • Graph algorithms: If you’re running BFS/DFS on account hierarchy, one org’s BFS should not block another. Discuss per-org job isolation, queue-based processing, and resource quotas.
  • Caching: Shared caches create noisy-neighbor problems. Always mention per-tenant capacity limits and TTL enforcement alongside LRU.
  • Serialization: When replicating data across sandboxes, the serialization format must be version-tolerant — new fields added to a later release should not break deserialization of older snapshots.
  • Deduplication: At scale, account merge jobs must be idempotent — running the same merge twice should produce the same result with no side effects. Union-Find is inherently idempotent.

OOP Design Patterns

Mention these naturally alongside algorithmic solutions:

  • Strategy Pattern: When you have multiple merge strategies (email-match, phone-match, fuzzy-name-match), encapsulate each as a MergeStrategy interface — the Union-Find structure stays the same, only the “should these be merged?” predicate changes.
  • Composite Pattern: N-ary tree naturally maps to the Composite pattern — AccountNode and LeafContactNode implement the same CRMRecord interface; recursive operations (serialize, roll-up) work uniformly on both.
  • Observer Pattern: When a cache eviction happens (LRU evicts an entry), downstream systems (report generators, formula evaluators) may need to be notified. Discuss how observers subscribe to eviction events rather than polling.

Trust and Security

Salesforce’s #1 value is trust. Volunteer these in discussions:

  • Serialized account hierarchies must be encrypted at rest — discuss where in the serialize/deserialize flow you would apply tenant-specific encryption keys.
  • LRU cache keys must be scoped by org ID — cache.get(orgId + ":" + queryHash) not just queryHash, or one org could read another org’s cached query results.
  • Graph traversals that cross tenant boundaries (shared accounts in Experience Cloud) require permission checks at each edge traversal, not just at the root.