Skip to content

Python Idioms and Tricks for CP

Two solutions can have identical Big-O and still differ by 20 lines and a subtle bug. Idiomatic Python is shorter to write under time pressure and avoids some of the most common contest bugs. This page is a reference you’ll come back to constantly.

What you’ll learn

  • Comprehensions and generator expressions — and when to use which.
  • Slicing and reversal without extra loops.
  • Tuple packing/unpacking, enumerateenumerate, zipzip, and ** unpacking.
  • divmoddivmod for the pair every base-conversion problem needs.
  • Bit tricks: lowest set bit, bit_count()bit_count(), bit_length()bit_length().
  • Fast string building with joinjoin, and float("inf")float("inf") as a sentinel.
  • The 2D-array aliasing bug — the single most common silent bug in CP Python.

Comprehensions and generator expressions

A comprehension builds the whole collection in one expression; a generator expression (parentheses instead of brackets) produces items lazily, one at a time, without materializing the full list.

comprehensions.py
nums = [1, 2, 3, 4, 5, 6, 7, 8]
 
squares = [x * x for x in nums]                 # list comprehension
evens_squared = [x * x for x in nums if x % 2 == 0]
lookup = {x: x * x for x in nums}                # dict comprehension
unique_mods = {x % 3 for x in nums}               # set comprehension
 
# generator expression: no intermediate list — great for sum()/any()/all()
total_of_squares = sum(x * x for x in nums)
 
print(squares)
print(evens_squared)
print(lookup)
print(unique_mods)
print(total_of_squares)
comprehensions.py
nums = [1, 2, 3, 4, 5, 6, 7, 8]
 
squares = [x * x for x in nums]                 # list comprehension
evens_squared = [x * x for x in nums if x % 2 == 0]
lookup = {x: x * x for x in nums}                # dict comprehension
unique_mods = {x % 3 for x in nums}               # set comprehension
 
# generator expression: no intermediate list — great for sum()/any()/all()
total_of_squares = sum(x * x for x in nums)
 
print(squares)
print(evens_squared)
print(lookup)
print(unique_mods)
print(total_of_squares)

Slicing and reversal

Slicing is one of Python’s biggest CP advantages — no manual index loops.

slicing.py
arr = [10, 20, 30, 40, 50]
 
print(arr[1:4])       # middle chunk
print(arr[::-1])      # reversed copy, O(n)
print(arr[:3])        # first 3
print(arr[-2:])       # last 2
print(arr[::2])       # every other element
 
s = "competitive"
print(s[::-1])        # reverse a string just as easily
print(s == s[::-1])   # palindrome check in one line
slicing.py
arr = [10, 20, 30, 40, 50]
 
print(arr[1:4])       # middle chunk
print(arr[::-1])      # reversed copy, O(n)
print(arr[:3])        # first 3
print(arr[-2:])       # last 2
print(arr[::2])       # every other element
 
s = "competitive"
print(s[::-1])        # reverse a string just as easily
print(s == s[::-1])   # palindrome check in one line

Tuple packing/unpacking, enumerate, zip, **

unpacking_tour.py
# tuple packing / unpacking
point = (3, 4)
x, y = point
print("x:", x, "y:", y)
 
# swap without a temp variable
a, b = 1, 2
a, b = b, a
print("swapped:", a, b)
 
# enumerate: index + value, no manual range(len(...))
for i, val in enumerate(["a", "b", "c"]):
    print(i, val)
 
# zip: walk two sequences together
names = ["Ann", "Bo", "Cy"]
scores = [90, 85, 77]
for name, score in zip(names, scores):
    print(name, score)
 
# * unpacking: grab "the rest" of a sequence
first, *middle, last = [1, 2, 3, 4, 5]
print("first:", first, "middle:", middle, "last:", last)
 
# * to spread arguments
def add3(a, b, c):
    return a + b + c
 
nums = [1, 2, 3]
print(add3(*nums))
unpacking_tour.py
# tuple packing / unpacking
point = (3, 4)
x, y = point
print("x:", x, "y:", y)
 
# swap without a temp variable
a, b = 1, 2
a, b = b, a
print("swapped:", a, b)
 
# enumerate: index + value, no manual range(len(...))
for i, val in enumerate(["a", "b", "c"]):
    print(i, val)
 
# zip: walk two sequences together
names = ["Ann", "Bo", "Cy"]
scores = [90, 85, 77]
for name, score in zip(names, scores):
    print(name, score)
 
# * unpacking: grab "the rest" of a sequence
first, *middle, last = [1, 2, 3, 4, 5]
print("first:", first, "middle:", middle, "last:", last)
 
# * to spread arguments
def add3(a, b, c):
    return a + b + c
 
nums = [1, 2, 3]
print(add3(*nums))

divmoddivmod: quotient and remainder together

divmod_tour.py
q, r = divmod(17, 5)
print("quotient:", q, "remainder:", r)
 
# classic use: base conversion
def to_base(n, base):
    digits = []
    while n:
        n, rem = divmod(n, base)
        digits.append(str(rem))
    return "".join(reversed(digits)) or "0"
 
print(to_base(26, 2))   # binary
print(to_base(255, 16)) # hex-ish (digits only, no a-f mapping here)
divmod_tour.py
q, r = divmod(17, 5)
print("quotient:", q, "remainder:", r)
 
