>_ python-101
~/python-101 — zsh

➜ ~/python-101 python3 learn.py --level beginner

import python_101
>>> "Cheat Sheet"▋

You already have an idea for how to solve the problem. This course gives you the Python tools to write it down — fast input, handy built-ins, the standard-library helpers that save you writing code, plus reading and writing files and scraping websites.

27 sections copy-paste snippets visual diagrams python >= 3.10

How to read the coloured boxes: # analogy # tip # watch out

01

Prerequisites

You don't need to be a programmer yet. You need a few things on your computer and a couple of ideas in your head.

🧰 Tools

  • Python 3.10 or newer from python.org
  • A code editor — VS Code with the Python extension is a good free choice
  • A terminal (Terminal on macOS/Linux, PowerShell on Windows)
  • For the scraping section: pip install requests beautifulsoup4

🧠 Ideas

  • A program is a list of instructions run top to bottom.
  • A variable is a name for a value.
  • A file lives in a folder, and has a path like docs/notes.txt.
  • Basic maths: +, −, ×, ÷ and remainders.

Check your setup

python3 --version      # Windows: py --version
python3                # opens the interactive "REPL"; type exit() to leave
python3 solution.py    # runs a file
python3 solution.py < input.txt   # feeds input.txt as if you typed it
# tip: The < input.txt trick is how you test contest solutions: paste the sample input into a file and run against it again and again.
02

Your First Program

Create a file called hello.py, paste this in, and run python3 hello.py.

# Lines starting with # are comments: Python ignores them.
name = "Ada"
print("Hello,", name)        # Hello, Ada
print(f"{name} has {len(name)} letters")   # Ada has 3 letters

Indentation is grammar

Other languages use { } to group lines. Python uses indentation (4 spaces). A colon : means “an indented block comes next”.

for i in range(3):
    print("inside the loop", i)   # indented: repeats
print("after the loop")           # not indented: runs once
# analogy: Indentation is like bullet points in a to-do list. Sub-bullets belong to the bullet above them.
03

Variables, Types & Operators

A variable is a name tag stuck onto a value. The value has a type: whole number, decimal number, text, true/false, or “nothing”.

DIAGRAM · Names point at values

a──▶ [1, 2, 3]◀── b

b = a does not copy the list. Both tags now point at the same list, so changing it through b also changes what a sees.

age = 21              # int   (whole number, any size)
price = 9.99          # float (decimal number)
name = "Ada"          # str   (text)
is_ready = True       # bool  (True / False)
nothing = None        # NoneType ("no value yet")

print(type(age), type(price), type(name))

# Convert between types
int("42"), float("3.5"), str(10), bool(0), int(3.9)   # 42 3.5 '10' False 3

# Several assignments at once, and the famous swap
x, y = 1, 2
x, y = y, x           # x=2, y=1, no temp variable needed
a = b = 0             # both start at 0

Arithmetic

OperatorMeaningExampleResult
+ - *add, subtract, multiply7 * 321
/divide (always gives a float)7 / 23.5
//floor divide (round down)7 // 23
%remainder (modulo)7 % 21
**power2 ** 101024
divmodquotient and remainder togetherdivmod(7, 2)(3, 1)
count = 0
count += 1            # same as count = count + 1  (also -=, *=, //=, %=)

# Comparisons give True/False, and you can chain them
print(1 < 5 <= 5)      # True  (means 1 < 5 and 5 <= 5)
print(3 == 3.0, 3 != 4)  # True True

# Logic words
print(True and False, True or False, not True)   # False True False
# watch out: // and % round towards minus infinity: -7 // 2 is -4 and -7 % 2 is 1. C++/Java give −3 and −1. Use int(-7 / 2) if you want truncation towards zero.
04

Input & Output (Fast I/O)

Every coding test starts by reading numbers. input() reads one line as text; you then cut it up and convert it.

# analogy: input() hands you a whole sentence. .split() cuts it into words. map(int, …) turns each word into a number.
n = int(input())                        # "5"         -> 5
a, b = map(int, input().split())        # "3 7"       -> a=3, b=7
nums = list(map(int, input().split()))  # "1 2 3 4"   -> [1, 2, 3, 4]
word = input().strip()                  # strip removes spaces/newline at the ends
grid = [input() for _ in range(n)]      # n lines of text
matrix = [list(map(int, input().split())) for _ in range(n)]  # n rows of numbers

Faster input for big tests

input() is slow when there are 10⁵+ lines. Swap it for sys.stdin.readline, or read everything in one go.

import sys
input = sys.stdin.readline          # drop-in replacement (keeps the trailing "\n"!)

n = int(input())
s = input().strip()                 # .strip() matters for strings now

# Or: read ALL tokens at once and walk through them
data = sys.stdin.buffer.read().split()
n = int(data[0])
nums = list(map(int, data[1:1 + n]))

Printing

nums = [1, 2, 3]
print(1, 2, 3)                   # 1 2 3
print(1, 2, 3, sep=",")          # 1,2,3
print("no newline", end="")      # stays on the same line
print()
print(*nums)                     # 1 2 3   (* unpacks the list)
print(" ".join(map(str, nums)))  # 1 2 3   (join needs strings)
print("\n".join(map(str, nums))) # one per line

# Fast output: collect lines, print once
out = []
for i in range(3):
    out.append(str(i * i))
print("\n".join(out))

# f-strings: the easiest formatting
pi = 3.14159265
print(f"{pi:.2f}")       # 3.14     (2 decimal places)
print(f"{42:5d}|")       #    42|   (width 5)
print(f"{42:05d}")       # 00042    (zero padded)
print(f"{1234567:,}")    # 1,234,567
print(f"{'hi':>6}|{'hi':<6}|{'hi':^6}|")   # right / left / centre
print(f"{255:b} {255:x} {255:o}")             # 11111111 ff 377
05

