Python Syntax for DSA

Quick revision of the built-ins and collections you use in every problem⏱ 31 min readUpdated

The Python built-ins, collections and standard-library helpers you type in every coding round, with tested snippets and the Big-O of each operation.

0 of 59 done

Python lets you write a working solution in very few lines, but only if the right built-in comes to mind instantly: deque instead of list.pop(0), heapq with negation for a max-heap, Counter instead of a hand-rolled frequency map. This sheet groups that everyday syntax by data structure and lists the cost of each operation.

Everything assumes Python 3.10 or newer. Every snippet was run and the comment on each print line is its real output, so you can paste it into the editor and tweak it. Tick items off once you can type them from memory, and read the bullets: they are the gotchas that cost time in interviews.

Lists & slicing

Python's dynamic array: your default container for almost everything.

  1. 1Create a list and a 2D grid (correctly)

    Build each row of a grid with a comprehension so every row is its own list.

    • [[0] * c] * r repeats the SAME row object r times: writing one cell changes every row.
    • [0] * n is fine for a 1D list of immutable values like numbers or None.
    python
    a = [0] * 3
    grid = [[0] * 3 for _ in range(2)]  # 2 rows x 3 cols, independent
    grid[0][0] = 1
    print(grid)                # [[1, 0, 0], [0, 0, 0]]
    bad = [[0] * 3] * 2        # both rows are the same list object
    bad[0][0] = 1
    print(bad)                 # [[1, 0, 0], [1, 0, 0]]
    print(a, len(grid), len(grid[0]))  # [0, 0, 0] 2 3
    
  2. 2append, pop, insert, remove, extend

    Add and remove at the end in O(1); anything at the front or middle shifts elements and costs O(n).

    • a.pop(0) and a.insert(0, x) are O(n); use collections.deque for a queue.
    • a.remove(x) deletes the first match and raises ValueError if x is absent.
    • a[-1] reads the last element; it raises IndexError on an empty list.
    python
    a = [1, 2, 3]
    a.append(4)
    a.insert(0, 0)
    print(a)                # [0, 1, 2, 3, 4]
    print(a.pop())          # 4
    print(a.pop(0))         # 0
    a.remove(2)
    print(a, a[-1])         # [1, 3] 3
    a.extend([7, 8])
    print(a)                # [1, 3, 7, 8]
    
  3. 3Slicing a[i:j:k] and reversing

    A slice copies the items from index i up to (not including) j, taking every k-th; negative indexes count from the end.

    • a[::-1] returns a reversed copy; a.reverse() reverses in place and returns None.
    • Slices never raise on out-of-range bounds, and a[:] is a shallow copy (handy as path[:] in backtracking).
    python
    a = [0, 1, 2, 3, 4, 5]
    print(a[1:4])          # [1, 2, 3]
    print(a[:2], a[4:])    # [0, 1] [4, 5]
    print(a[::2], a[-2:])  # [0, 2, 4] [4, 5]
    print(a[::-1])         # [5, 4, 3, 2, 1, 0]
    b = a[:]
    b[0] = 99
    print(a[0], b[0])      # 0 99
    print(a[10:20])        # []
    
  4. 4len, in, index, count

    Quick questions about a list: its size, whether it holds a value, where, and how often.

    • x in a scans the whole list (O(n)); build a set once if you test membership repeatedly.
    • a.index(x) raises ValueError when x is missing, so check x in a first.
    python
    a = [4, 7, 4, 9]
    print(len(a))           # 4
    print(7 in a, 5 in a)   # True False
    print(a.index(4))       # 0
    print(a.count(4))       # 2
    print(not [])           # True
    
  5. 5enumerate and zip

    enumerate gives (index, value) pairs; zip walks several sequences side by side.

    • enumerate(a, start=1) begins counting at 1 without touching the list.
    • zip stops at the shortest input; list(zip(*grid)) transposes a matrix.
    python
    names = ['ann', 'bob', 'cat']
    scores = [90, 75, 88]
    for i, n in enumerate(names, start=1):
        print(i, n, end=' ')            # 1 ann 2 bob 3 cat
    print()
    print(list(zip(names, scores)))     # [('ann', 90), ('bob', 75), ('cat', 88)]
    print(list(zip([1, 2, 3], 'ab')))   # [(1, 'a'), (2, 'b')]
    print(list(zip(*[[1, 2], [3, 4]]))) # [(1, 3), (2, 4)]
    
  6. 6List comprehensions

    Build a new list from a loop and an optional filter in one readable line.

    • Nested loops read left to right, the same order as written-out for loops.
    • Use a generator (x for x in ...) inside sum/any/max to skip building a list.
    python
    sq = [x * x for x in range(5)]
    print(sq)                         # [0, 1, 4, 9, 16]
    evens = [x for x in range(10) if x % 2 == 0]
    print(evens)                      # [0, 2, 4, 6, 8]
    flat = [v for row in [[1, 2], [3]] for v in row]
    print(flat)                       # [1, 2, 3]
    tags = ['even' if x % 2 == 0 else 'odd' for x in range(3)]
    print(tags)                       # ['even', 'odd', 'even']
    
  7. 7sum, min, max with key; any and all

    Aggregate a sequence in O(n); key= decides what is compared and default= covers empty input.

    • min([]) and max([]) raise ValueError; pass default= when the input may be empty.
    • all([]) is True and any([]) is False.
    python
    a = [3, -7, 5]
    print(sum(a), min(a), max(a))     # 1 -7 5
    print(max(a, key=abs))            # -7
    print(min(['kiwi', 'fig', 'banana'], key=len))  # fig
    print(max([], default=0))         # 0
    print(any(x < 0 for x in a))      # True
    print(all(x > 0 for x in a))      # False
    print(all([]), any([]))           # True False
    
