Side by Side · The Interview Toolkit

The Interview Toolkit, Side by Side

The tools every coding round uses - lists, maps, sets, heaps, deques, sorting by a key, building strings, integer limits - in Java and in Python, and the calls that differ in cost or behavior.

  • 8 min read
  • 20 practice questions
On this page
  1. Description
  2. Which tool for which job?
  3. Arrays and grids
  4. Stacks, queues and deques
  5. Maps, sets and counting
  6. Heaps
  7. Sorting by a key
  8. Strings and characters
  9. Numbers
  10. Recursion depth
  11. Common mistakes
  12. Check yourself
  13. Key takeaways
  14. Quick revise
  15. Practice

Description

You know the algorithm; the language is what trips you. This skill is the map between Java and Python for coding interviews: which tool plays which role in each language - queues, maps, heaps, sorting, strings, numbers - and which calls cost more than they look. It's written for switching between the two, and for anyone who learned one and interviews in the other.

Here's the kind of bug we mean. The same BFS loop in both languages:

Java

ArrayDeque<Integer> queue = new ArrayDeque<>(List.of(start));
while (!queue.isEmpty()) {
    int node = queue.poll();                    // O(1)
    for (int next : adj.get(node)) {
        if (!seen[next]) { seen[next] = true; queue.offer(next); }
    }
}

Python

queue = [start]
while queue:
    node = queue.pop(0)                         # O(n): shifts every remaining element left
    for nxt in adj[node]:
        if not seen[nxt]:
            seen[nxt] = True
            queue.append(nxt)

Both are correct. Popping 200,000 items with list.pop(0) took about 3 seconds in a measurement; deque.popleft() took a few milliseconds. Interview problems are sized so that one of these passes and the other times out.

We'll go from simple to more technical:

  1. The map: each job and its tool in both languages.
  2. Arrays and grids, stacks and queues.
  3. Maps, sets and counting; heaps; sorting by a key.
  4. Strings and characters; numbers, where the two languages really differ.
  5. Recursion depth.

Which tool for which job?

You needJavaPythonWatch out
Growable arrayArrayList, int[]listinserting or removing at the front is O(n) in both
StackArrayDeque (push/pop/peek)list (append/pop()/[-1])not the legacy java.util.Stack
Queue, dequeArrayDeque (offer/poll)collections.deque (append/popleft)never list.pop(0)
Hash mapHashMapdictHashMap has no iteration order; a dict keeps insertion order
Countingmerge(k, 1, Integer::sum)Counter, defaultdict(int)map.get(k) + 1 throws on a missing key
Hash setHashSetseta Python key must be hashable - a tuple, not a list
Sorted mapTreeMap (floorKey, ceilingKey)none built in - bisect on a sorted listinserting into a sorted list is O(n)
Min-heapPriorityQueueheapq functions on a listties in a tuple fall through to the next field
Sort by keylist.sort(Comparator...)list.sort(key=...)a comparator must never subtract
Build a stringStringBuilder"".join(parts)+ in a loop copies
Big integerslong, BigIntegerint (unbounded)Java's int wraps silently
"Infinity"Integer.MAX_VALUEfloat("inf"), math.infMAX_VALUE + 1 is negative

Arrays and grids

Java

int[][] grid = new int[rows][cols];             // every row its own array, all zeros
List<Integer> a = new ArrayList<>(List.of(3, 1, 2));
a.add(4);                                       // amortized O(1)
a.add(0, 9);                                    // O(n): shifts everything right
List<Integer> copy = new ArrayList<>(a);        // a shallow copy

Python

grid = [[0] * cols for _ in range(rows)]        # NOT [[0] * cols] * rows
a = [3, 1, 2]
a.append(4)                                     # amortized O(1)
a.insert(0, 9)                                  # O(n): shifts everything right
copy = a[:]                                     # a shallow copy (or list(a), a.copy())

[[0] * cols] * rows builds one row and a list holding rows references to it, so grid[0][0] = 1 sets the first cell of every row. The comprehension builds a new row each time. Java's new int[rows][cols] has no such trap.

Stacks, queues and deques

Java

Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); stack.push(2);
int top = stack.pop();                          // 2; throws NoSuchElementException when empty
Deque<Integer> queue = new ArrayDeque<>();
queue.offer(1); queue.offer(2);
Integer first = queue.poll();                   // 1; returns null when empty

Python

from collections import deque

stack = []
stack.append(1); stack.append(2)
top = stack.pop()                               # 2; raises IndexError when empty
queue = deque()
queue.append(1); queue.append(2)
first = queue.popleft()                         # 1; raises IndexError when empty

Both deques are O(1) at either end. Indexing into the middle of a deque is O(n); a list is the other way round - O(1) indexing, O(n) at the front.

Maps, sets and counting

Java

