Python Performance Quiz: Memory, Big-O and Profiling Practice
Test your Python performance knowledge with 12 questions and three timed programs on reference counting, operation costs, bytecode, profiling and NumPy.
- Course: Python study plan
- Module: Memory, performance and the interpreter
- Kind: Checkpoint — cleared at 70%
- Reading time: 25 min
- Runtime: CPython 3.11
Checkpoint — Memory, performance and the interpreter is the checkpoint that closes the Memory, performance and the interpreter module: a graded quiz and whole-program exercises, passed at 70%.
Instructions
This checkpoint covers the whole module: the object model with reference counting, the cycle collector, weak references and the memory levers; the cost model of the built-in operations and the loop shapes that hide a quadratic; bytecode, code objects, the four kinds of name load and the 3.11 specialising interpreter; measuring with perf_counter, timeit, cProfile and tracemalloc under proper hygiene; numeric performance with array, bytes and NumPy vectorisation; and the checklist for writing fast Python from the algorithm down to the hot loop.
How it works. Twelve questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.
Before you start, make sure you can answer these from memory:
- What does
del xdo to the objectxreferred to, and when is an object actually freed? - Why can reference counting not free two objects that refer to each other?
- What is the cost of
x in lstversusx in s, and oflst.pop(0)versusdeque.popleft()? - Why is
LOAD_FASTcheaper thanLOAD_GLOBAL? - Which statistic does
timeitreport, and why the minimum? - What is the difference between
tottimeandcumtimein a profile? - What does vectorisation remove from a numeric loop?
The three programs are a top-k selection over a large generated stream with a heap, an order-preserving de-duplication of a large generated list that only passes with constant-time membership, and a word-frequency tally over generated tokens with a stable tie order — each sized so that the wrong data structure exceeds the time limit.
Common questions
Why can reference counting not free two objects that refer to each other?
Each object holds a reference to the other, so neither count ever reaches zero, even when nothing outside refers to them. CPython's generational cycle collector finds such groups and frees them.
Why is LOAD_FAST cheaper than LOAD_GLOBAL?
LOAD_FAST reads a local from the frame's slot array by index; LOAD_GLOBAL looks the name up in the module's globals dict and then in the built-ins. Binding a hot global to a local turns those lookups into slot reads.
What does vectorisation remove from a numeric loop?
The per-element interpreter work: a boxed object allocated for each result, type dispatch on every operation, reference-count updates and the bytecode loop itself. The loop still runs, but in C over raw numbers.
Exercises
Top-k with a heap
Read n k seed. Generate xs = [rng.randrange(10**9) for _ in range(n)] with rng = random.Random(seed) and find the k largest values with heapq.nlargest — O(n log k), not a full sort. Print top <the k values, largest first, space-separated> and threshold <the k-th largest>.
Input: n k seed. Output: two lines.
10 3 1
prints
top 909925047 861425548 820096753
threshold 820096753Order-preserving de-duplication
Read n seed. Generate words = [f"w{rng.randrange(20000)}" for _ in range(n)]. Remove duplicates keeping the first occurrence of each word, in O(n) with a seen set — a list membership test per word is O(n·d) and will not finish the hidden case. Print distinct <count>, first <the first five distinct words> and last <the last five distinct words> (fewer when there are fewer).
Input: n seed. Output: three lines.
12 1
prints
distinct 12
first w4402 w18651 w2067 w8358 w3863
last w15474 w12439 w6879 w3075 w15986Frequencies with stable ties
Read n seed. Generate tokens = [f"t{rng.randrange(50)}" for _ in range(n)]. Count them with collections.Counter and print the five most frequent (fewer if there are fewer distinct) as <token> <count>, ordered by count descending and then by token ascending — most_common alone does not define the tie order, so sort explicitly. Finish with distinct <number of distinct tokens>.
Input: n seed. Output: five lines, then the count.
30 1
prints
t48 3
t24 2
t28 2
t31 2
t6 2
distinct 24In this module: Memory, performance and the interpreter
- The object model — objects, references, reference counting and the cycle collector
- The cost model — what the built-in operations really cost
- Bytecode and the interpreter — code objects, dis, name lookup and the 3.11 specialiser
- Measuring — timeit, perf_counter, cProfile, tracemalloc and benchmarking hygiene
- Numeric performance — boxed numbers, array, bytes and NumPy vectorisation
- Writing fast Python — the checklist, from algorithm to micro-optimisation
- Checkpoint — Memory, performance and the interpreter (this lesson)
← Writing fast Python — the checklist, from algorithm to micro-optimisation · The interview template — the round, the file, fast I/O and what is actually judged →