OperationCodeTime
Index read / writea[i], a[i] = xO(1)
Append / pop lasta.append(x), a.pop()O(1) amortised
Insert / pop at front or middlea.insert(i, x), a.pop(i)O(n)
Membership / index / countx in a, a.index(x)O(n)
Slice copya[i:j]O(j - i)
Lengthlen(a)O(1)
Sorta.sort(), sorted(a)O(n log n)
Min / max / summin(a), sum(a)O(n)

Strings

Immutable sequences of characters with a rich set of methods.

  1. 8Indexing, slicing and reversing

    Strings index and slice exactly like lists, so s[::-1] is the quickest palindrome check.

    • Strings are immutable: s[0] = 'x' raises TypeError.
    • reversed(s) gives an iterator; wrap it in ''.join(...) to get a string back.
    python
    s = "python"
    print(s[0], s[-1], len(s))   # p n 6
    print(s[1:4])                # yth
    print(s[::-1])               # nohtyp
    print(''.join(reversed(s)))  # nohtyp
    w = "level"
    print(w == w[::-1])          # True
    
  2. 9Build strings with a list + join

    Collect pieces in a list and join once at the end instead of growing a string in a loop.

    • s += c inside a loop may copy the string each time (O(n^2) in the worst case); ''.join(parts) is linear.
    • To edit characters, convert with list(s), change the list, then join it back.
    python
    s = "abc"
    chars = list(s)
    chars[0] = 'z'
    print(''.join(chars))        # zbc
    parts = []
    for i in range(4):
        parts.append(str(i))
    print('-'.join(parts))       # 0-1-2-3
    print(''.join(map(str, [1, 2, 3])))  # 123
    
  3. 10split, join and strip

    split breaks text into tokens, join glues them back, strip trims the ends.

    • split() with no argument splits on any run of whitespace and drops empty tokens; split(',') keeps them.
    • join only accepts strings, so map numbers with str first.
    python
    line = "  the  quick brown "
    print(line.split())               # ['the', 'quick', 'brown']
    print("a,b,,c".split(','))        # ['a', 'b', '', 'c']
    print(' '.join(['x', 'y', 'z']))  # x y z
    print(line.strip() + '|')         # the  quick brown|
    print("xxhixx".strip('x'))        # hi
    print("a-b-c".replace('-', ''))   # abc
    
  4. 11find, index, count, startswith

    Search inside a string for a substring, its position, or how many times it occurs.

    • find returns -1 when missing; index raises ValueError.
    • count counts non-overlapping matches.
    python
    s = "banana"
    print(s.find("an"), s.find("z"))   # 1 -1
    print(s.index("na"))               # 2
    print(s.count("a"), s.count("ana"))  # 3 1
    print(s.startswith("ban"), s.endswith("na"))  # True True
    print("nan" in s)                  # True
    
  5. 12Character tests and case

    isalpha, isdigit, isalnum, isspace and lower/upper cover most input-cleaning tasks.

    • These methods work on whole strings too: '123'.isdigit() is True, ''.isdigit() is False.
    python
    print("abc".isalpha(), "123".isdigit())    # True True
    print("a1".isalnum(), " ".isspace())       # True True
    print("Q".isupper(), "Hi".lower(), "hi".upper())  # True hi HI
    s = "A man, a plan"
    clean = [c.lower() for c in s if c.isalnum()]
    print(''.join(clean))                      # amanaplan
    
    Practice: Valid Palindrome →
  6. 13ord and chr

    Convert between a character and its code point; ord(c) - ord('a') maps a-z to 0-25.

    • A 26-slot list indexed this way is a fast fixed-size counter for lowercase letters.
    python
    print(ord('a'), chr(98))         # 97 b
    counts = [0] * 26
    for c in "abca":
        counts[ord(c) - ord('a')] += 1
    print(counts[:3])                # [2, 1, 1]
    print(chr(ord('a') + 25))        # z
    print(tuple(counts) == tuple([2, 1, 1] + [0] * 23))  # True
    
    Practice: Valid Anagram →
  7. 14f-strings

    Embed expressions in a string with f"...{expr}..." and an optional format spec after a colon.

    • {x:.2f} rounds to 2 decimals, {n:03d} zero-pads, {n:b} prints binary, {x=} shows name and value.
    python
    name, score = "Ada", 9.5678
    print(f"{name} scored {score:.2f}")   # Ada scored 9.57
    n = 5
    print(f"{n:03d} {n:b} {n=}")          # 005 101 n=5
    print(f"[{'x':>3}]")                  # [  x]
    print(f"{1234567:,}")                 # 1,234,567
    
OperationCodeTime
Index / lengths[i], len(s)O(1)
Slice / reverses[i:j], s[::-1]O(k) for k chars
Concatenates + tO(n + m)
Join many pieces''.join(parts)O(total length)
Substring searcht in s, s.find(t)O(n * m) worst
Split / strip / replaces.split(), s.strip()O(n)
Compares == tO(n)

dict, defaultdict & Counter

