L03 · Python for Speed — The 20 Idioms That Matter
The small set of standard-library tools that turn a fifteen-line interview solution into a five-line one, and the syntax you must be able to type without thinking.
🎯 Build a small, reliable Python vocabulary — Counter, defaultdict, heapq, deque, comprehensions, enumerate/zip — so the mechanical parts of a solution take seconds and your thinking goes to the algorithm.
Series: LeetCode — From Basics to Interview-Ready · Session 3 / 65 · Phase 0 · Foundations
What this session IS — a vocabulary, not a language
You already know Python's syntax. This session is about a small vocabulary of standard-library tools that come up in almost every interview problem. The point is fluency: when the algorithm calls for "count the letters," your hand should type Counter(s) before your brain finishes the sentence. Every second you spend fighting syntax is a second not spent on the actual problem.
The naive approach is to reinvent these each time — a manual dict with if key in d: d[key] += 1 else: d[key] = 1. It works, but it is four lines of boilerplate that can hide a bug, and it costs interview minutes. The idioms below collapse those to one line each.
The 20 idioms — the toolbox
Counting and grouping
from collections import Counter, defaultdict
Counter("aabbbc") # Counter({'b': 3, 'a': 2, 'c': 1}) — counts in one call
Counter(nums).most_common(2) # the 2 most frequent (value, count) pairs, sorted
d = defaultdict(list) # missing keys auto-create an empty list
d[key].append(x) # no "if key not in d" guard needed
d2 = defaultdict(int) # missing keys default to 0
d2[key] += 1 # a manual CounterHeaps (priority queues)
import heapq
h = []
heapq.heappush(h, 3) # O(log n) insert
heapq.heappush(h, 1)
heapq.heappop(h) # 1 — always the SMALLEST (min-heap), O(log n)
heapq.heappush(h, -x) # push NEGATIVES to simulate a MAX-heap
-heapq.heappop(h) # negate again on the way out
heapq.nlargest(3, nums) # top 3 without managing the heap yourself
heapq.heapify(nums) # turn a list into a heap in place, O(n)Deques (both-ends queues)
from collections import deque
q = deque([1, 2, 3])
q.append(4) # O(1) right
q.appendleft(0) # O(1) left (a list's insert(0, x) is O(n)!)
q.popleft() # O(1) — this is your BFS queueComprehensions and iteration helpers
[x * x for x in nums if x > 0] # filter + map in one line
{x for x in nums} # a set comprehension (dedupe)
{k: v for k, v in pairs} # a dict comprehension
for i, x in enumerate(nums): # index AND value, no range(len(...))
...
for a, b in zip(list1, list2): # walk two lists in lockstep
...
sorted(words, key=len) # sort by a computed key
sorted(pairs, key=lambda p: (-p[1], p[0])) # by count desc, then name ascThe little sharp tools
"".join(parts) # build a string in O(n), NOT += in a loop (O(n^2))
a, b = b, a # swap without a temp
min(nums), max(nums), sum(nums) # one-pass aggregates, O(n)
float("inf"), float("-inf") # sentinels for running min/max
divmod(17, 5) # (3, 2) — quotient and remainder together
nums[::-1] # reversed copy; s[i:j:2] strided slice- 1An interview gives you ~20 minutes of thinking, not typing.forced by · The clock does not pause while you recall dict-default syntax.
- 2Boilerplate (manual counting, guards) is where silent bugs hide.forced by · An off-by-one in a hand-rolled counter is easy to write and hard to spot under stress.
- 3Standard-library tools are already correct and complexity-optimal.forced by · Counter, heapq, deque are C-implemented and battle-tested.
- 4Fluency frees working memory for the algorithm.forced by · If typing is automatic, your attention stays on the invariant and edge cases.
The template — a solution skeleton using the vocabulary
Most array/string problems fit this shape. Notice how little of it is bespoke:
def solve(nums, target):
from collections import Counter, defaultdict
import heapq
counts = Counter(nums) # 1. summarise the input
groups = defaultdict(list) # 2. group if needed
for i, x in enumerate(nums): # 3. one pass with index+value
groups[some_key(x)].append(i)
heap = [] # 4. if you need "top/smallest K"
for key, val in counts.items():
heapq.heappush(heap, (val, key))
if len(heap) > k: # keep heap size at k -> O(n log k)
heapq.heappop(heap)
return [key for val, key in heap] # 5. comprehension to shape the outputYou will not use every line every time. The value is that each line is a reflex, so you assemble the right subset in seconds.
- Counting letters/frequencies -> Counter.
- Bucketing by a computed key -> defaultdict(list).
- Top-K / smallest-K / running priority -> heapq (negate for max).
- BFS / sliding window queue -> deque, never list.pop(0).
- Building output -> comprehension or ''.join, never += in a loop.
Memory technique — "CHeD-Sez", one peg per tool
Make the five workhorses a five-letter peg you can recite: C-H-D-S-Z →
- Counter — count.
- Heapq — top-K / priority.
- Defaultdict — group.
- Sorted(key=…) — order by a rule.
- Zip / enumerate — walk pairs / index+value.
Say "CHeD-Sez" (like a name) and each letter unpacks to a tool and its job. Because they map to the four-step model — summarise (C), prioritise (H), group (D), order/shape (S, Z) — recalling the peg recalls the whole workflow. Pair each with its one canonical line (Counter(s), heapq.heappop, d[k].append, sorted(x, key=…), enumerate(x)) and drill until you type them without thought.
What interviewers actually ask
These are the problems where the right idiom is most of the solution — recognising which tool to reach for is exactly the skill being tested:
- Valid Anagram (242) · Amazon, Bloomberg (Easy) — probes: do you reach for
Counter(s) == Counter(t)instead of hand-rolling counts. Follow-up: "what if inputs are unicode / streaming?" - Group Anagrams (49) · Amazon, Uber (Medium) — probes:
defaultdict(list)keyed by the sorted string (or a char-count tuple). Follow-up: "avoid the O(k log k) sort key" → use a 26-length count tuple. - Top K Frequent Elements (347) · Amazon, Meta (Medium) — probes:
Counter+heapq.nlargest(O(n log k)) or bucket sort (O(n)). Follow-up: "can you do it in O(n)?" - K Closest Points to Origin (973) · Amazon (Medium) — probes: a size-K max-heap of negative distances. Follow-up: "streaming points, unknown total."
- Number of Islands (200) · Amazon, Microsoft, Meta (Medium) — probes:
dequefor BFS flood fill (or recursion for DFS). Follow-up: "very large grid → BFS to avoid recursion limits." - Task Scheduler (621) · Amazon, Meta (Medium) — probes:
Counterof task frequencies plus greedy scheduling from the most frequent.
Worked example 1 — Top K Frequent, the idiomatic way
Problem in plain words: Return the k most frequent elements of nums.
Brute + cost. Count into a dict, sort all pairs by count, take the top k: O(n log n) from the full sort. Fine, but we can do O(n log k) with a bounded heap, and O(n) with buckets.
Insight + heap version. Keep a heap of at most k elements; the smallest count sits at the top and gets evicted when a bigger one arrives.
from collections import Counter
import heapq
def top_k_frequent(nums, k):
counts = Counter(nums) # O(n): {value: frequency}
heap = [] # min-heap of (count, value)
for value, count in counts.items():
heapq.heappush(heap, (count, value))
if len(heap) > k: # keep only the k largest counts
heapq.heappop(heap) # evict the smallest -> O(log k)
return [value for count, value in heap]
# Time O(n log k), space O(n).Even better (bucket sort). Counts range from 1..n, so index buckets by count:
def top_k_frequent_bucket(nums, k):
counts = Counter(nums)
buckets = [[] for _ in range(len(nums) + 1)] # buckets[c] = values with frequency c
for value, c in counts.items():
buckets[c].append(value)
result = []
for c in range(len(buckets) - 1, 0, -1): # walk high frequency -> low
for value in buckets[c]:
result.append(value)
if len(result) == k:
return result
# Time O(n), space O(n).Complexity. Heap: O(n log k). Bucket: O(n). Both use the vocabulary (Counter, heapq) rather than reinventing counting.
Follow-up. "Ties broken by value" → make the heap entries (count, value) and rely on tuple ordering, or sort within a bucket.
import heapq
nums = [5, 1, 8, 3, 9, 2]
h = []
for x in nums:
heapq.heappush(h, -x) # store negatives
largest3 = [-heapq.heappop(h) for _ in range(3)] # pop 3, negate back
print(largest3) # [9, 8, 5]Now redo it with heapq.nlargest(3, nums) and confirm you get the same answer with none of the negation bookkeeping.
Worked example 2 — Group Anagrams with defaultdict
Problem in plain words: Group words that are anagrams of each other.
Insight. Two words are anagrams iff they share the same letter multiset. Use that multiset as a dictionary key; defaultdict(list) collects the groups with no guard code.
from collections import defaultdict
def group_anagrams(words):
groups = defaultdict(list)
for w in words:
key = tuple(sorted(w)) # ('a','e','t') for "ate","eat","tea"
groups[key].append(w) # no "if key in groups" needed
return list(groups.values())
# Time O(n * k log k) for n words of length k; space O(n*k).Follow-up. "Drop the sort from the key." → use a 26-length count tuple as the key: O(n·k) total, no per-word sort.
def group_anagrams_fast(words):
groups = defaultdict(list)
for w in words:
count = [0] * 26
for ch in w:
count[ord(ch) - ord('a')] += 1
groups[tuple(count)].append(w) # tuple is hashable -> valid key
return list(groups.values())Where this shows up in production
collections.Counter and defaultdict are not interview-only toys — they are everywhere in real Python: tallying log lines by status code, grouping records by a key before aggregation, deduping with sets. heapq powers task schedulers and "keep the top N" dashboards; deque is the standard bounded buffer and BFS frontier. Fluency you build for interviews is the same fluency that makes your day-to-day Python shorter and less buggy, because you stop hand-rolling primitives the standard library already got right.
- Count and group with Counter and defaultdict instead of manual guards.
- Use heapq for top-K, including the negate-for-max-heap trick.
- Reach for deque (not list.pop(0)) for BFS and window queues.
- Write filter+map comprehensions and sorted(key=...) fluently.
- Build strings with ''.join, never += in a loop.
- Recite the CHeD-Sez peg and its canonical one-liners cold.
Practice queue
Solve each using the idioms — no hand-rolled counters or guards.
- Valid Anagram (242) — Easy · Counter equality. (spaced-repeat anchor: +7d)
- Contains Duplicate (217) — Easy · set in one line.
- Group Anagrams (49) — Medium · defaultdict + key choice.
- Top K Frequent Elements (347) — Medium · Counter + heap/bucket. (spaced-repeat anchor: +21d)
- K Closest Points to Origin (973) — Medium · size-K heap of negatives.
- Task Scheduler (621) — Medium · Counter + greedy.
- Sort Characters By Frequency (451) — Medium · Counter.most_common.