Attic
DSAExam Prep & Patterns

DSA Patterns & Best Practices

12 essential problem-solving archetypes, canonical code snippets, safe iteration removal, and production guidelines

Most technical interview problems reduce to identifying the problem's underlying shape and selecting the optimal Java collection to model it.


12 Core Problem Archetypes

PatternProblem ArchetypeRecommended Java Collection
1. Frequency CountingAnagrams, character counts, majority elementHashMap<T, Integer> with merge(k, 1, Integer::sum) or int[26]
2. Seen-Before / DedupTwo Sum, cycle detection, uniqueness verificationHashSet<T> for O(1) membership; HashMap<T, Integer> to track indices
3. Grouping / BucketingGroup anagrams, categorize items by keyHashMap<K, List<V>> with computeIfAbsent(k, k -> new ArrayList<>())
4. Sliding WindowSubstring with unique chars, window maxTwo pointers (left, right) + int[128] or HashMap for window state
5. Monotonic StackNext Greater Element, Daily TemperaturesDeque<Integer> = new ArrayDeque<>() holding array indices
6. BFS / Level-OrderShortest path in unweighted graph, tree levelsQueue<Node> = new ArrayDeque<>() (never use LinkedList)
7. DFS / BacktrackingPermutations, combinations, pathfindingImplicit call-stack recursion, or explicit Deque<Node> stack
8. Top-K / ExtremesK closest points, K frequent numbers, medianPriorityQueue<T> capped at size K (Min-heap for largest K)
9. LRU CacheCache design with eviction policiesLinkedHashMap<K, V> with accessOrder = true & removeEldestEntry
10. Nearest / RangeClosest scheduled event, range queriesTreeSet<T> / TreeMap<K, V> using ceiling, floor, subMap
11. Two PointersPair sum in sorted array, Container With Most WaterFlat array int[] or ArrayList with left and right indices
12. Disjoint UnionConnected components, cycle detectionFlat array int[] parent (Union-Find / Disjoint Set Union)

Canonical Implementation Snippets

1. Two Sum (O(n) Single Pass)

TwoSum.java
public int[] twoSum(int[] nums, int target) {
    Map<Integer, Integer> indexMap = new HashMap<>();

    for (int i = 0; i < nums.length; i++) {
        int complement = target - nums[i];
        if (indexMap.containsKey(complement)) {
            return new int[]{indexMap.get(complement), i};
        }
        indexMap.put(nums[i], i);
    }
    return new int[0];
}

2. Monotonic Stack (Next Greater Element)

NextGreaterElement.java
public int[] nextGreaterElement(int[] nums) {
    int[] result = new int[nums.length];
    Arrays.fill(result, -1);
    Deque<Integer> stack = new ArrayDeque<>(); // Stores array indices

    for (int i = 0; i < nums.length; i++) {
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            int prevIndex = stack.pop();
            result[prevIndex] = nums[i];
        }
        stack.push(i);
    }
    return result;
}

3. Top-K Frequent Elements

TopKFrequent.java
public int[] topKFrequent(int[] nums, int k) {
    Map<Integer, Integer> freq = new HashMap<>();
    for (int n : nums) freq.merge(n, 1, Integer::sum);

    // Min-heap ordered by frequency
    PriorityQueue<Integer> pq = new PriorityQueue<>(
        Comparator.comparingInt(freq::get)
    );

    for (int num : freq.keySet()) {
        pq.offer(num);
        if (pq.size() > k) pq.poll();
    }

    int[] res = new int[k];
    for (int i = 0; i < k; i++) res[i] = pq.poll();
    return res;
}

4. BFS / Level-Order Traversal (Unweighted Shortest Path)

BreadthFirstSearch.java
public void bfs(Node start) {
    Queue<Node> queue = new ArrayDeque<>(); // Never use LinkedList
    Set<Node> visited = new HashSet<>();

    queue.offer(start);
    visited.add(start);

    while (!queue.isEmpty()) {
        Node curr = queue.poll();

        for (Node neighbor : curr.neighbors) {
            if (visited.add(neighbor)) { // add returns false if already visited
                queue.offer(neighbor);
            }
        }
    }
}

Array & Matrix Problem Patterns

1. In-Place Reversal & k-Step Rotation (Two Pointers)

Reversing an array without auxiliary memory runs in O(n) time and O(1) space. A classic LeetCode problem (e.g., Rotate Array) uses this three-step reversal technique:

ArrayRotation.java
public void rotate(int[] nums, int k) {
    int n = nums.length;
    k %= n; // Handle k >= n

    // 1. Reverse entire array
    reverse(nums, 0, n - 1);
    // 2. Reverse first k elements
    reverse(nums, 0, k - 1);
    // 3. Reverse remaining n - k elements
    reverse(nums, k, n - 1);
}