Hash maps: O(1) average lookups for counting, grouping and caching.

  1. 15dict basics and get with a default

    Store key-value pairs; get(key, default) reads safely when the key may be missing.

    • d[key] raises KeyError for a missing key; get returns None or your default.
    • Dicts remember insertion order (guaranteed since 3.7).
    • Keys must be hashable: ints, strings, tuples - not lists or sets.
    python
    d = {'a': 1}
    d['b'] = 2
    print(d.get('z'), d.get('z', 0))   # None 0
    print('a' in d, len(d))            # True 2
    d['a'] = d.get('a', 0) + 1
    print(d)                           # {'a': 2, 'b': 2}
    del d['b']
    print(d.pop('a'), d)               # 2 {}
    
    Practice: Two Sum →
  2. 16Iterate items, keys, values; setdefault

    Loop with .items() to get key and value together; setdefault inserts a starting value only if the key is new.

    • Adding or deleting keys while looping raises RuntimeError; loop over list(d) if you must modify.
    • setdefault(k, []) builds the empty list on every call, even when unused; defaultdict is cleaner.
    python
    ages = {'ann': 30, 'bob': 25}
    for k, v in ages.items():
        print(k, v, end='; ')          # ann 30; bob 25;
    print()
    print(list(ages), list(ages.values()))  # ['ann', 'bob'] [30, 25]
    graph = {}
    for u, v in [(1, 2), (1, 3), (2, 3)]:
        graph.setdefault(u, []).append(v)
    print(graph)                       # {1: [2, 3], 2: [3]}
    
  3. 17defaultdict for grouping and adjacency lists

    A dict that creates a default value (list, int, set) the first time a key is touched.

    • Merely reading dd[k] inserts k, which can grow the dict or break a 'key exists' check; use k in dd to test.
    • Pass the factory, not a call: defaultdict(list), not defaultdict([]).
    python
    from collections import defaultdict
    groups = defaultdict(list)
    for w in ["eat", "tea", "tan", "ate"]:
        groups[''.join(sorted(w))].append(w)
    print(dict(groups))   # {'aet': ['eat', 'tea', 'ate'], 'ant': ['tan']}
    cnt = defaultdict(int)
    cnt['x'] += 1
    print(cnt['x'], cnt['missing'], len(cnt))  # 1 0 2
    
    Practice: Group Anagrams →
  4. 18Counter: counting, most_common, arithmetic

    A dict subclass that counts hashable items in one call and supports multiset maths.

    • Missing keys read as 0 without being inserted.
    • a - b keeps only positive counts, so an empty result means b covers a (ransom-note check).
    • most_common(k) breaks ties by first appearance.
    python
    from collections import Counter
    c = Counter("mississippi")
    print(c['s'], c['z'])              # 4 0
    print(c.most_common(2))            # [('i', 4), ('s', 4)]
    print(Counter("listen") == Counter("silent"))  # True
    a, b = Counter("aabc"), Counter("abd")
    print(a - b)                       # Counter({'a': 1, 'c': 1})
    print(a & b, (a + b)['a'])         # Counter({'a': 1, 'b': 1}) 3
    print(not (Counter("ab") - Counter("aabb")))  # True
    
    Practice: Ransom Note →
  5. 19Dict comprehension

    Build a dict from any iterable in one expression, e.g. value-to-index maps.

    • When a key repeats, the later value wins.
    • dict(zip(keys, values)) is another one-liner for pairing two lists.
    python
    nums = [3, 1, 3]
    last = {v: i for i, v in enumerate(nums)}
    print(last)                      # {3: 2, 1: 1}
    sq = {x: x * x for x in range(3)}
    print(sq)                        # {0: 0, 1: 1, 2: 4}
    print(dict(zip("ab", [1, 2])))   # {'a': 1, 'b': 2}
    
  6. 20Sort a dict by value

    Sort the keys (or the item pairs) with a key function that reads the value.

    • Use -count in the key for descending counts with ascending ties.
    • max(d, key=d.get) finds the key with the largest value in O(n).
    python
    freq = {'a': 3, 'b': 1, 'c': 2}
    print(sorted(freq, key=freq.get))                    # ['b', 'c', 'a']
    print(sorted(freq.items(), key=lambda kv: -kv[1]))   # [('a', 3), ('c', 2), ('b', 1)]
    print(max(freq, key=freq.get))                       # a
    
    Practice: Top K Frequent Elements →
OperationCodeTime
Get / set / deleted[k], d[k] = v, del d[k]O(1) avg
Membershipk in dO(1) avg
Safe readd.get(k, default)O(1) avg
Iteratefor k, v in d.items()O(n)
Count itemsCounter(iterable)O(n)
Top k countsc.most_common(k)O(n log k)
Sort by valuesorted(d, key=d.get)O(n log n)

set