Map<String, Integer> counts = new HashMap<>();
for (String w : words) counts.merge(w, 1, Integer::sum);
int n = counts.getOrDefault("missing", 0);
Map<Integer, List<Integer>> groups = new HashMap<>();
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(value);

Python

from collections import Counter, defaultdict

counts = Counter(words)                         # or: counts[w] = counts.get(w, 0) + 1
n = counts["missing"]                           # a Counter answers 0 for a missing key
groups = defaultdict(list)
groups[key].append(value)

Membership is O(1) on average for a HashSet, HashMap, set or dict - and O(n) for list.contains or x in some_list. A plain dict raises KeyError for a missing key (counts[w] += 1 fails on the first word); Java's get returns null, and counts.get(w) + 1 throws NullPointerException when it unboxes it.

Heaps

Java

PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
pq.offer(new int[] {dist, node});
int[] top = pq.poll();                          // the smallest dist
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Comparator.reverseOrder());

Python

import heapq

pq = []
heapq.heappush(pq, (dist, node))                # tuples compare field by field
dist, node = heapq.heappop(pq)                  # the smallest dist
max_heap = []
heapq.heappush(max_heap, -x)                    # a max-heap: store negated keys, negate on the way out

Python 3.14 adds heapq.heappush_max and heappop_max, but interview platforms often run older versions - negation works everywhere. When two tuples tie on dist, Python compares node next; if nodes are objects without <, heappush raises TypeError. Put a counter in between - (dist, count, node) - so ties never reach the object. A Java comparator only looks at what you tell it to.

Sorting by a key

Java

people.sort(Comparator.comparingInt(Person::age).thenComparing(Person::name));
people.sort(Comparator.comparingInt(Person::score).reversed().thenComparing(Person::name));
Arrays.sort(nums);                              // an int[]: ascending only, no comparator

Python

people.sort(key=lambda p: (p.age, p.name))
people.sort(key=lambda p: (-p.score, p.name))   # score descending, name ascending
nums.sort()

Negation reverses a number, not a string: to sort one string key descending and another ascending, sort twice (both sorts are stable - the last sort decides the primary order) or use functools.cmp_to_key. A Java comparator must return the sign of the comparison: (a, b) -> a - b overflows for large values of opposite signs, so use Integer.compare.

Strings and characters

Java

String s = "hello";
char c = s.charAt(1);                           // 'e' - a char is a 16-bit number
int index = c - 'a';                            // 4
StringBuilder sb = new StringBuilder();
for (String part : parts) sb.append(part);
String joined = sb.toString();
boolean same = s.equals(t);                     // contents; == compares references

Python

s = "hello"
c = s[1]                                        # "e" - a one-character string
index = ord(c) - ord("a")                       # 4
joined = "".join(parts)
same = s == t                                   # contents; `is` compares identity

Strings are immutable in both languages, so s = s + part in a loop builds a new string every time. StringBuilder and "".join are the linear versions.

Numbers

Java

int big = Integer.MAX_VALUE + 1;                // -2147483648: int wraps silently
long product = (long) a * b;                    // widen *before* multiplying
int q = -7 / 2;                                 // -3: truncates toward zero
int r = -7 % 2;                                 // -1: the dividend's sign
int m = Math.floorMod(-7, 2);                   // 1

Python

big = 2**31 - 1 + 1                             # 2147483648: ints never overflow
product = a * b                                 # exact, however large
q = -7 // 2                                     # -4: floors
r = -7 % 2                                      # 1: the divisor's sign
t = int(-7 / 2)                                 # -3: Java's truncation (exact while floats are)

In Python, / always returns a float - len(nums) / 2 can't index a list. In Java, Integer.MAX_VALUE as "infinity" breaks the moment you add to it; Python's float("inf") stays infinite (inf + 5 == inf) and compares correctly with ints.

Recursion depth

A recursive DFS goes one frame per node on the current path. CPython stops at sys.getrecursionlimit(), 1,000 by default, with RecursionError. The JVM stops when the thread's stack is full, with StackOverflowError - for a small method that's usually tens of thousands of frames (about 30,000 for a DFS in one test with the default stack). Neither language turns tail calls into loops. sys.setrecursionlimit(10**6) raises Python's limit - and since Python 3.11, a Python function calling a Python function no longer uses the C stack, so pure-Python recursion 500,000 deep runs (checked on 3.14). Before 3.11 a raised limit could crash the interpreter instead of raising RecursionError, and interview platforms don't always run a recent Python - for input depths like 10^5, an explicit stack is the answer that works everywhere.

Common mistakes

