Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

The Coding Round: Patterns That Keep Coming Back

The six patterns that cover most coding-screen questions, with Java templates, the language-specific traps that cost points, and how to talk while you code without losing your place.

Intermediate7 min readUpdated
On this page

Coding screens for backend roles reuse a small number of shapes. Recognising which one a problem is matters more than raw algorithmic ability, because recognition is what lets you start writing within two minutes instead of ten.

Key Takeaways

  • Six patterns — two pointers, sliding window, hash counting, BFS/DFS, binary search, heap — cover most medium-difficulty questions.
  • Say the brute force and its complexity first, then improve it deliberately.
  • Java-specific traps cost real points: boxed ==, overflow in a midpoint, char arithmetic.
  • Talk in checkpoints, not continuously. Narrate the plan, then code, then walk the example.
  • Always state the final time and space complexity without being asked.

Pattern 1: two pointers

Sorted array, pair-finding, in-place partitioning, palindromes.

two pointers from both ends
public int[] twoSum(int[] sorted, int target) {
    int lo = 0, hi = sorted.length - 1;
    while (lo < hi) {
        int sum = sorted[lo] + sorted[hi];
        if (sum == target) return new int[]{lo, hi};
        if (sum < target) lo++; else hi--;
    }
    return new int[]{-1, -1};
}
// O(n) time, O(1) space — versus O(n^2) brute force or O(n) with a map.

The variant with both pointers moving forward — fast and slow — handles cycle detection, removing duplicates in place, and finding the middle of a linked list.

Pattern 2: sliding window

Contiguous subarray or substring with a constraint.

longest substring without repeating characters
public int longestUnique(String s) {
    Map<Character, Integer> lastSeen = new HashMap<>();
    int best = 0, start = 0;
 
    for (int end = 0; end < s.length(); end++) {
        char c = s.charAt(end);
        // Only move the window forward, never backward.
        if (lastSeen.containsKey(c) && lastSeen.get(c) >= start) {
            start = lastSeen.get(c) + 1;
        }
        lastSeen.put(c, end);
        best = Math.max(best, end - start + 1);
    }
    return best;
}
// O(n) — each character is visited at most twice.

The template is always the same: expand end, and while the window is invalid, shrink start. The work is in defining "invalid" and in updating whatever state tracks it.

Pattern 3: hash counting

Anything about frequency, grouping, or "have I seen this before".

group anagrams
public List<List<String>> groupAnagrams(String[] words) {
    Map<String, List<String>> groups = new HashMap<>();
    for (String w : words) {
        char[] chars = w.toCharArray();
        Arrays.sort(chars);
        groups.computeIfAbsent(new String(chars), k -> new ArrayList<>()).add(w);
    }
    return new ArrayList<>(groups.values());
}
the three Map idioms worth having automatic
counts.merge(key, 1L, Long::sum);                              // count
index.computeIfAbsent(key, k -> new ArrayList<>()).add(value); // multimap
config.getOrDefault(key, fallback);                            // safe read

Using merge and computeIfAbsent rather than a get-null-check-put block is a small signal that you write modern Java, and it removes a whole class of null bug from the code you write under pressure.

Pattern 4: BFS and DFS

Trees, graphs, grids, and anything phrased as "shortest path" or "connected components".

BFS — shortest path in an unweighted graph
public int shortestPath(Map<String, List<String>> graph, String from, String to) {
    Queue<String> queue = new ArrayDeque<>();
    Set<String> visited = new HashSet<>();
    queue.add(from);
    visited.add(from);
    int depth = 0;
 
    while (!queue.isEmpty()) {
        int levelSize = queue.size();          // process one level at a time
        for (int i = 0; i < levelSize; i++) {
            String node = queue.poll();
            if (node.equals(to)) return depth;
            for (String next : graph.getOrDefault(node, List.of())) {
                if (visited.add(next)) queue.add(next);   // add() returns false if present
            }
        }
        depth++;
    }
    return -1;
}

Two details worth internalising. The levelSize loop is what makes BFS able to report a distance rather than just a reachability answer. And visited.add(next) returning false for a duplicate lets you test and insert in one operation.

DFS — iterative, to avoid a StackOverflowError
Deque<String> stack = new ArrayDeque<>();
stack.push(start);
while (!stack.isEmpty()) {
    String node = stack.pop();
    if (!visited.add(node)) continue;
    for (String next : graph.getOrDefault(node, List.of())) stack.push(next);
}

Recursion is fine up to roughly ten thousand frames on a default 1MB stack. Saying "I would write this iteratively for a large input to avoid StackOverflowError" is worth a point even if you then write the recursive version for clarity.

A sorted array, or any monotonic predicate — including "the smallest capacity that finishes in time".