Unordered unique values with O(1) average membership tests.

  1. 21add, remove, discard and membership

    Use a set whenever you only need to know 'have I seen this?'.

    • {} is an empty dict; write set() for an empty set.
    • remove raises KeyError if the value is absent; discard does not.
    • list(dict.fromkeys(a)) removes duplicates while keeping the original order.
    python
    s = set()
    for x in [3, 1, 3]:
        s.add(x)
    print(len(s), 3 in s)       # 2 True
    s.discard(99)
    s.remove(1)
    print(s)                    # {3}
    nums = [3, 1, 3, 2]
    print(len(set(nums)) != len(nums))   # True
    print(list(dict.fromkeys(nums)))     # [3, 1, 2]
    
    Practice: Contains Duplicate →
  2. 22Union, intersection, difference

    Operators combine sets in time linear in their sizes.

    • | union, & intersection, - difference, ^ symmetric difference, <= subset.
    • Printed set order is not guaranteed in general; do not rely on it in answers.
    python
    a, b = {1, 2, 3}, {2, 3, 4}
    print(a | b)          # {1, 2, 3, 4}
    print(a & b)          # {2, 3}
    print(a - b)          # {1}
    print(a ^ b)          # {1, 4}
    print({1, 2} <= a)    # True
    
  3. 23Tuples and frozenset as keys

    Only hashable values can go into a set or be dict keys, so store coordinates as tuples and sets as frozensets.

    • Adding a list raises TypeError: unhashable type: 'list'; convert with tuple(lst).
    • A frozenset key ignores order, which is handy for 'same group of items' lookups.
    python
    visited = set()
    visited.add((0, 1))
    print((0, 1) in visited)            # True
    state = [2, 3]
    visited.add(tuple(state))
    print(len(visited))                 # 2
    groups = {frozenset("abc"): "g1"}
    print(groups[frozenset("cba")])     # g1
    
OperationCodeTime
Add / remove / discards.add(x), s.discard(x)O(1) avg
Membershipx in sO(1) avg
Build from listset(a)O(n)
Uniona | bO(len(a) + len(b))
Intersectiona & bO(min(len(a), len(b)))
Differencea - bO(len(a))

heapq (heap)

A binary min-heap stored inside a plain list.

  1. 24heappush and heappop

    heapq keeps a list arranged so the smallest element is always at index 0.

    • heapq is a min-heap only; there is no comparator argument.
    • h[0] peeks at the minimum; the rest of the list is NOT sorted.
    python
    import heapq
    h = []
    for x in [5, 1, 4]:
        heapq.heappush(h, x)
    print(h[0])                        # 1
    print(heapq.heappop(h))            # 1
    print(heapq.heappop(h), len(h))    # 4 1
    
  2. 25heapify in O(n)

    Turn an existing list into a heap in place in linear time, faster than n pushes.

    • heapify returns None; keep using the same list.
    • Popping everything off yields sorted order (heap sort in O(n log n)).
    python
    import heapq
    a = [7, 2, 9, 1]
    heapq.heapify(a)
    print(a[0])                                        # 1
    print([heapq.heappop(a) for _ in range(len(a))])   # [1, 2, 7, 9]
    
  3. 26Max-heap via negation

    Push negated values into the min-heap and negate again when you read them.

    • Python 3.14 adds heapq.heappush_max / heappop_max, but negation works on every version and judge.
    • For tuples, negate only the field you want reversed: (-count, word).
    python
    import heapq
    nums = [3, 8, 5]
    h = [-x for x in nums]
    heapq.heapify(h)
    print(-h[0])                # 8
    heapq.heappush(h, -10)
    print(-heapq.heappop(h))    # 10
    print(-heapq.heappop(h))    # 8
    
  4. 27Tuples (priority, item)

    Push tuples so the heap orders by the first field, then the next field on ties.

    • This is the standard shape for Dijkstra: (dist, node).
    python
    import heapq
    tasks = []
    heapq.heappush(tasks, (2, "write"))
    heapq.heappush(tasks, (1, "plan"))
    heapq.heappush(tasks, (2, "test"))
    while tasks:
        pri, name = heapq.heappop(tasks)
        print(pri, name, end=' | ')      # 1 plan | 2 test | 2 write |
    print()
    
    Pattern: Shortest path →
  5. 28Tie-breaking gotcha: add a counter

    When priorities tie, Python compares the next field, which crashes if it is a dict, node or other non-comparable object.

    • Insert a unique, increasing counter as the second field: (priority, next(counter), item).
    • Heaps of lists like [dist, node] hit the same problem when nodes are custom objects.
    python
    import heapq, itertools
    h, counter = [], itertools.count()
    for dist, node in [(1, {'id': 'A'}), (1, {'id': 'B'})]:
        heapq.heappush(h, (dist, next(counter), node))
    print(heapq.heappop(h)[2])        # {'id': 'A'}
    try:
        heapq.heappush(h, (1, {'id': 'C'}))
        heapq.heappush(h, (1, {'id': 'D'}))
    except TypeError:
        print("dicts are not comparable")  # dicts are not comparable
    
  6. 29nlargest and nsmallest

    Return the k biggest or smallest items, optionally ordered by a key.

    • Cost is about O(n log k); for k = 1 just use min/max, for k near n just sort.
    python
    import heapq
    a = [5, 1, 9, 3, 7]
    print(heapq.nlargest(2, a))      # [9, 7]
    print(heapq.nsmallest(2, a))     # [1, 3]
    pts = [(1, 2), (3, 0), (0, 1)]
    print(heapq.nsmallest(1, pts, key=lambda p: p[0]**2 + p[1]**2))  # [(0, 1)]
    
    Practice: K Closest Points to Origin →
  7. 30K-th largest with a size-k min-heap

    Keep only the k largest values seen so far; the heap's top is then the k-th largest.

    • O(n log k) time, O(k) space.
    • heapq.heappushpop(h, x) pushes then pops in one faster step.
    python
    import heapq
    def kth_largest(nums, k):
        h = []
        for x in nums:
            heapq.heappush(h, x)
            if len(h) > k:
                heapq.heappop(h)
        return h[0]
    print(kth_largest([3, 2, 1, 5, 6, 4], 2))   # 5
    
    Practice: Kth Largest Element in an Array →
