Preamble
Interview Python rarely needs exotic libraries: itertools and collections cover most combinatorial, frequency, and sliding-window problems with clear intent and predictable complexity. This post is a practical map—what to import, what each tool is good for, and the footguns that lose you the round.
itertools: combinations without materializing lists
combinations(iterable, r) chooses unordered subsets of size r. permutations(iterable, r) cares about order; omit r to permute the full length. Saying the difference out loud prevents subtle bugs in constraint and backtracking setups.
product(*iterables, repeat=n) is the Cartesian product—nested loops as one iterator. combinations_with_replacement allows repeated picks when order does not matter but elements can repeat (e.g. coin-change style enumeration).
from itertools import combinations, permutations, product
list(combinations("ABC", 2)) # [('A','B'), ('A','C'), ('B','C')]
list(permutations("AB", 2)) # [('A','B'), ('B','A')]
list(product([0, 1], repeat=2)) # (0,0), (0,1), (1,0), (1,1)
itertools: chaining and flattening
chain(*iterables) walks many iterables one after another without building a concatenated list. chain.from_iterable flattens one level of nesting—common after grouping or splitting.
zip_longest(*iterables, fillvalue=...) pairs streams of unequal length; plain zip stops at the shortest and can hide bugs when lengths differ on purpose.
from itertools import chain, zip_longest
list(chain([1, 2], [3], [4, 5])) # [1, 2, 3, 4, 5]
list(chain.from_iterable([[1, 2], [3]])) # [1, 2, 3]
list(zip_longest("ab", "xyz", fillvalue="-")) # [('a','x'), ('b','y'), ('-','z')]
itertools: windows, prefixes, and filtering iterators
pairwise(iterable) (Python 3.10+) yields adjacent pairs—clean for “compare neighbor” scans. Before 3.10, zip(s, s[1:]) on a sequence or a manual iterator step.
accumulate(iterable[, func]) is running sum by default; pass max, min, or a custom binary function for running aggregates.
islice(iterable, start, stop, step) slices without copying—useful on infinite iterators (count, cycle) or to cap work.
takewhile / dropwhile split or skip prefixes by predicate; they do not re-evaluate the middle—know where the cut is.
groupby(iterable, key=...) groups consecutive runs only. Sort first (or otherwise arrange adjacency) if you need global grouping—it is not SQL GROUP BY.
from itertools import accumulate, groupby
list(accumulate([1, 2, 3, 4])) # [1, 3, 6, 10]
runs = [(k, list(g)) for k, g in groupby("aaabbc")]
# [('a', ['a','a','a']), ('b', ['b','b']), ('c', ['c'])]
collections.Counter: frequencies in one object
Counter(iterable) counts hashable items; .most_common(k) returns the top k pairs—interview shorthand for “majority,” “top k,” or validation of character counts.
Counter supports addition and subtraction of counts (multiset-style), and intersection via & / union via | when you model “minimum/maximum count per key” puzzles.
from collections import Counter
Counter("abracadabra").most_common(2) # [('a', 5), ('b', 2)]
Counter("aabc") - Counter("ab") # Counter({'a': 1, 'c': 1})
collections.deque: O(1) ends and BFS
deque gives O(1) append, appendleft, pop, popleft. It is the default structure for BFS (queue) and for sliding windows of fixed size when you only need ends—not random access in the middle.
For “window of k elements,” maintain indices or store pairs (index, value) if you need to drop stale entries from the left.
from collections import deque
q = deque([1, 2, 3])
q.append(4)
q.popleft() # 1
defaultdict: graphs, grouping, and “first touch” maps
defaultdict(factory) supplies a default for missing keys so you avoid if key not in d boilerplate. Typical factories: list (adjacency lists, grouping), int (counting with +=), set.
Interview tip: defaultdict(list) for graph[u].append(v) and for buckets[key].append(item) reads faster than manual setdefault.
from collections import defaultdict
dd = defaultdict(list)
dd["a"].append(1) # no KeyError
If you need exactly one default for all missing keys (e.g. a shared mutable default is wrong), use dict.get(key, default) or initialize explicitly—defaultdict is for per-key fresh defaults from the factory.
collections.OrderedDict and deque vs list
OrderedDict remembers insertion order (since 3.7, plain dict does too). It still matters for move_to_end and a few LRU-style patterns; many interviews are fine with dict + explicit ordering logic.
For FIFO eviction, deque with maxlen gives a bounded window without manual index juggling.
When to drop to indexes
Random access, heavy pruning in backtracking, or in-place array algorithms still want classic loops and indexing—see N-Queens and DFS style posts. itertools shines for forward-only generation; Counter and defaultdict shine when the problem is aggregation or sparse structure, not pointer chasing on a tiny fixed grid.
Conclusion
itertools names the combinatorial shape of the search; collections names the shape of the data you accumulate. Together they reduce custom bookkeeping and free mental bandwidth for the actual invariant. Poetry for Dependency Management and Packaging consolidates dependencies with Poetry—reproducible graphs when the interview turns into a real service.