On this page
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:
- The map: each job and its tool in both languages.
- Arrays and grids, stacks and queues.
- Maps, sets and counting; heaps; sorting by a key.
- Strings and characters; numbers, where the two languages really differ.
- Recursion depth.
Which tool for which job?
| You need | Java | Python | Watch out |
|---|---|---|---|
| Growable array | ArrayList, int[] | list | inserting or removing at the front is O(n) in both |
| Stack | ArrayDeque (push/pop/peek) | list (append/pop()/[-1]) | not the legacy java.util.Stack |
| Queue, deque | ArrayDeque (offer/poll) | collections.deque (append/popleft) | never list.pop(0) |
| Hash map | HashMap | dict | HashMap has no iteration order; a dict keeps insertion order |
| Counting | merge(k, 1, Integer::sum) | Counter, defaultdict(int) | map.get(k) + 1 throws on a missing key |
| Hash set | HashSet | set | a Python key must be hashable - a tuple, not a list |
| Sorted map | TreeMap (floorKey, ceilingKey) | none built in - bisect on a sorted list | inserting into a sorted list is O(n) |
| Min-heap | PriorityQueue | heapq functions on a list | ties in a tuple fall through to the next field |
| Sort by key | list.sort(Comparator...) | list.sort(key=...) | a comparator must never subtract |
| Build a string | StringBuilder | "".join(parts) | + in a loop copies |
| Big integers | long, BigInteger | int (unbounded) | Java's int wraps silently |
| "Infinity" | Integer.MAX_VALUE | float("inf"), math.inf | MAX_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
| Mistake | What happens | Do this instead |
|---|---|---|
Python list.pop(0) or insert(0, x) in a loop | quadratic time | collections.deque |
Python [[0] * m] * n | one row, shared by every row | [[0] * m for _ in range(n)] |
result.append(path) / result.add(path) in backtracking | every entry is the same list | path[:] / new ArrayList<>(path) |
Python def dfs(node, path=[]) | one list shared by every call | default to None |
Java (a, b) -> a - b comparator, Integer.MAX_VALUE + 1 | silent wraparound | Integer.compare, long, Math.addExact |
Java == on Integer or String | identity - right only for small cached values and literals | .equals |
| Python heap tuples whose last field can't be compared | TypeError on the first tie | (dist, count, node) |
| Python recursion past 1,000 frames | RecursionError on inputs Java handles | an 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,
inon a list,+on strings - in whichever language you use. - Java's
intwraps and/truncates; Python'sintnever 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.