OperationCodeTime
Peek minh[0]O(1)
Push / popheappush(h, x), heappop(h)O(log n)
Push then popheappushpop(h, x)O(log n)
Build heapheapify(a)O(n)
k largest / smallestnlargest(k, a)O(n log k)

deque, queue & stack

O(1) operations at both ends for BFS, stacks and sliding windows.

  1. 31collections.deque

    A double-ended queue with O(1) append and pop on both sides.

    • Indexing the middle q[i] is O(n); deque is for the ends.
    • deque(maxlen=k) silently drops from the other end when full.
    python
    from collections import deque
    q = deque([2, 3])
    q.append(4)
    q.appendleft(1)
    print(q)                       # deque([1, 2, 3, 4])
    print(q.popleft(), q.pop())    # 1 4
    print(q[0], q[-1], len(q))     # 2 3 2
    q.rotate(1)
    print(list(q))                 # [3, 2]
    
  2. 32BFS skeleton

    Pop from the left, push neighbours on the right; the first time you reach a node is via a shortest path in an unweighted graph.

    • Mark nodes as visited when you enqueue them, not when you pop them, to avoid duplicates.
    • For level-by-level work, loop for _ in range(len(q)) inside the while.
    python
    from collections import deque
    graph = {0: [1, 2], 1: [3], 2: [3], 3: []}
    dist = {0: 0}
    q = deque([0])
    while q:
        u = q.popleft()
        for v in graph[u]:
            if v not in dist:
                dist[v] = dist[u] + 1
                q.append(v)
    print(dist)                    # {0: 0, 1: 1, 2: 1, 3: 2}
    
    Pattern: Graph BFS →
  3. 33List as a stack

    A plain list is the idiomatic stack: append to push, pop() to pop, [-1] to peek.

    • Check if stack before stack[-1] or pop(); both raise IndexError on empty.
    • Python has no separate Stack class; do not use queue.LifoQueue (it is for threads).
    python
    def valid(s):
        pairs = {')': '(', ']': '[', '}': '{'}
        stack = []
        for c in s:
            if c in pairs:
                if not stack or stack.pop() != pairs[c]:
                    return False
            else:
                stack.append(c)
        return not stack
    print(valid("([]{})"), valid("(]"), valid("(("))   # True False False
    
    Practice: Valid Parentheses →
  4. 34Monotonic deque: sliding window max

    Keep indices whose values decrease from front to back; the front is always the window maximum.

    • Each index is pushed and popped at most once, so the whole pass is O(n).
    • Store indices, not values, so you can tell when the front has left the window.
    python
    from collections import deque
    def window_max(nums, k):
        dq, out = deque(), []
        for i, x in enumerate(nums):
            while dq and nums[dq[-1]] <= x:
                dq.pop()
            dq.append(i)
            if dq[0] <= i - k: dq.popleft()   # front left the window
            if i >= k - 1:
                out.append(nums[dq[0]])
        return out
    print(window_max([1, 3, -1, -3, 5, 3, 6, 7], 3))  # [3, 3, 5, 5, 6, 7]
    
    Practice: Sliding Window Maximum →
OperationCodeTime
Push / pop rightq.append(x), q.pop()O(1)
Push / pop leftq.appendleft(x), q.popleft()O(1)
Peek endsq[0], q[-1]O(1)
Index middleq[i]O(n)
Stack push / pop (list)st.append(x), st.pop()O(1) amortised
Queue via list (avoid)a.pop(0)O(n)

Sorting & comparators

Stable O(n log n) sorting driven by key functions.

  1. 35sorted() vs .sort()

    sorted returns a new list from any iterable; list.sort() sorts in place and returns None.

    • a = a.sort() is a classic bug: it sets a to None.
    • Both are stable, so equal keys keep their original order; reverse=True flips the order.
    python
    a = [3, 1, 2]
    b = sorted(a)
    print(a, b)                          # [3, 1, 2] [1, 2, 3]
    r = a.sort()
    print(a, r)                          # [1, 2, 3] None
    print(sorted("bca", reverse=True))   # ['c', 'b', 'a']
    
  2. 36key=lambda and multi-key tuples

    A key function maps each element to the value to compare; return a tuple to sort by several fields.

    • Negate a numeric field inside the tuple to make just that field descending.
    • operator.itemgetter(1) is a fast alternative to lambda p: p[1].
    python
    ps = [("bo", 25), ("al", 30), ("cy", 25)]
    print(sorted(ps, key=lambda p: p[1]))           # [('bo', 25), ('cy', 25), ('al', 30)]
    print(sorted(ps, key=lambda p: (-p[1], p[0])))  # [('al', 30), ('bo', 25), ('cy', 25)]
    words = ["bb", "a", "ccc", "dd"]
    print(sorted(words, key=lambda w: (len(w), w))) # ['a', 'bb', 'dd', 'ccc']
    
  3. 37functools.cmp_to_key

    Wrap an old-style two-argument comparator when the order cannot be expressed as a simple key.

    • Return a negative number if a should come first, positive if b should, 0 if equal.
    • Classic use: ordering numbers as strings to form the largest number.
    python
    from functools import cmp_to_key
    def cmp(a, b):
        if a + b > b + a:
            return -1
        return 1 if a + b < b + a else 0
    nums = [3, 30, 34, 5, 9]
    s = sorted(map(str, nums), key=cmp_to_key(cmp))
    print(''.join(s))              # 9534330
    
  4. 38Sorting intervals

    Sort by start time first; overlapping intervals then sit next to each other and can be merged in one pass.

    • For 'fewest removals' or 'arrows' problems, sort by end time instead.
    python
    intervals = [[8, 10], [1, 3], [2, 6]]
    intervals.sort(key=lambda x: x[0])
    merged = []
    for s, e in intervals:
        if merged and s <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], e)
        else:
            merged.append([s, e])
    print(merged)                  # [[1, 6], [8, 10]]
    
    Practice: Merge Intervals →