private void reverse(int[] arr, int left, int right) {
    while (left < right) {
        int temp = arr[left];
        arr[left++] = arr[right];
        arr[right--] = temp;
    }
}

2. Prefix Sums & O(1) Range Queries

Precomputing prefix sums allows calculating the sum of any subarray [left, right] in O(1) time after O(n) preprocessing:

PrefixSum.java
public class NumArray {
    private final int[] prefix;

    public NumArray(int[] nums) {
        // Size n + 1 simplifies boundary conditions (prefix[0] = 0)
        prefix = new int[nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
            prefix[i + 1] = prefix[i] + nums[i];
        }
    }

    public int sumRange(int left, int right) {
        return prefix[right + 1] - prefix[left];
    }
}

3. Grid Direction Vectors & Boundary Guards (BFS / DFS)

When traversing 2D matrices in graph problems (e.g., Number of Islands, Flood Fill), avoid cascading if-else blocks by using direction delta arrays with a boundary guard check:

GridDirections.java
// 4-Directional: Up, Down, Left, Right
private static final int[][] DIRS_4 = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

// 8-Directional (includes diagonals)
private static final int[][] DIRS_8 = {
    {-1, 0}, {1, 0}, {0, -1}, {0, 1},
    {-1, -1}, {-1, 1}, {1, -1}, {1, 1}
};

public void exploreNeighbors(int[][] grid, int r, int c) {
    int rows = grid.length;
    int cols = grid[0].length;

    for (int[] dir : DIRS_4) {
        int nr = r + dir[0];
        int nc = c + dir[1];

        // Safe boundary guard check
        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols) {
            // Process valid neighbor cell grid[nr][nc]
        }
    }
}

4. Spiral Matrix Traversal (Boundary Simulation)

Simulate perimeter peeling using four dynamic boundary pointers (top, bottom, left, right):

SpiralMatrix.java
public List<Integer> spiralOrder(int[][] matrix) {
    List<Integer> result = new ArrayList<>();
    if (matrix.length == 0) return result;

    int top = 0, bottom = matrix.length - 1;
    int left = 0, right = matrix[0].length - 1;

    while (top <= bottom && left <= right) {
        // 1. Traverse top row (left to right)
        for (int c = left; c <= right; c++) result.add(matrix[top][c]);
        top++;

        // 2. Traverse right column (top to bottom)
        for (int r = top; r <= bottom; r++) result.add(matrix[r][right]);
        right--;

        // 3. Traverse bottom row (right to left)
        if (top <= bottom) {
            for (int c = right; c >= left; c--) result.add(matrix[bottom][c]);
            bottom--;
        }

        // 4. Traverse left column (bottom to top)
        if (left <= right) {
            for (int r = bottom; r >= top; r--) result.add(matrix[r][left]);
            left++;
        }
    }
    return result;
}

Safe Iteration, Removal & Streams

Mutating a collection inside an enhanced for loop throws ConcurrentModificationException. Always use removeIf() or an explicit iterator:

SafeRemoveIf.java
List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6));

// Clean, high-performance in-place removal (Java 8+)
list.removeIf(n -> n % 2 == 0);

11 Production Engineering Best Practices

Declare by Interface, Instantiate by Class

Write List<String> list = new ArrayList<>() to decouple consumers from internal implementations.

Prefer ArrayDeque for Stacks and Queues

ArrayDeque beats Stack (eliminates synchronization lock overhead) and outperforms LinkedList (eliminates discrete node pointer allocations).

Check Emptiness with isEmpty(), Not size() == 0

isEmpty() is guaranteed O(1) constant time across all collection structures.

Pre-Size Collections Upfront

Initialize collections with expected capacity (new ArrayList<>(count)) to eliminate dynamic array reallocations and hash re-indexing.

Always Override equals() and hashCode() Together

Custom keys in HashSet or HashMap require consistent hash codes when objects evaluate as equal.

Iterate Maps Using entrySet() Over keySet() + get()

Avoid paying for redundant hash computations and bucket lookups on each row.

Protect Exposed State with Immutable Views

Use Collections.unmodifiableList(list) or List.copyOf(list) to prevent unauthorized external mutations.

Use java.util.concurrent for Concurrency

Prefer ConcurrentHashMap and CopyOnWriteArrayList over legacy Vector or Hashtable.

Guard Against Autoboxing in Hot Loops

Avoid continuous heap allocations from generic wrappers in high-throughput numeric algorithms.

On this page