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. divmoddivmodfor 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, andfloat("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.
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)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.
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 linearr = [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 lineTuple packing/unpacking, enumerate, zip, **
# 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))# 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
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)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
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))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 — per concatenation, total over a loop.
Build a list of pieces and joinjoin once instead.
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)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
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)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.
# 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# 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 changedPractice
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/maxmaxlazily without materializing one. - Slicing (
[::-1][::-1],[a:b][a:b]) replaces most manual index loops. enumerateenumerate,zipzip,**unpacking, anddivmoddivmodcut 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 coffeeWas this page helpful?
Let us know how we did