# classic use: base conversion
def to_base(n, base):
    digits = []
    while n:
        n, rem = divmod(n, base)
        digits.append(str(rem))
    return "".join(reversed(digits)) or "0"
 
print(to_base(26, 2))   # binary
print(to_base(255, 16)) # hex-ish (digits only, no a-f mapping here)

Bit tricks

bit_tricks.py
x = 44   # 0b101100
 
lowest_set_bit = x & -x
print("x:            ", bin(x))
print("lowest set bit:", bin(lowest_set_bit))
 
print("bit_count():  ", x.bit_count())    # number of 1 bits (Python 3.10+)
print("bit_length(): ", x.bit_length())   # bits needed to represent x
 
# power of two check, using the lowest-set-bit trick
def is_power_of_two(n):
    return n > 0 and (n & (n - 1)) == 0
 
print("16 is power of two:", is_power_of_two(16))
print("18 is power of two:", is_power_of_two(18))
bit_tricks.py
x = 44   # 0b101100
 
lowest_set_bit = x & -x
print("x:            ", bin(x))
print("lowest set bit:", bin(lowest_set_bit))
 
print("bit_count():  ", x.bit_count())    # number of 1 bits (Python 3.10+)
print("bit_length(): ", x.bit_length())   # bits needed to represent x
 
# power of two check, using the lowest-set-bit trick
def is_power_of_two(n):
    return n > 0 and (n & (n - 1)) == 0
 
print("16 is power of two:", is_power_of_two(16))
print("18 is power of two:", is_power_of_two(18))

Fast string building

Strings are immutable, so s += chunks += chunk in a loop silently creates a new string every time — O(n)O(n) per concatenation, O(n2)O(n^2) total over a loop. Build a list of pieces and joinjoin once instead.

string_join.py
parts = []
for i in range(6):
    parts.append(str(i))
 
# one join at the end: O(n) total, not O(n^2)
result = "-".join(parts)
print(result)
string_join.py
parts = []
for i in range(6):
    parts.append(str(i))
 
# one join at the end: O(n) total, not O(n^2)
result = "-".join(parts)
print(result)

float("inf")float("inf"): the universal “worse than anything” sentinel

inf_sentinel.py
distances = {"a": 4, "b": 2, "c": 9}
 
best_node, best_dist = None, float("inf")
for node, dist in distances.items():
    if dist < best_dist:
        best_node, best_dist = node, dist
 
print("closest:", best_node, best_dist)
inf_sentinel.py
distances = {"a": 4, "b": 2, "c": 9}
 
best_node, best_dist = None, float("inf")
for node, dist in distances.items():
    if dist < best_dist:
        best_node, best_dist = node, dist
 
print("closest:", best_node, best_dist)

float("inf")float("inf") (or math.infmath.inf) compares as larger than every real number, so it’s the standard starting value for a running minimum — no need for a sentinel like -1-1 that could collide with a real distance.

The 2D-array aliasing bug

This is the single most common silent bug in competitive Python: building a grid with [[0] * cols] * rows[[0] * cols] * rows looks reasonable but creates rows aliases of the same inner list — mutate one row, and every row changes.

aliasing_bug.py
# THE BUG: all three rows are the SAME list object
grid_wrong = [[0] * 3] * 3
grid_wrong[0][0] = 9
print("wrong:", grid_wrong)   # every row shows the 9 — not what you wanted!
 
# THE FIX: build each row independently with a comprehension
grid_right = [[0] * 3 for _ in range(3)]
grid_right[0][0] = 9
print("right:", grid_right)  # only row 0 changed
aliasing_bug.py
# THE BUG: all three rows are the SAME list object
grid_wrong = [[0] * 3] * 3
grid_wrong[0][0] = 9
print("wrong:", grid_wrong)   # every row shows the 9 — not what you wanted!
 
# THE FIX: build each row independently with a comprehension
grid_right = [[0] * 3 for _ in range(3)]
grid_right[0][0] = 9
print("right:", grid_right)  # only row 0 changed

Practice

Drill 1 — generator expression sum. Sum only the even squares without building an intermediate list.

Drill 2 — lowest set bit. Compute the lowest set bit of xx using the x & -xx & -x trick.

Drill 3 — fix the aliasing bug. Build a 3x3 grid where mutating one row does not affect the others.

Recap

  • Comprehensions build a full collection; generator expressions feed sumsum/anyany/allall/maxmax lazily without materializing one.
  • Slicing ([::-1][::-1], [a:b][a:b]) replaces most manual index loops.
  • enumerateenumerate, zipzip, ** unpacking, and divmoddivmod cut boilerplate everywhere.
  • x & -xx & -x, bit_count()bit_count(), bit_length()bit_length() are the bit tricks worth memorizing.
  • Build strings with a list + "".join(...)"".join(...), never +=+= in a loop.
  • Never [[0] * cols] * rows[[0] * cols] * rows — always [[0] * cols for _ in range(rows)][[0] * cols for _ in range(rows)].

You now have the Python-specific toolkit. Next phase: Core Data Structures — arrays, linked lists, stacks, queues, trees, graphs, and heaps, built from scratch and used in practice.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did