Strings

A string is a row of characters. Strings are immutable: you can't change a letter in place, you build a new string instead.

DIAGRAM · Indexing s = "PYTHON"

P
0
-6
Y
1
-5
T
2
-4
H
3
-3
O
4
-2
N
5
-1

Green numbers count from the front (starting at 0). Pink numbers count from the back. s[1:4] means “from index 1 up to, but not including, 4” → "YTH".

s = "PYTHON"
s[0], s[-1]        # 'P', 'N'
s[1:4]             # 'YTH'     start:stop  (stop is excluded)
s[:2], s[2:]       # 'PY', 'THON'
s[::2]             # 'PTO'     every 2nd char
s[::-1]            # 'NOHTYP'  reversed!
len(s)             # 6
"TH" in s          # True

t = "  Hello, World  "
t.strip()                    # 'Hello, World'
t.lower(), t.upper()         # lower / UPPER case
"a,b,c".split(",")           # ['a', 'b', 'c']
"-".join(["a", "b", "c"])    # 'a-b-c'
"banana".count("a")          # 3
"banana".find("n")           # 2   (-1 if missing)
"banana".index("n")          # 2   (error if missing)
"banana".replace("a", "o")   # 'bonono'
"hello".startswith("he")     # True
"abc123".isalnum(), "123".isdigit(), "abc".isalpha()   # True True True
"hello world".title()        # 'Hello World'

# Characters <-> numbers (ASCII / Unicode code points)
ord("a"), chr(97)            # 97, 'a'
ord("c") - ord("a")          # 2   -> handy for "letter index" problems

# Changing a string = rebuild it via a list
chars = list("hello")
chars[0] = "j"
print("".join(chars))        # jello
import string
string.ascii_lowercase   # 'abcdefghijklmnopqrstuvwxyz'
string.ascii_uppercase   # 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
string.digits            # '0123456789'

# Repeat and multi-line
print("ab" * 3)          # ababab
poem = """line one
line two"""
# watch out: building a big string with s += piece inside a loop can be slow. Append pieces to a list and "".join(list) at the end.
06

Lists

A list is an ordered, changeable row of items — Python's “array”. Indexing and slicing work exactly like strings.

# analogy: A list is a train. Each carriage has a number (its index), and you can add carriages at the end, uncouple some, or reorder them.
nums = [5, 3, 8]
nums.append(1)        # [5, 3, 8, 1]         add to end      O(1)
nums.extend([7, 7])   # [5, 3, 8, 1, 7, 7]   add many
nums.insert(0, 9)     # [9, 5, 3, 8, 1, 7, 7] insert at index   O(n)
nums.pop()            # removes & returns last item   O(1)
nums.pop(0)           # removes first item             O(n)
nums.remove(8)        # removes first 8 found          O(n)
nums.index(3)         # position of first 3
nums.count(7)         # how many 7s
nums.sort()           # sorts IN PLACE, returns None
nums.reverse()        # reverses IN PLACE
print(nums)

new = sorted(nums)    # returns a NEW sorted list
copy = nums[:]        # shallow copy (also nums.copy() or list(nums))
zeros = [0] * 5       # [0, 0, 0, 0, 0]
last_two = nums[-2:]
del nums[0]           # delete by index
nums[1:3] = [100]     # replace a slice

List comprehensions — loops in one line

squares = [x * x for x in range(6)]            # [0, 1, 4, 9, 16, 25]
evens   = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
labels  = ["even" if x % 2 == 0 else "odd" for x in range(4)]
pairs   = [(i, j) for i in range(2) for j in range(2)]   # nested loops
flat    = [v for row in [[1, 2], [3, 4]] for v in row]    # [1, 2, 3, 4]