OperationCodeTime
Sort in placea.sort(key=f)O(n log n)
New sorted listsorted(it, key=f)O(n log n)
Reverse ordersorted(a, reverse=True)O(n log n)
Custom comparatorsorted(a, key=cmp_to_key(c))O(n log n)
Min / max onlymin(a, key=f)O(n)

Binary search & bisect

Find positions in sorted data in O(log n).

  1. 39bisect_left and bisect_right

    Return the insertion point that keeps a sorted list sorted: before equal elements (left) or after them (right).

    • bisect_right(a, x) - bisect_left(a, x) counts occurrences of x.
    • Check i < len(a) and a[i] == x before treating the result as a hit.
    • Since 3.10 both accept key= for searching by a field.
    python
    import bisect
    a = [1, 2, 2, 2, 5]
    print(bisect.bisect_left(a, 2))    # 1
    print(bisect.bisect_right(a, 2))   # 4
    print(bisect.bisect_right(a, 2) - bisect.bisect_left(a, 2))  # 3
    i = bisect.bisect_left(a, 4)
    print(i, i < len(a) and a[i] == 4) # 4 False
    
    Pattern: Binary search →
  2. 40insort

    Insert a value into a sorted list at the right place.

    • Finding the spot is O(log n) but shifting elements makes it O(n) overall.
    • For many inserts, a heap or sortedcontainers.SortedList (available on LeetCode) is faster.
    python
    import bisect
    a = [1, 4, 9]
    bisect.insort(a, 5)
    bisect.insort(a, 0)
    print(a)                 # [0, 1, 4, 5, 9]
    
  3. 41Manual lo/hi template (first True)

    Binary search on the answer: find the smallest value where a monotone condition flips from False to True.

    • Use lo < hi, hi = mid on True and lo = mid + 1 on False; the loop always ends at the answer.
    • Python ints never overflow, so (lo + hi) // 2 is safe.
    python
    def first_true(lo, hi, ok):
        while lo < hi:
            mid = (lo + hi) // 2
            if ok(mid):
                hi = mid
            else:
                lo = mid + 1
        return lo
    print(first_true(0, 100, lambda x: x * x >= 50))   # 8
    piles, h = [3, 6, 7, 11], 8
    fits = lambda k: sum((p + k - 1) // k for p in piles) <= h
    print(first_true(1, max(piles), fits))             # 4
    
    Practice: Koko Eating Bananas →
OperationCodeTime
Insertion pointbisect_left(a, x)O(log n)
After equal itemsbisect_right(a, x)O(log n)
Sorted insertinsort(a, x)O(n)
Search on answerwhile lo < hiO(log range * check)

itertools & functools

Ready-made combinatorics, prefix sums and memoisation.

  1. 42permutations, combinations, product

    Generate orderings, subsets of a fixed size, or the cartesian product without writing recursion.

    • Outputs are tuples and are produced lazily; wrap in list only if you need them all.
    • Counts explode fast: n! permutations, C(n, r) combinations.
    python
    from itertools import permutations, combinations, product
    print(list(permutations([1, 2, 3], 2)))  # [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
    print(list(combinations('abc', 2)))      # [('a', 'b'), ('a', 'c'), ('b', 'c')]
    print(list(product([0, 1], repeat=2)))   # [(0, 0), (0, 1), (1, 0), (1, 1)]
    print(len(list(permutations(range(4)))))  # 24
    
    Pattern: Backtracking →
  2. 43accumulate for prefix sums

    Running totals in one call; prepend 0 so that pre[j] - pre[i] is the sum of a[i:j].

    • initial=0 (3.8+) prepends the 0 for you.
    • Pass another function, e.g. max, for running maximums.
    python
    from itertools import accumulate
    a = [3, 1, 4, 1]
    pre = list(accumulate(a, initial=0))
    print(pre)                         # [0, 3, 4, 8, 9]
    print(pre[3] - pre[1])             # 5
    print(list(accumulate(a, max)))    # [3, 3, 4, 4]
    
    Pattern: Prefix sum →
  3. 44groupby

    Group consecutive equal elements (or equal keys) into runs.

    • It only groups neighbours; sort first if you want all equal items together.
    • Each group is a one-shot iterator: consume it before moving to the next.
    python
    from itertools import groupby
    s = "aaabccdd"
    print([(k, len(list(g))) for k, g in groupby(s)])  # [('a', 3), ('b', 1), ('c', 2), ('d', 2)]
    print(''.join(f"{k}{len(list(g))}" for k, g in groupby(s)))  # a3b1c2d2
    
  4. 45lru_cache / cache for memoised DP

    Decorate a recursive function so repeated arguments return a stored result: top-down DP in one line.

    • Arguments must be hashable; pass tuples, not lists.
    • @cache (3.9+) equals @lru_cache(maxsize=None); call f.cache_clear() between test cases if the function is global.
    • Deep recursion still hits the recursion limit; raise it or switch to bottom-up.
    python
    from functools import cache
    @cache
    def ways(n):
        if n <= 1:
            return 1
        return ways(n - 1) + ways(n - 2)
    print(ways(40))                  # 165580141
    print(ways.cache_info().currsize)  # 41
    
    Practice: Climbing Stairs →
  5. 46reduce

    Fold a sequence into one value by applying a two-argument function left to right.

    • Give an initial value as the third argument so empty input does not raise.
    • math.prod and sum are clearer for products and sums.
    python
    from functools import reduce
    import operator
    print(reduce(operator.mul, [1, 2, 3, 4], 1))         # 24
    print(reduce(lambda a, b: a ^ b, [4, 1, 2, 1, 2]))   # 4
    print(reduce(operator.add, [], 0))                   # 0
    
    Practice: Single Number →
ToolCodeOutput size / time
Permutationspermutations(a, r)n! / (n - r)!
Combinationscombinations(a, r)C(n, r)
Cartesian productproduct(a, repeat=k)n^k
Prefix sumsaccumulate(a, initial=0)O(n)
Runs of equal itemsgroupby(a)O(n)
Memoised call@cacheO(1) avg per cached hit

Classes & nodes

Linked-list and tree nodes, quick records, and function gotchas.

  1. 47ListNode and a dummy head

    A tiny class with val and next; a dummy head lets you build or edit a list without special-casing the first node.

    • Return dummy.next, not dummy.
    • Check node is None (or not node) before reading node.next.
    python
    class ListNode:
        def __init__(self, val=0, next=None):
            self.val, self.next = val, next
    def build(vals):
        dummy = tail = ListNode()
        for v in vals:
            tail.next = ListNode(v)
            tail = tail.next
        return dummy.next
    head = build([1, 2, 3])
    print(head.val, head.next.val, head.next.next.next)   # 1 2 None
    
    Pattern: Linked list →
  2. 48TreeNode and recursive traversal

    Nodes with left and right; most tree answers are a short recursive function returning a value per subtree.

    • The base case if not node handles empty trees and leaves' missing children.
    • Use nonlocal best inside a nested helper when it must update an outer variable.
    python
    class TreeNode:
        def __init__(self, val=0, left=None, right=None):
            self.val, self.left, self.right = val, left, right
    #     1
    #    / \
    #   2   3
    root = TreeNode(1, TreeNode(2), TreeNode(3))
    def inorder(n):
        return inorder(n.left) + [n.val] + inorder(n.right) if n else []
    def depth(n):
        return 1 + max(depth(n.left), depth(n.right)) if n else 0
    print(inorder(root), depth(root))   # [2, 1, 3] 2
    
    Pattern: Tree DFS →
  3. 49dataclass for quick records

    @dataclass writes __init__, __repr__ and __eq__ for you; order=True also adds comparisons by field order.

    • Fields compare in declaration order, so put the sort field first.
    • collections.namedtuple is a lighter immutable alternative.
    python
    from dataclasses import dataclass
    @dataclass(order=True)
    class Job:
        end: int
        start: int
        name: str = ""
    jobs = [Job(5, 1, "b"), Job(3, 0, "a")]
    print(min(jobs))                  # Job(end=3, start=0, name='a')
    print(sorted(jobs)[-1].name)      # b
    print(Job(1, 0) == Job(1, 0))     # True
    
  4. 50__lt__ so objects work in a heap

    Define __lt__ and your objects can go straight into heapq or sorted.

    • heapq only ever uses <, so __lt__ is enough.
    • Without it, pushing two objects raises TypeError; the counter-tuple trick is the alternative.
    python
    import heapq
    class Node:
        def __init__(self, cost, name):
            self.cost, self.name = cost, name
        def __lt__(self, other):
            return self.cost < other.cost
    h = []
    for c, n in [(3, "c"), (1, "a"), (2, "b")]:
        heapq.heappush(h, Node(c, n))
    print([heapq.heappop(h).name for _ in range(3)])   # ['a', 'b', 'c']
    
    Practice: Merge k Sorted Lists →
  5. 51Mutable default arguments

    Default values are created once when the function is defined, so a default list is shared by every call.

    • Use None as the default and create the list inside the function.
    • The same sharing bites backtracking: append path[:], not path, to the results.
    python
    def add_bad(x, acc=[]):
        acc.append(x)
        return acc
    print(add_bad(1), add_bad(2))      # [1, 2] [1, 2]
    def add_ok(x, acc=None):
        acc = [] if acc is None else acc
        acc.append(x)
        return acc
    print(add_ok(1), add_ok(2))        # [1] [2]
    

Handy utilities

Infinity, integer maths, recursion, fast input and bit tricks.

  1. 52Infinity: float('inf') and math.inf

    Start a running minimum at infinity (or maximum at minus infinity) so the first real value always wins.

    • Infinity is a float; convert the final answer back, e.g. return -1 if best == inf else best.
    • int(math.inf) raises OverflowError.
    python
    import math
    best = float('inf')
    for x in [7, 3, 9]:
        best = min(best, x)
    print(best)                                 # 3
    print(math.inf == float('inf'))             # True
    print(-math.inf < -10**18)                  # True
    print([math.inf] * 3)                       # [inf, inf, inf]
    
  2. 53// and % with negatives, divmod

    // floors toward minus infinity and % takes the sign of the divisor, unlike Java or C++.

    • For C-style truncation use int(a / b) (fine for normal sizes) or adjust the sign yourself.
    • Ceiling division without floats: -(-a // b) or (a + b - 1) // b for positive b.
    • / always returns a float, even for 4 / 2.
    python
    print(7 // 2, -7 // 2)        # 3 -4
    print(-7 % 3, 7 % -3)         # 2 -2
    print(int(-7 / 2))            # -3
    print(divmod(17, 5))          # (3, 2)
    print(4 / 2, -(-7 // 2))      # 2.0 4
    
  3. 54pow with mod, gcd, lcm and other math helpers

    Built-ins for modular exponentiation, gcd/lcm, integer square roots and binomials.

    • Python ints never overflow, but reduce modulo 10**9 + 7 as you go to keep them fast.
    • pow(a, -1, m) (3.8+) gives a modular inverse when gcd(a, m) is 1.
    python
    import math
    MOD = 10**9 + 7
    print(pow(2, 10, 1000), pow(3, -1, 7))   # 24 5
    print(pow(2, 100, MOD))                  # 976371285
    print(math.gcd(12, 18), math.lcm(4, 6))  # 6 12
    print(math.isqrt(17), math.comb(5, 2))   # 4 10
    print(2**64)                             # 18446744073709551616
    
  4. 55sys.setrecursionlimit

    The default limit is about 1000 frames, which deep DFS or memoised recursion can exceed.

    • Raise it at the top of the file for recursion depths in the thousands.
    • Very deep recursion can still crash the interpreter; rewrite as iterative with an explicit stack if depth may hit ~10^5.
    python
    import sys
    sys.setrecursionlimit(10**6)
    def depth(n):
        return 0 if n == 0 else 1 + depth(n - 1)
    print(depth(5000))               # 5000
    
  5. 56Fast input with sys.stdin

    On stdin-based judges, sys.stdin.readline is much faster than input() for large inputs.

    • readline keeps the trailing newline; int() and split() ignore it, but strip it before using the line as text.
    • For huge inputs, read everything at once: data = sys.stdin.buffer.read().split().
    python
    import sys
    input = sys.stdin.readline
    # stdin:
    # 3
    # 1 2 3
    n = int(input())
    nums = list(map(int, input().split()))
    print(n, sum(nums))             # 3 6
    
  6. 57Bit tricks

    Test, clear and count bits with a handful of operators.

    • x & 1 is odd/even, x & (x - 1) clears the lowest set bit, x & -x isolates it.
    • x.bit_count() (3.10+) counts set bits; bin(x).count('1') works everywhere.
    • a ^ a == 0, so XOR-ing everything cancels pairs.
    python
    x = 12
    print(bin(x), x & 1, x >> 1, x << 2)    # 0b1100 0 6 48
    print(x & (x - 1), x & -x)              # 8 4
    print(bin(x).count('1'), x.bit_count()) # 2 2
    print(x > 0 and x & (x - 1) == 0)       # False
    print(5 ^ 5, 5 ^ 0)                     # 0 5
    
  7. 58Swap and unpacking

    Tuple assignment swaps without a temp variable and unpacks sequences in one line.

    • The right side is fully evaluated first, then targets are assigned left to right; order matters in tricks like cur.next, prev, cur = prev, cur, cur.next.
    • first, *rest = a collects the remainder into a list.
    python
    a, b = 1, 2
    a, b = b, a
    print(a, b)                 # 2 1
    nums = [5, 6, 7]
    nums[0], nums[2] = nums[2], nums[0]
    print(nums)                 # [7, 6, 5]
    first, *rest = nums
    print(first, rest)          # 7 [6, 5]
    
  8. 59Grid neighbours with chained comparisons

    Loop over four direction offsets and bounds-check with 0 <= r < R, which Python reads as two comparisons.

    • Grids are grid[row][col]; get sizes with R, C = len(grid), len(grid[0]).
    python
    grid = [[1, 1, 0], [0, 1, 0]]
    R, C = len(grid), len(grid[0])
    r, c = 0, 1
    for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
        nr, nc = r + dr, c + dc
        if 0 <= nr < R and 0 <= nc < C and grid[nr][nc] == 1:
            print((nr, nc), end=' ')    # (1, 1) (0, 0)
    print()
    
    Practice: Number of Islands →

Quick tips

  • Use deque for queues and BFS; list.pop(0) silently turns an O(n) solution into O(n^2).
  • heapq is min-only: negate values (or the priority field) for a max-heap, and add a counter to tuples when items are not comparable.
  • Build 2D grids with a comprehension, never [[0] * c] * r, and default mutable parameters to None.
  • Reach for Counter, defaultdict, bisect and @cache before writing them by hand - interviewers value knowing the standard library.
  • State complexities as you code: dict/set lookups are O(1) average, x in list is O(n), sorting is O(n log n).

Topic list based on the free Everything You Need to Get Placed checklist by Mohammad Fraz. Explanations and examples are original.