the safe template
public int firstTrue(int lo, int hi, IntPredicate condition) {
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;      // NOT (lo + hi) / 2 — that overflows
        if (condition.test(mid)) hi = mid; else lo = mid + 1;
    }
    return lo;
}

The overflow is the classic: (lo + hi) / 2 with values near Integer.MAX_VALUE produces a negative midpoint. This bug lived in the JDK's own Arrays.binarySearch until 2006, which makes it a good thing to mention by name.

Binary search on the answer — not on an array — is the version that appears most in senior interviews: "what is the smallest number of ships needed to ship all packages in D days?" is a binary search over capacity with a feasibility check.

Pattern 6: heap

Top-k, streaming medians, merging sorted sequences, scheduling.

top k without sorting everything
public List<String> topK(Map<String, Integer> counts, int k) {
    // Min-heap of size k: the smallest of the current best sits at the head,
    // so it is the one to evict. O(n log k) instead of O(n log n).
    PriorityQueue<Map.Entry<String, Integer>> heap =
            new PriorityQueue<>(Map.Entry.comparingByValue());
 
    for (var entry : counts.entrySet()) {
        heap.offer(entry);
        if (heap.size() > k) heap.poll();
    }
 
    List<String> out = new ArrayList<>();
    while (!heap.isEmpty()) out.add(0, heap.poll().getKey());   // reverse into order
    return out;
}

Remember that a PriorityQueue is not sorted on iteration — only the head is guaranteed. Printing one and expecting sorted output is a common slip, covered in Queues and BlockingQueues.

The Java traps

five that cost points
// 1. Boxed comparison. Works below 128, fails above — the Integer cache.
Integer a = 1000, b = 1000;
a == b;                      // false. Use a.equals(b) or intValue().
 
// 2. Overflow in a comparator.
Comparator<Integer> bad  = (x, y) -> x - y;      // overflows
Comparator<Integer> good = Integer::compare;
 
// 3. char arithmetic produces an int.
char c = 'a';
c + 1;                       // int 98, not char 'b'
(char) (c + 1);              // 'b'
int index = word.charAt(i) - 'a';    // the standard 0-25 idiom
 
// 4. Mutating while iterating.
for (String s : list) if (pred(s)) list.remove(s);   // CME, or silently skips one
list.removeIf(this::pred);                            // correct
 
// 5. Integer division and negative modulo.
5 / 2;                       // 2
-7 % 3;                      // -1 in Java, not 2
Math.floorMod(-7, 3);        // 2

Every one of these is a bug that reaches production, which is exactly why interviewers watch for them.

How to talk while coding

Silence reads as being stuck; continuous narration is exhausting for both of you. Use checkpoints:

Before writing (60–90 seconds). Restate the problem in your own words. Ask about the input size, duplicates, nulls, and whether the input can be modified. State the brute force and its complexity. State the approach you will actually take and why.

While writing. Say what each block does as you start it — "this loop expands the window" — then go quiet and write it. If you get stuck, say what you are stuck on; interviewers hint far more readily than candidates expect.

After writing. Walk one small example through the code out loud. Then check the edge cases you listed: empty input, one element, all identical, the maximum size. Then state the final time and space complexity without being asked.

What to practise

Fifty to eighty problems spread across the six patterns, not several hundred at random. Roughly: fifteen array and two-pointer, ten sliding window, ten hash-map, fifteen tree and graph, ten binary search and sorting, ten heap and interval, and five string manipulation.

For a backend role, that is the right allocation of effort — the rest belongs in system design and in your Spring answers, both of which carry more weight than an extra thirty puzzle problems.

Frequently Asked Questions

How much data-structures practice does a Java backend interview need?
Enough to solve a typical medium-difficulty problem in thirty minutes while explaining yourself. That is roughly fifty to eighty problems spread across the six patterns on this page, not several hundred. Backend interviews weight system design, Spring and production experience far more heavily than algorithmic depth, so past a certain point extra puzzle practice has poor returns compared with rehearsing your architecture and incident stories.
Should I optimise immediately or write the brute force first?
State the brute force out loud with its complexity, then say what you intend to improve and why, then write the better version. That sequence shows you can see the problem clearly and that the optimisation is deliberate. Writing an optimal solution silently reads as memorisation, and silently writing a brute force reads as not seeing the improvement.
What Java-specific mistakes lose points in a coding round?
Comparing boxed Integers with double equals, integer overflow in a binary-search midpoint or a comparator, forgetting that char arithmetic produces an int, mutating a collection while iterating it, and using an unbounded recursion depth. Interviewers notice these because they are exactly the bugs that reach production, and each of them has a one-line correct form worth committing to memory.

Related tutorials