2D grids (and the #1 beginner trap)

DIAGRAM · [[0]*3]*3 vs a comprehension

❌ [[0]*3]*3

row 0
row 1
row 2
─┐
─┼▶
─┘
[0, 0, 0]

Three tags, one list. Change one row and every row changes.

✅ [[0]*3 for _ in range(3)]

row 0
row 1
row 2
──▶
──▶
──▶
[0, 0, 0]
[0, 0, 0]
[0, 0, 0]

Three separate lists. Rows are independent.

bad = [[0] * 3] * 3
bad[0][0] = 1
print(bad)        # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]  -- surprise!

rows, cols = 3, 4
grid = [[0] * cols for _ in range(rows)]
grid[0][0] = 1
print(grid)       # only the first row changed

# Walk the 4 neighbours of a cell (very common in grid problems)
r, c = 1, 1
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
    nr, nc = r + dr, c + dc
    if 0 <= nr < rows and 0 <= nc < cols:
        print("neighbour", nr, nc)

transposed = [list(col) for col in zip(*grid)]   # swap rows and columns
07

Tuples & Unpacking

A tuple is a list you can't change. Because it never changes, it can be used as a dictionary key or stored in a set — perfect for coordinates like (row, col).

point = (3, 4)
x, y = point                 # unpacking
single = (5,)                # one-item tuple needs a comma

first, *rest = [1, 2, 3, 4]  # first=1, rest=[2, 3, 4]
*init, last = [1, 2, 3, 4]   # init=[1, 2, 3], last=4
a, _, c = (1, 2, 3)          # _ means "I don't care about this one"

# Tuples compare item by item, left to right
print((1, 5) < (2, 0))       # True
print((1, 5) < (1, 6))       # True

visited = {(0, 0), (0, 1)}   # set of coordinates
print((0, 1) in visited)     # True
08

Dictionaries

A dictionary maps keys to values. Looking up a key is instant (O(1)) no matter how big the dict is.

# analogy: A real dictionary: you look up the word (key) and read its meaning (value). You never read the whole book to find one word.
ages = {"ada": 36, "alan": 41}
ages["grace"] = 85           # add / update
print(ages["ada"])           # 36  (KeyError if missing!)
print(ages.get("bob"))       # None (safe)
print(ages.get("bob", 0))    # 0    (safe with default)
print("ada" in ages)         # True (checks keys)
del ages["alan"]
ages.pop("grace", None)      # remove if present

for name, age in ages.items():
    print(name, age)
list(ages.keys()), list(ages.values())

# Counting by hand
freq = {}
for ch in "banana":
    freq[ch] = freq.get(ch, 0) + 1
print(freq)                  # {'b': 1, 'a': 3, 'n': 2}

# Dict comprehension
sq = {x: x * x for x in range(4)}      # {0: 0, 1: 1, 2: 4, 3: 9}
inverted = {v: k for k, v in sq.items()}

# Sort a dict by value (highest first)
top = sorted(freq.items(), key=lambda kv: kv[1], reverse=True)
print(top)                   # [('a', 3), ('n', 2), ('b', 1)]

# Merge two dicts
merged = {"a": 1} | {"b": 2}           # Python 3.9+
# tip: Since Python 3.7, dicts remember insertion order. Keys must be immutable: numbers, strings, tuples — not lists.
09

Sets

A set is a bag of unique items with instant “is it in here?” checks. Use it to remove duplicates or track what you've already seen.

DIAGRAM · Set operations on A = {1,2,3} and B = {2,3,4}

A | B
union
{1,2,3,4}
A & B
intersection
{2,3}
A - B
difference
{1}
A ^ B
in exactly one
{1,4}
seen = set()                 # NOT {} -- that's an empty dict
seen.add(3)
seen.update([4, 5])
seen.discard(99)             # no error if missing (remove() would error)
print(4 in seen)             # True, in O(1)

unique = set([3, 1, 3, 2, 1])   # {1, 2, 3}
print(len(unique))              # count distinct values

A, B = {1, 2, 3}, {2, 3, 4}
print(A | B, A & B, A - B, A ^ B)
print({1, 2} <= A)              # subset check: True

frozen = frozenset([1, 2])      # immutable set, can be a dict key
# watch out: x in my_list scans the whole list (slow, O(n)). x in my_set is instant. If you check membership inside a loop, convert to a set first.
10

Control Flow & Loops

x = 7
if x > 10:
    print("big")
elif x > 5:
    print("medium")
else:
    print("small")

label = "even" if x % 2 == 0 else "odd"   # one-line if (ternary)

for i in range(5):          print(i, end=" ")   # 0 1 2 3 4
print()
for i in range(2, 10, 3):   print(i, end=" ")   # 2 5 8
print()
for i in range(5, 0, -1):   print(i, end=" ")   # 5 4 3 2 1
print()

n = 3
while n > 0:
    n -= 1
    if n == 1:
        continue            # skip to next round
    print("n is", n)

for v in [1, 3, 5]:
    if v % 2 == 0:
        print("found even")
        break               # leave the loop early
else:
    print("no even number") # runs only if the loop did NOT break

# Walrus operator := assigns inside an expression (3.8+)
data = [1, 2, 3, 4]
if (n := len(data)) > 3:
    print(f"long list of {n}")

# match/case (3.10+): a tidy multi-way switch
cmd = "add"
match cmd:
    case "add":
        print("adding")
    case "remove" | "delete":
        print("removing")
    case _:
        print("unknown")
# tip: range(a, b) stops before b. To loop 1..n inclusive, write range(1, n + 1).
11

Functions, Lambdas & Recursion

A function is a reusable recipe: it takes ingredients (arguments) and gives back a result (return).

def area(width, height=1):          # height has a default value
    """Return the area of a rectangle."""   # docstring
    return width * height

print(area(3, 4), area(5), area(height=2, width=3))   # 12 5 6

def min_max(nums):
    return min(nums), max(nums)     # return several values as a tuple
lo, hi = min_max([4, 1, 9])

def total(*args, **kwargs):         # any number of positional / named args
    return sum(args) + sum(kwargs.values())
print(total(1, 2, 3, bonus=10))     # 16

square = lambda x: x * x            # tiny anonymous function
print(square(5))                    # 25

# Functions inside functions, and changing an outer variable
def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc
c = counter(); c(); print(c())      # 2

# Type hints are optional labels -- Python doesn't enforce them
def greet(name: str) -> str:
    return "Hi " + name

Recursion

A function that calls itself on a smaller version of the problem. It needs a base case to stop.

import sys
sys.setrecursionlimit(10**6)   # default is ~1000: deep recursion would crash

def factorial(n):
    if n <= 1:                 # base case
        return 1
    return n * factorial(n - 1)

print(factorial(5))            # 120
# watch out: never use a mutable default like def f(items=[]). The same list is reused on every call. Use items=None and create the list inside. Very deep recursion (10⁵+ levels) can still crash Python itself; convert it to a loop with your own stack if that happens.
12

Built-in Power Pack

These need no import. Knowing them saves you writing loops by hand.

nums = [4, 1, 7, 3]
names = ["ann", "bob", "cy"]

for i, v in enumerate(nums):           # index AND value
    print(i, v)
for i, v in enumerate(nums, start=1):  # count from 1
    pass

for name, n in zip(names, nums):       # walk two lists side by side
    print(name, n)                     # stops at the shorter one

sum(nums), min(nums), max(nums), len(nums)        # 15 1 7 4
max(names, key=len)                    # 'ann'  (longest; first on ties)
min(nums, default=0)                   # safe on empty lists
any(x > 5 for x in nums)               # True  (at least one)
all(x > 0 for x in nums)               # True  (every one)
list(map(str, nums))                   # ['4', '1', '7', '3']
list(filter(lambda x: x % 2, nums))    # [1, 7, 3]  (keep odd)
list(reversed(nums))                   # [3, 7, 1, 4]
abs(-5), round(2.675, 2), round(7.5)   # 5 2.67 8
pow(2, 10), pow(2, 10, 1000)           # 1024 24  (3rd arg = fast modulo)
divmod(17, 5)                          # (3, 2)
sorted("hello")                        # ['e', 'h', 'l', 'l', 'o']
isinstance(5, int)                     # True
int("ff", 16), int("101", 2)           # 255 5   (parse other bases)
bin(5), hex(255), oct(8)               # '0b101' '0xff' '0o10'
float("inf"), -float("inf")            # infinities, great as starting min/max
# watch out: round() uses “banker's rounding” — round(2.5) is 2, round(3.5) is 4. For classic rounding of positives use int(x + 0.5).
13

Sorting Like a Pro

The key= argument tells Python what to look at when comparing items. Python's sort is stable: equal items keep their original order.

people = [("ann", 30), ("bob", 25), ("cy", 30)]

sorted(people, key=lambda p: p[1])                 # by age, ascending
sorted(people, key=lambda p: p[1], reverse=True)   # by age, descending
sorted(people, key=lambda p: (-p[1], p[0]))        # age desc, then name asc
sorted(["bb", "a", "ccc"], key=len)                # by length
sorted(["b", "A", "c"], key=str.lower)             # case-insensitive

from operator import itemgetter
sorted(people, key=itemgetter(1, 0))               # same as lambda p: (p[1], p[0])

# Custom compare function (when a key is hard to write)
from functools import cmp_to_key
def compare(a, b):
    # return negative if a goes first, positive if b goes first, 0 if tie
    return -1 if a + b > b + a else 1
nums = ["3", "30", "34", "5", "9"]
print("".join(sorted(nums, key=cmp_to_key(compare))))   # 9534330 (largest number)

# Indices that would sort a list
vals = [30, 10, 20]
order = sorted(range(len(vals)), key=lambda i: vals[i])  # [1, 2, 0]
# tip: Negating a number in the key (-p[1]) is the easy way to mix ascending and descending in one sort.
14

collections

Supercharged containers from the standard library. These four come up constantly.

Counter — count things instantly

from collections import Counter
c = Counter("mississippi")
print(c)                     # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
print(c["s"], c["z"])        # 4 0   (missing keys are 0, no error)
print(c.most_common(2))      # [('i', 4), ('s', 4)]
print(Counter("listen") == Counter("silent"))   # True -> anagram check
c.update("ss")               # add more counts
print(Counter([1, 1, 2]) - Counter([1]))        # Counter({1: 1, 2: 1})

defaultdict — dict with an automatic starting value

from collections import defaultdict
graph = defaultdict(list)    # missing key -> starts as []
for a, b in [(1, 2), (1, 3), (2, 3)]:
    graph[a].append(b)
    graph[b].append(a)
print(dict(graph))           # {1: [2, 3], 2: [1, 3], 3: [1, 2]}

counts = defaultdict(int)    # missing key -> starts as 0
for w in "a b a".split():
    counts[w] += 1

groups = defaultdict(set)    # missing key -> starts as set()

deque — a list that's fast at both ends

DIAGRAM · Double-ended queue

appendleft
popleft
⇄
1
2
3
4
append
pop
⇄

All four operations are O(1). A plain list's pop(0) is O(n) because every item has to shuffle left. Use a deque for queues (e.g. BFS).

from collections import deque
q = deque([1, 2, 3])
q.append(4); q.appendleft(0)   # deque([0, 1, 2, 3, 4])
q.popleft(); q.pop()           # removes 0 and 4
q.rotate(1)                    # deque([3, 1, 2])
print(q[0], q[-1])             # peek both ends

window = deque(maxlen=3)       # keeps only the last 3 items
for x in range(6):
    window.append(x)
print(list(window))            # [3, 4, 5]

# BFS skeleton
graph = {0: [1, 2], 1: [3], 2: [3], 3: []}
dist = {0: 0}
q = deque([0])
while q:
    node = q.popleft()
    for nxt in graph[node]:
        if nxt not in dist:
            dist[nxt] = dist[node] + 1
            q.append(nxt)
print(dist)                    # {0: 0, 1: 1, 2: 1, 3: 2}

namedtuple — tuples with field names

from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y, p)             # 3 4 Point(x=3, y=4)
15

heapq: Priority Queues

A heap always gives you the smallest item fast, even while you keep adding new ones. It's stored in a normal list.

# analogy: A hospital waiting room: whoever is most urgent (smallest number) is seen next, no matter when they arrived.

DIAGRAM · The list [1, 3, 2, 7, 4] as a min-heap tree

1
3
2
7
4

Every parent is ≤ its children, so the smallest is always at index 0. Children of index i live at 2i+1 and 2i+2. Push and pop are O(log n).

import heapq
h = []
heapq.heappush(h, 5)
heapq.heappush(h, 1)
heapq.heappush(h, 3)
print(h[0])                  # 1   peek smallest, O(1)
print(heapq.heappop(h))      # 1   remove smallest, O(log n)

nums = [5, 7, 1, 3]
heapq.heapify(nums)          # turn a list into a heap in O(n)

# Max-heap trick: store negatives
mx = []
for x in [5, 1, 9]:
    heapq.heappush(mx, -x)
print(-heapq.heappop(mx))    # 9

# Tuples: sorted by first item, then second...
tasks = []
heapq.heappush(tasks, (2, "write"))
heapq.heappush(tasks, (1, "read"))
print(heapq.heappop(tasks))  # (1, 'read')

print(heapq.nlargest(2, [4, 1, 9, 7]))    # [9, 7]
print(heapq.nsmallest(2, [4, 1, 9, 7]))   # [1, 4]
16

bisect: Binary Search

On a sorted list, bisect finds where a value would go in O(log n) — like opening a phone book in the middle instead of reading every page.

DIAGRAM · Where does 3 go in [1, 3, 3, 5]?

1
0
▼left=1
3
1
3
2
▼right=3
5
3

bisect_left = before existing 3s; bisect_right = after them. Their difference is how many 3s there are.

from bisect import bisect_left, bisect_right, insort
a = [1, 3, 3, 5]
print(bisect_left(a, 3), bisect_right(a, 3))   # 1 3
print(bisect_right(a, 3) - bisect_left(a, 3))  # 2  (count of 3s)
print(bisect_left(a, 4))       # 3  -> first index with value >= 4
print(bisect_right(a, 4) - 1)  # 2  -> last index with value <= 4

i = bisect_left(a, 5)
found = i < len(a) and a[i] == 5   # "is 5 in the list?" in O(log n)

insort(a, 4)                   # insert keeping it sorted
print(a)                       # [1, 3, 3, 4, 5]

# Binary search on the answer with a key (Python 3.10+)
words = ["a", "bb", "ccc", "dddd"]
print(bisect_left(words, 3, key=len))   # 2 -> first word with len >= 3
17

itertools: Combinatorics

Brute-force helpers. Instead of writing nested loops to try every arrangement, ask itertools.

from itertools import (permutations, combinations, combinations_with_replacement,
                       product, accumulate, groupby, chain, pairwise, count, islice)

list(permutations([1, 2, 3]))      # all 6 orderings
list(permutations("abc", 2))       # ordered pairs: ('a','b'), ('a','c'), ...
list(combinations([1, 2, 3], 2))   # [(1, 2), (1, 3), (2, 3)]  order doesn't matter
list(combinations_with_replacement([1, 2], 2))   # [(1, 1), (1, 2), (2, 2)]
list(product([0, 1], repeat=3))    # all 8 bit patterns of length 3
list(product("ab", [1, 2]))        # [('a', 1), ('a', 2), ('b', 1), ('b', 2)]

list(accumulate([1, 2, 3, 4]))             # [1, 3, 6, 10]  prefix sums
list(accumulate([1, 2, 3, 4], initial=0))  # [0, 1, 3, 6, 10]
list(accumulate([3, 1, 4], max))           # [3, 3, 4]  running max

# groupby groups CONSECUTIVE equal items -> run-length encoding
print([(k, len(list(g))) for k, g in groupby("aaabbc")])   # [('a', 3), ('b', 2), ('c', 1)]

list(chain([1, 2], [3], [4, 5]))   # [1, 2, 3, 4, 5]  glue iterables
list(pairwise([1, 2, 3, 4]))       # [(1, 2), (2, 3), (3, 4)]  (3.10+)
list(islice(count(10, 5), 3))      # [10, 15, 20]  first 3 of an endless counter

Prefix sums — range totals in O(1)

from itertools import accumulate
a = [3, 1, 4, 1, 5]
pre = list(accumulate(a, initial=0))   # [0, 3, 4, 8, 9, 14]
l, r = 1, 3                            # sum of a[1..3] inclusive
print(pre[r + 1] - pre[l])             # 6  (1 + 4 + 1)
# watch out: these grow fast. 10 items have 3,628,800 permutations. Fine for n ≤ 8–10, too slow beyond that.
18

math & functools

import math
math.gcd(12, 18), math.lcm(4, 6)   # 6 12
math.isqrt(17)                     # 4   exact integer square root
math.sqrt(16)                      # 4.0 (float)
math.ceil(2.1), math.floor(2.9)    # 3 2
math.factorial(5)                  # 120
math.comb(5, 2), math.perm(5, 2)   # 10 20   "5 choose 2", ordered picks
math.log2(8), math.log10(1000), math.log(math.e)   # 3.0 3.0 1.0
math.pi, math.inf
math.hypot(3, 4)                   # 5.0  distance
math.prod([2, 3, 4])               # 24