MistakeWhat happensDo this instead
Python list.pop(0) or insert(0, x) in a loopquadratic timecollections.deque
Python [[0] * m] * none row, shared by every row[[0] * m for _ in range(n)]
result.append(path) / result.add(path) in backtrackingevery entry is the same listpath[:] / new ArrayList<>(path)
Python def dfs(node, path=[])one list shared by every calldefault to None
Java (a, b) -> a - b comparator, Integer.MAX_VALUE + 1silent wraparoundInteger.compare, long, Math.addExact
Java == on Integer or Stringidentity - right only for small cached values and literals.equals
Python heap tuples whose last field can't be comparedTypeError on the first tie(dist, count, node)
Python recursion past 1,000 framesRecursionError on inputs Java handlesan explicit stack

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why does queue.pop(0) make a Python BFS quadratic, and what's the fix?

Show answer

A list stores its elements contiguously, so removing the first one shifts all the others left - O(n) per pop, O(V^2) for the BFS. collections.deque removes from either end in O(1) (popleft()), like Java's ArrayDeque.poll().

2. What do -7 / 2 in Java and -7 // 2 in Python evaluate to, and why do they differ?

Show answer

-3 and -4. Java's integer division truncates toward zero; Python's // rounds down (toward negative infinity). The remainders follow: -7 % 2 is -1 in Java and 1 in Python.

3. How do you get a max-heap in each language?

Show answer

Java: new PriorityQueue<>(Comparator.reverseOrder()) (or any comparator that reverses the order). Python: push negated keys into heapq and negate what comes out - or, on Python 3.14+, heappush_max/heappop_max.

Key takeaways

  • Same algorithm, different drawers: ArrayDeque/deque, HashMap/dict, PriorityQueue/heapq, TreeMap/bisect.
  • Know the hidden O(n) calls - front of a list, in on a list, + on strings - in whichever language you use.
  • Java's int wraps and / truncates; Python's int never overflows and // floors.
  • Both languages pass references: copy lists you keep, build grid rows separately, and never share a mutable default.
  • Python's recursion stops at 1,000 frames; convert deep recursion to an explicit stack.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You needJavaPython
StackArrayDeque (push/pop)list (append/pop())
QueueArrayDeque (offer/poll)deque (append/popleft)
Hash map / countingHashMap, merge(k, 1, Integer::sum)dict, Counter, defaultdict
Sorted mapTreeMap (floorKey)bisect on a sorted list
Min-heap / max-heapPriorityQueue / Comparator.reverseOrder()heapq / negated keys
Sort by two keyscomparing(...).thenComparing(...)key=lambda p: (-p.score, p.name)
Build a stringStringBuilder"".join(parts)
Big numberslong, BigIntegerint
Floor divisionMath.floorDiv, Math.floorMod//, %

Snippets worth knowing by heart

Java

Deque<Integer> queue = new ArrayDeque<>();
Map<String, Integer> counts = new HashMap<>();
counts.merge(word, 1, Integer::sum);
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> Integer.compare(a[0], b[0]));
long product = (long) a * b;

Python

queue = deque([start])
counts = Counter(words)
heapq.heappush(pq, (dist, count, node))
grid = [[0] * cols for _ in range(rows)]
result.append(path[:])

The traps

  • list.pop(0), insert(0, x), x in some_list - O(n) each.
  • [[0] * m] * n - one shared row.
  • Appending the working list - copy it.
  • Mutable default arguments - one list for every call.
  • Java a - b comparators, MAX_VALUE + 1, == on objects.
  • -7 / 2 is -3 in Java, -7 // 2 is -4 in Python.
  • Python recursion stops at 1,000 frames.

Staff-level points, one line each

  • int[] 4 bytes per value; List<Integer> about 20; a Python list 8 per pointer plus 28-32 per distinct int.
  • CPython caches ints -5 to 256: [0] * n shares one object.
  • ArrayList grows 1.5x; CPython's list about 1.125x - both amortized O(1).
  • CPython's s += x is in place only when nothing else references s: 13 ms vs 2.3 s for 100,000 appends.
  • str() of an int over 4,300 digits raises ValueError (3.11+); sys.set_int_max_str_digits(0).
  • Math.abs(Integer.MIN_VALUE) is negative; Math.addExact throws on overflow.
  • HashMap treeifies buckets over 8 entries; iteration order follows buckets.
  • Python ints hash to themselves (hash(-1) == hash(-2)); string hashes are randomized.
  • No decrease-key in either heap: lazy deletion in Dijkstra; heapify is O(n).
  • Deep recursion: a Java thread with a big stack; Python 3.11+ setrecursionlimit alone.

Say it in an interview

  • "I'll use a deque here - popping the front of a list is linear."
  • "The sum can exceed 2^31, so I'm accumulating in a long."
  • "Ties fall through to the next tuple field, so I add a counter."
  • "Recursion depth could reach 10^5, so I'll use an explicit stack."

Practice

This skill comes with 20 multiple-choice questions that tell you which misconception a wrong answer reveals. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice The Interview Toolkit, Side by Side free

Practice The Interview Toolkit, Side by Side free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime