📒 problems 📖 glossary
Mock interview
46:30
ready
of 46:30
Lesson 5 · Foundations

Python idioms for interviews

📖 Walk me through it — plain English

This lesson isn't one algorithm — it's a toolbox of Python idioms, the small ready-made phrasings the language gives you so you don't have to re-invent them mid-interview. An "idiom" just means "the normal, expected way to write a thing in this language." For example, instead of building a new list by hand with a loop, Python lets you say it in one line: a comprehension like [x for x in nums if x % 2 == 0] reads as "x, for each x in nums, but only if x is even." Other tools here: slicing (grabbing a chunk of a list, like arr[i:j]), enumerate (loop and get the index i for free), zip (walk two lists side by side), and helpers from the standard library like Counter (counts how often each item appears) and defaultdict (a dictionary that auto-creates a default value so you never hit a missing-key error).

Think of it like cooking in a kitchen you've used for years. A beginner hunts for the can opener every time; a pro's hand goes straight to the right drawer without looking. The point of drilling these is the same — so the 10 seconds you'd waste recalling sorted syntax go to the actual problem instead.

Let's trace the flagship coded example at the bottom — two_sum. Goal: find two numbers in a list that add up to a target, and return their positions. It uses two idioms from this page at once: enumerate (gives index i and value v together) and a dictionary called seen that maps each value we've passed to its index. The trick: for each value v, instead of searching the whole list for its partner, we just check "have I already seen the number that completes the pair?" — that number is target - v. Let's run it on nums = [2, 7, 11] with target = 9.

Step 1 · i=0, v=2 (highlighted). The number that would complete the pair is target − v = 9 − 2 = 7. Is 7 in seen? seen is still empty, so no. Record it: seen now holds {2 → 0}.
2
7
11
Step 2 · i=1, v=7 (highlighted). Complement = 9 − 7 = 2. Is 2 in seen? Yes — we stored it in Step 1 at index 0. Match found.
2
7
11
Step 3 · Return the two positions: [seen[2], i] = [0, 1]. We never even looked at index 2 (the 11, now greyed out) — we stopped the moment the pair was complete.
2
7
11

Why it works: each value's partner is fixed (target - v), so checking a dictionary answers "is the partner already behind me?" instantly. A dictionary lookup is O(1) — roughly constant time no matter how big the dictionary gets — so we touch each element once and the whole thing is O(n) time, where n is the list length. The naive alternative — for every element, scan all the others — would be O(n²), meaning the work grows with the square of the list size. Same answer, far slower. That speed-up comes entirely from reaching for the right idiom (a dict + enumerate) instead of brute force — which is exactly the muscle memory this whole lesson is training.

If Python is your interview language, the goal isn't "knows Python" — it's reaches for the right idiom without thinking. The 10 seconds you spend looking up sorted syntax mid-interview is 10 seconds you didn't spend on the actual problem. This page is the brush-up: comprehensions, slicing, generators, the standard library moves interviewers expect.

Comprehensions — list, dict, set, nested
# List comprehension with filter
evens = [x for x in nums if x % 2 == 0]

# Dict comprehension — common in interviews for "build index"
idx = {v: i for i, v in enumerate(arr)}

# Set comprehension
seen = {s.lower() for s in words}

# Nested — flatten a 2D grid
flat = [cell for row in grid for cell in row]

# Ternary inside a comprehension
labels = ["even" if x % 2 == 0 else "odd" for x in nums]

# Generator expression — same syntax, parens not brackets, lazy
total = sum(x*x for x in nums if x > 0)

Reach for a comprehension when the body is one expression. If you need an if/else tree or multiple statements, write the loop — comprehensions hurt readability past one condition.

Slicing & unpacking
arr[::-1]            # reverse a list or string (O(n) — makes a copy)
arr[i:j]             # half-open, end exclusive
arr[:k], arr[k:]     # first k / from k onward
arr[::2]             # every 2nd element
s[-1]                # last char; s[-k:] for last k

# Multi-assign + swap — O(1), no temp variable
a, b = b, a
prev, curr = curr, prev.next

# Starred unpacking — first / last / middle
first, *rest = [1, 2, 3, 4]      # first=1, rest=[2,3,4]
*init, last = [1, 2, 3, 4]       # init=[1,2,3], last=4
head, *mid, tail = [1, 2, 3, 4]   # head=1, mid=[2,3], tail=4
enumerate / zip / sorted — the workhorses
for i, v in enumerate(arr):       # i = 0, 1, 2, ...
    ...

for a, b in zip(arr1, arr2):       # pairwise; stops at shorter
    ...

for a, b in zip(arr, arr[1:]):     # adjacent pairs trick
    ...

# sorted with key — interview gold
arr.sort(key=lambda x: (x[0], -x[1]))   # asc on x[0], desc on x[1]
sorted(words, key=len)                       # by length
sorted(items, key=lambda p: p.cost)         # by attribute

# min/max with key — one-liner extrema
closest = min(points, key=lambda p: p.x**2 + p.y**2)
longest = max(words, key=len)

# any / all with generators — short-circuit
has_neg = any(x < 0 for x in arr)
all_pos = all(x > 0 for x in arr)
collections + itertools — deep cuts
from collections import defaultdict, Counter, deque, OrderedDict

# defaultdict — no more KeyError; "group by" idiom
groups = defaultdict(list)
for word in words:
    groups[tuple(sorted(word))].append(word)   # anagram grouping

# Counter — frequency counts + arithmetic
c1, c2 = Counter("banana"), Counter("nab")
c1 - c2          # multiset subtract: Counter({'a': 2, 'n': 1})
c1 & c2          # intersection (min counts)
c1.most_common(3)  # top-3 by frequency

# deque — O(1) appendleft / popleft, BFS queue
q = deque([root])
while q:
    node = q.popleft()
    for nb in node.neighbors:
        q.append(nb)

from itertools import combinations, permutations, product, pairwise, accumulate, chain

combinations(arr, 3)         # all C(n,3) tuples
permutations(arr, 2)         # all ordered pairs
product([0,1], repeat=3)    # cartesian — bitmask enumeration
pairwise(arr)                # [(a0,a1), (a1,a2), ...] (Py 3.10+)
accumulate(arr)              # running prefix sums
chain(a, b, c)               # concatenate iterables lazily
Strings — the high-leverage tricks
s.join(parts)            # always faster than += in a loop
", ".join(str(x) for x in nums)

s.split(), s.split(",")
s.strip(), s.lower(), s.upper()
s.isdigit(), s.isalpha(), s.isalnum()
s.startswith(p), s.endswith(p)
s.replace("a", "b")

ord("a") == 97            # char → int; ord('a') - ord('a') = 0 for indexing
chr(97) == "a"            # int → char

# f-strings — debug form with =
n = 42
print(f"{n=}")            # "n=42" — great for debugging
print(f"{n:04d}")         # "0042" — zero-pad
print(f"{ratio:.2f}")      # "0.33"

# Palindrome check — slicing one-liner
def is_palindrome(s): return s == s[::-1]
Truthiness, gotchas, and the mutable-default trap
# Falsy: 0, 0.0, "", [], {}, set(), None, False
if not arr: ...      # empty check — Pythonic
if x is None: ...    # identity check — for None specifically
if x == None: ...    # works but is non-idiomatic; use 'is'

# 'is' vs '==' — identity vs equality
a, b = [1], [1]
a == b      # True  (same value)
a is b      # False (different objects)

# THE classic Python footgun — mutable default arg
def bad(x, acc=[]):       # shared across calls!
    acc.append(x); return acc

def good(x, acc=None):     # the fix
    if acc is None: acc = []
    acc.append(x); return acc

# dict.get / setdefault — avoid the if-else dance
count = freq.get(key, 0) + 1
groups.setdefault(k, []).append(v)

# Integer infinity for DP / comparisons
import math
best = math.inf
for x in arr: best = min(best, x)
Generators, memoization, type hints
# Generator function — yield instead of return
def walk(node):
    if not node: return
    yield node.val
    yield from walk(node.left)
    yield from walk(node.right)

# Memoize recursive DP with one decorator
from functools import cache
@cache
def fib(n):
    return n if n < 2 else fib(n-1) + fib(n-2)

# Modern type hints (Py 3.10+) — no more typing imports for basics
def two_sum(nums: list[int], target: int) -> list[int] | None:
    seen: dict[int, int] = {}
    for i, v in enumerate(nums):
        if target - v in seen:
            return [seen[target - v], i]
        seen[v] = i
    return None

Type hints aren't required, but adding them in an interview signals senior fluency — especially the modern list[int] + X | None form over the legacy List[int] + Optional[X].

Reach for the comprehension

One-line transforms (map/filter shape) read better as comprehensions. Reach for a loop the moment you need multiple statements or nested branching.

Tuples are the unsung hero

Hashable, immutable, can be dict keys / set members. Use tuples for "compound key" like (row, col) in grid DP or (state, depth) in memoized recursion.

Negative indexing > len()-1

arr[-1] not arr[len(arr)-1]. arr[-k:] for last k. Cleaner + harder to off-by-one.

Sort by a tuple key

arr.sort(key=lambda x: (x.priority, -x.timestamp)) sorts on multiple fields in one pass. Negate for descending. Memorize this shape.

heapq is a min-heap

For max-heap, negate values on push and pop: heapq.heappush(h, -x), then -heapq.heappop(h). Or push tuples: (-priority, item).

bisect for sorted insert

bisect.insort(arr, x) keeps arr sorted in O(log n) search + O(n) insert. bisect_left / bisect_right for find-position-without-inserting.

Walrus when it helps readability

while (chunk := f.read(1024)): assigns + tests in one. Use sparingly — when it removes a duplicate expression.

Don't over-Pythonify

A clear loop beats a one-line nested comprehension. Show off when it reads better, not when it shows off.

The interview tell: a candidate who writes idx = {v: i for i, v in enumerate(arr)} and arr.sort(key=lambda x: (x[0], -x[1])) without hesitating reads as 2+ years of real Python. Drill the idioms above until they're muscle memory — they're free style points and they save thinking time for the actual algorithm.

A 30-second warm-up to prove the in-browser runner works — every later lesson has one of these. Write the function, hit Run tests, and it grades you instantly. (The examples teach in Python; the live editor runs JavaScript — the logic is the same.)

→ Going deeper: Fluent Python meets its interview test in the stdlib containers. See Stdlib + data structures.