# Ceiling division without floats (safe for huge ints)
a, b = 7, 2
print(-(-a // b))                  # 4
print((a + b - 1) // b)            # 4

MOD = 10**9 + 7                    # the classic contest modulus
print(pow(3, 10**18, MOD))         # fast modular exponentiation
print(pow(3, -1, MOD))             # modular inverse (3.8+)

functools — memoisation in one line

# analogy: @cache is a sticky note on the function: “I've solved this exact input before, here's the answer”. It turns slow recursive solutions into fast ones (dynamic programming).
import math
from functools import cache, lru_cache, reduce

@cache                             # Python 3.9+; same as @lru_cache(maxsize=None)
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(90))                     # instant: 2880067194370816120

@lru_cache(maxsize=None)
def ways(n):                       # ways to climb n stairs taking 1 or 2 steps
    if n <= 1:
        return 1
    return ways(n - 1) + ways(n - 2)
print(ways(30))
ways.cache_clear()                 # reset between test cases if needed

print(reduce(lambda acc, x: acc * x, [1, 2, 3, 4]))   # 24  fold a list into one value
print(reduce(math.gcd, [12, 18, 24]))                 # 6   gcd of many numbers
# watch out: cached function arguments must be hashable — pass tuples, not lists.
19

Bit Manipulation

Numbers are stored as 0s and 1s. Bit tricks let you treat one integer as a row of on/off switches.

DIAGRAM · 13 in binary

1
8
1
4
0
2
1
1

8 + 4 + 1 = 13 → 0b1101. Bit i is worth 2ⁱ, counting from the right starting at 0.

a, b = 0b1100, 0b1010     # 12, 10
a & b, a | b, a ^ b       # 8 14 6     AND, OR, XOR
~a                        # -13        flip all bits
1 << 4, 40 >> 2           # 16 10      multiply / divide by powers of 2

x, i = 13, 2
(x >> i) & 1              # 1   is bit i on?
x | (1 << i)              # turn bit i on
x & ~(1 << i)             # turn bit i off
x ^ (1 << i)              # toggle bit i
x & -x                    # lowest set bit (1)
x & (x - 1) == 0          # power of two? (for x > 0)
bin(x).count("1")         # 3  number of 1 bits
x.bit_count()             # 3  same, faster (3.10+)
x.bit_length()            # 4  bits needed

# Every subset of n items, as bitmasks
items = ["a", "b", "c"]
n = len(items)
for mask in range(1 << n):
    subset = [items[j] for j in range(n) if mask >> j & 1]
    print(mask, subset)

# XOR trick: find the one number without a pair
from functools import reduce
from operator import xor
print(reduce(xor, [4, 1, 2, 1, 2]))   # 4
20

Big Numbers & Precision

Python ints never overflow — they grow as big as you need. Floats, however, are approximations.

print(2 ** 200)                 # exact, no overflow
print(0.1 + 0.2)                # 0.30000000000000004  (!)
print(0.1 + 0.2 == 0.3)         # False

import math
print(math.isclose(0.1 + 0.2, 0.3))     # True  compare floats safely
print(abs((0.1 + 0.2) - 0.3) < 1e-9)    # True  manual epsilon

from decimal import Decimal, getcontext
getcontext().prec = 50
print(Decimal("0.1") + Decimal("0.2"))  # 0.3  exact decimal maths
print(Decimal(2).sqrt())                # 50 significant digits

from fractions import Fraction
print(Fraction(1, 3) + Fraction(1, 6))  # 1/2  exact fractions

print(10**18 // 7, 10**18 % 7)          # stay in integers when you can
print(1_000_000 + 1)                    # underscores make big literals readable

import sys
sys.set_int_max_str_digits(0)           # lift the 4300-digit str(int) limit (3.11+)
# tip: When a problem says “print the answer modulo 10⁹+7”, take % MOD after every multiplication. Python won't overflow, but huge numbers get slow.
21

Classes & Dataclasses

A class is a blueprint for making your own type of object that bundles data and behaviour. In tests you'll mostly use them for nodes (linked lists, trees) and small helper structures.

class Node:
    def __init__(self, val, next=None):   # runs when you create one
        self.val = val
        self.next = next

    def __repr__(self):                    # how it prints
        return f"Node({self.val})"

head = Node(1, Node(2, Node(3)))
cur = head
while cur:
    print(cur.val, end=" ")                # 1 2 3
    cur = cur.next
print()

from dataclasses import dataclass, field

@dataclass(order=True)                     # free __init__, __repr__, ==, and <
class Job:
    priority: int
    name: str = field(compare=False)

jobs = sorted([Job(3, "c"), Job(1, "a")])
print(jobs)                                # [Job(priority=1, name='a'), Job(priority=3, name='c')]

# Union-Find (Disjoint Set) -- a classic helper class
class DSU:
    def __init__(self, n):
        self.parent = list(range(n))
    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]   # path halving
            x = self.parent[x]
        return x
    def union(self, a, b):
        ra, rb = self.find(a), self.find(b)
        if ra == rb:
            return False
        self.parent[ra] = rb
        return True

d = DSU(5); d.union(0, 1); d.union(1, 2)
print(d.find(0) == d.find(2))              # True
22

Errors & Debugging

Errors aren't failures; they're Python telling you exactly which line confused it. Read the last line of the message first.

ErrorUsually means
IndexErrorYou asked for a[5] but the list is shorter. Check off-by-one loops.
KeyErrorThat key isn't in the dict. Use .get() or defaultdict.
ValueErrorRight type, wrong value: int("abc"), or unpacking the wrong number of items.
TypeErrorMixing types: "a" + 1, or calling something that isn't a function.
NameErrorTypo in a variable name, or it wasn't defined yet.
RecursionErrorNo base case, or recursion too deep. Raise the limit or use a loop.
ZeroDivisionErrorDividing by zero. Check denominators.
IndentationErrorMixed tabs/spaces or a missing indent after :.
try:
    value = int("abc")
except ValueError as e:
    print("Not a number:", e)
except (KeyError, IndexError):
    print("lookup failed")
else:
    print("ran only if no error")
finally:
    print("always runs (cleanup)")

def withdraw(balance, amount):
    if amount > balance:
        raise ValueError("insufficient funds")   # throw your own error
    return balance - amount

assert withdraw(10, 3) == 7, "sanity check failed"   # crash loudly if false

# Quick debugging: = in an f-string prints the name and value
x, y = 3, 4
print(f"{x=} {y=} {x * y=}")    # x=3 y=4 x * y=12

import sys
print("debug info", file=sys.stderr)   # judges ignore stderr: safe for debug prints
23

Reading & Writing Files

Your program can save data to disk and load it back later. You open a file in a mode, use it, and close it.

# analogy: Opening a file is like borrowing a library book. with is the librarian who makes sure it's returned (closed) even if you trip over something halfway.

DIAGRAM · The life of a file

open(path, mode)→ read() / write()→ close() (with does the close for you)
ModeMeaningIf the file exists…If it doesn't…
"r"read (default)reads iterror
"w"writeerases it firstcreates it
"a"appendadds to the endcreates it
"x"exclusive createerrorcreates it
"r+"read and writeopens iterror
+"b"binary (images, PDFs): "rb", "wb"works with bytes instead of text

Text files

# Write (creates or overwrites notes.txt)
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("first line\n")
    f.write("second line\n")
    f.writelines(["third\n", "fourth\n"])
    print("fifth", file=f)            # print can write to files too

# Append
with open("notes.txt", "a", encoding="utf-8") as f:
    f.write("appended line\n")

# Read everything
with open("notes.txt", encoding="utf-8") as f:
    text = f.read()                   # one big string

# Read line by line (memory friendly for huge files)
with open("notes.txt", encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))

# Other ways to read
with open("notes.txt", encoding="utf-8") as f:
    first = f.readline()              # just one line
    rest = f.readlines()              # list of the remaining lines

lines = open("notes.txt", encoding="utf-8").read().splitlines()   # quick, no "\n"s
print(lines)

CSV files (spreadsheets)

import csv

rows = [["name", "score"], ["ada", 95], ["alan", 88]]
with open("scores.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)

with open("scores.csv", newline="", encoding="utf-8") as f:
    for row in csv.reader(f):
        print(row)                    # ['name', 'score'], ['ada', '95'], ...

with open("scores.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):     # each row becomes a dict by header
        print(row["name"], int(row["score"]))

with open("people.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=["name", "city"])
    w.writeheader()
    w.writerow({"name": "grace", "city": "NYC"})

JSON files (structured data)

import json

data = {"name": "ada", "skills": ["math", "code"], "active": True}
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2)      # Python -> file

with open("data.json", encoding="utf-8") as f:
    loaded = json.load(f)             # file -> Python
print(loaded["skills"][0])            # math

text = json.dumps(data)               # Python -> string
back = json.loads(text)               # string -> Python

Paths, folders and housekeeping

from pathlib import Path
import os, shutil

p = Path("output") / "report.txt"     # builds paths that work on every OS
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello\n", encoding="utf-8")   # one-liner write
print(p.read_text(encoding="utf-8"))        # one-liner read
print(p.exists(), p.name, p.stem, p.suffix) # True report.txt report .txt

for file in Path(".").glob("*.csv"):  # every .csv in the current folder
    print(file, file.stat().st_size, "bytes")

print(os.getcwd())                    # current working directory
print(os.listdir("output"))           # ['report.txt']

# Binary copy (works for images, PDFs...)
with open("output/report.txt", "rb") as src, open("output/copy.bin", "wb") as dst:
    dst.write(src.read())

# Clean up the demo files
for name in ["notes.txt", "scores.csv", "people.csv", "data.json"]:
    os.remove(name)
shutil.rmtree("output")               # delete a folder and everything in it
# watch out: "w" wipes the file the instant it opens. Always pass encoding="utf-8" so text with accents or emoji behaves the same on Windows, macOS and Linux.
24

Web Scraping

Scraping means downloading a web page with code and pulling out the parts you care about — prices, headlines, tables — instead of copying them by hand.

# analogy: requests is the delivery driver who fetches the page. BeautifulSoup is the highlighter you use to find the sentences you want in it.

DIAGRAM · The scraping pipeline

1. Request
requests.get(url)
2. HTML
resp.text
3. Parse
BeautifulSoup(html)
4. Save
CSV / JSON file
pip install requests beautifulsoup4

Step 1 — understand HTML

Web pages are made of nested tags. Tags have names (h2, a), and attributes like class and href. Right-click any page → Inspect to see them.

<div class="product">
  <h2 class="title">Blue Mug</h2>
  <span class="price">$12.50</span>
  <a href="/mug/blue">details</a>
</div>

Step 2 — parse it (works offline)

from bs4 import BeautifulSoup

html = """
<div class="product"><h2 class="title">Blue Mug</h2><span class="price">$12.50</span><a href="/mug/blue">details</a></div>
<div class="product"><h2 class="title">Red Cup</h2><span class="price">$8.00</span><a href="/cup/red">details</a></div>
"""
soup = BeautifulSoup(html, "html.parser")

print(soup.find("h2").text)                  # first match: Blue Mug
print(len(soup.find_all("div", class_="product")))   # 2

for card in soup.select("div.product"):      # CSS selectors
    title = card.select_one(".title").get_text(strip=True)
    price = float(card.select_one(".price").text.strip("$"))
    link = card.find("a")["href"]            # read an attribute
    print(title, price, link)

Step 3 — fetch a real page and save the results

quotes.toscrape.com is a site built specifically for scraping practice.

import csv
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

BASE = "https://quotes.toscrape.com/"
headers = {"User-Agent": "python-101-tutorial (learning project)"}

rows = []
url = BASE
while url:
    resp = requests.get(url, headers=headers, timeout=10)
    resp.raise_for_status()                 # stop on 404 / 500 errors
    soup = BeautifulSoup(resp.text, "html.parser")

    for q in soup.select("div.quote"):
        rows.append({
            "text": q.select_one("span.text").get_text(strip=True),
            "author": q.select_one("small.author").get_text(strip=True),
            "tags": ", ".join(t.text for t in q.select("a.tag")),
        })

    nxt = soup.select_one("li.next a")      # follow pagination
    url = urljoin(url, nxt["href"]) if nxt else None
    time.sleep(1)                           # be polite: 1 request per second

with open("quotes.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["text", "author", "tags"])
    writer.writeheader()
    writer.writerows(rows)
print(f"saved {len(rows)} quotes")

Useful extras

import requests

# Query parameters and JSON APIs (often easier than scraping HTML!)
r = requests.get("https://api.github.com/search/repositories",
                 params={"q": "python", "per_page": 3}, timeout=10)
print(r.status_code)                      # 200 = OK
for repo in r.json()["items"]:
    print(repo["full_name"])

# Download a file (e.g. an image) in binary mode
img = requests.get("https://www.python.org/static/img/python-logo.png", timeout=10)
with open("logo.png", "wb") as f:
    f.write(img.content)

# HTML tables straight into a DataFrame (pip install pandas lxml)
import pandas as pd
tables = pd.read_html("https://en.wikipedia.org/wiki/List_of_programming_languages_by_type")
print(tables[0].head())

# No third-party libraries allowed? Use the standard library
from urllib.request import urlopen
html = urlopen("https://quotes.toscrape.com/").read().decode("utf-8")
print(html[:100])
# scrape responsibly:
  • Check https://site.com/robots.txt and the site's terms of service. Prefer an official API if one exists.
  • Slow down (time.sleep) and identify yourself in the User-Agent. Don't hammer servers.
  • Don't scrape personal data or content behind a login without permission.
  • Pages built with JavaScript may show nothing to requests. Look for the JSON API in your browser's Network tab, or use a browser tool like Playwright.
25

Speed Tips & Pitfalls

Python is slower than C++, so a few habits matter when time limits are tight. A rough budget: Python does about 10⁷ simple operations per second.

✅ Do

  • Read input with sys.stdin; print once with join.
  • Put your code in a main() function: local variables are faster than globals.
  • Use sets/dicts for lookups, deque for queues, heapq for “smallest next”.
  • Prefer built-ins (sum, sorted, comprehensions) over manual loops.
  • Test with the largest allowed input before submitting.
  • If available, submit with PyPy: often 5–10× faster for loops.

❌ Avoid

  • list.pop(0) or list.insert(0, x) in loops.
  • x in big_list inside a loop.
  • [[0]*m]*n for grids.
  • Mutable default arguments.
  • Comparing floats with ==.
  • Naming variables list, sum, max, input by accident — you hide the built-in.

Copying correctly

import copy
grid = [[1, 2], [3, 4]]
shallow = grid[:]               # new outer list, SAME inner lists
deep = copy.deepcopy(grid)      # everything copied
rows_copy = [row[:] for row in grid]   # fast deep copy for a 2D list
grid[0][0] = 99
print(shallow[0][0], deep[0][0], rows_copy[0][0])   # 99 1 1

Time complexity cheat sheet

Operationlistdict / setdequeheapq
Index a[i]O(1)—O(n) middlemin O(1)
Add at endO(1)O(1)O(1)O(log n) push
Remove from frontO(n)—O(1)O(log n) pop
x in cO(n)O(1)O(n)O(n)
SortO(n log n)———
Input size nApproach that usually fits in ~1 second (Python)
n ≤ 10O(n!) — try every permutation
n ≤ 20O(2ⁿ) — every subset / bitmask
n ≤ 500O(n³)
n ≤ 5,000O(n²)
n ≤ 10⁶O(n log n) or O(n)
n ≥ 10⁹O(log n) or O(1) — maths / binary search
26

Contest Template

Paste this at the start of every solution. It has fast I/O, the common imports, and a main() function ready to fill in.

import sys
from collections import Counter, defaultdict, deque
from heapq import heappush, heappop, heapify
from bisect import bisect_left, bisect_right, insort
from itertools import accumulate, permutations, combinations, product
from functools import cache, lru_cache, reduce, cmp_to_key
from math import gcd, lcm, isqrt, inf, comb

input = sys.stdin.readline
sys.setrecursionlimit(10**6)
MOD = 10**9 + 7

def ints():
    return list(map(int, input().split()))

def solve():
    n = int(input())
    a = ints()
    return sum(a)          # <-- your algorithm here

def main():
    t = int(input())       # remove these two lines if there is only one test
    out = [str(solve()) for _ in range(t)]
    print("\n".join(out))

if __name__ == "__main__":
    main()

Try it: save as sol.py, put this in in.txt, then run python3 sol.py < in.txt.

2
3
1 2 3
2
10 20

Expected output: 6 and 30 on separate lines.

27

One-Page Cheat Sheet

Everything above, compressed. Screenshot it, print it, revise it the night before.

I/O

n = int(input())
a = list(map(int, input().split()))
input = sys.stdin.readline
print(*a) · print("\n".join(out))
f"{x:.6f}"

Strings

s[::-1] · s.split() · "".join(l)
ord(c) - ord("a") · chr(97)
s.count(x) · s.find(x) · s.replace(a, b)
s.isdigit() · s.isalpha() · s.lower()

Lists & sorting

[[0]*m for _ in range(n)]
sorted(a, key=lambda x: (-x[1], x[0]))
enumerate(a) · zip(a, b) · zip(*grid)
accumulate(a, initial=0)  # prefix sums

Containers

Counter(a).most_common(k)
defaultdict(list) · deque().popleft()
heappush(h, x) · heappop(h) · -x for max
bisect_left(a, x) · insort(a, x)

Maths & bits

gcd lcm isqrt comb factorial
pow(b, e, MOD) · pow(x, -1, MOD)
(a + b - 1) // b   # ceil div
x >> i & 1 · x.bit_count() · 1 << n

Files & web

with open(p, "r"|"w"|"a", encoding="utf-8") as f:
csv.reader / csv.DictWriter
json.load(f) · json.dump(d, f, indent=2)
requests.get(url, timeout=10)
BeautifulSoup(html, "html.parser").select(css)
# next steps: You now have the tools. Practise turning ideas into code on sites like LeetCode, Codeforces, HackerRank or AtCoder — start with “easy” problems and focus on writing clean Python quickly.