The Architect's Question
What does it mean to count the cost before you build?
Chapter 1 — The Architect’s Question
“Premature optimization is the root of all evil.” — Donald Knuth
“For which of you, desiring to build a tower, does not first sit down and count the cost, whether he has enough to complete it?” — Luke 14:28 (ESV)
Why This Matters
You finished Coding 2. You can read an unfamiliar program and say what it actually does. You can write a specification before you write a line of logic. You test before you trust. You debug by hypothesis, not by flailing. And you can sit a fluent, fast, occasionally-wrong AI partner down beside you and direct it like a senior directs a junior — taking what’s good, catching what’s wrong.
That was the director’s skill. This book is about the architect’s.
Here is the difference, in one sentence. A director makes a given piece of code good. An architect decides which pieces should exist at all, what each one will cost, and how they fit together — before any code is written. The director answers “is this code correct?” The architect answers a harder question that comes earlier: “given the real constraints of this problem, what is the right tool, and what will it cost me in speed, in memory, and in complexity?”
That is the question this book is named for. And it is the one question your AI partner cannot answer for you. AI builds what you specify. It builds it fast, it builds it fluently, and in Phase 2 of this book it will act on your behalf — editing your files, running your code, iterating toward a goal you set. But it does not know which goal is worth setting. It does not know that your data will arrive sorted, that your users will hammer one endpoint a thousand times a second, that your memory budget is forty megabytes on a device in a church basement. You know those things. The constraints are yours, the cost accounting is yours, and therefore the architecture is yours.
To do that accounting, you need a language of cost. That is what Phase 1 of this book is: eight weeks learning what every classical data structure and every concurrency choice actually costs, by building each one by hand and measuring it. This chapter is the vocabulary lesson. By the end of it you will be able to say what a piece of code costs in the two currencies that matter — time and memory — both in the abstract (Big-O) and in real measured milliseconds and bytes. Everything after this chapter spends that vocabulary.
The Christian question for the week is the one Jesus asks in Luke 14: what does it mean to count the cost before you build? He is talking about discipleship, about a man who lays a foundation and cannot finish the tower and becomes a laughingstock. But the engineering image is exact, and not by accident — the Lord chose a builder’s image because building has always demanded foresight. The wise builder sits down first and counts. The fool starts laying stone and discovers the cost when the money runs out and the half-tower stands as a monument to his haste. This chapter teaches you to sit down first and count.
1.1 — What Changed Since Coding 2
In Coding 1 you wrote programs. In Coding 2 you read, specified, tested, and directed them. In both, the unit of work was a program — one artifact, one language, one file or handful of files, doing one job.
Coding 3 changes the unit of work to a system. A real internet-aware application is not one program. It is a server that waits at a port, a database that remembers, a front end that a human looks at, and the seams between them — and every one of those pieces could be built five different ways, each with a different cost. The job is no longer “write the program correctly.” The job is “choose the pieces correctly, then write each one correctly.”
The thesis of the whole book follows from that:
The right tool for the job is a decision you make BEFORE you write code, driven by the constraints of the problem.
Notice the order. The decision comes before the code. This is the opposite of how most beginners work — they start typing, hit a wall, and reach for whatever they already know. An architect inverts it. The constraints come first: how much data, how fast, how much memory, how many users, how often does it change, who maintains it. The constraints determine the cost budget. The cost budget determines which tools can possibly work. Then you write code, inside a decision you already made on purpose.
Coach’s Note — “Right tool for the job” sounds like a platitude until you notice the second half of it: for the job. There is no universally right data structure, no universally right database, no universally right concurrency model. There is only the right one for these constraints. A hash table is the right tool until you need ordered iteration. A linked list is wrong until the one workload where it’s right. Half of this book is teaching you to finish the sentence: right tool — for what?
You cannot make that decision honestly if you have only ever felt the cost of one tool. So Phase 1 makes you build and measure all of them. You will hand-build a dynamic array, a linked list, a hash table, a tree, a graph. You will watch the linked list lose a footrace to the array even though the textbook says it should win. You will watch a hash table degrade from magic to molasses under a bad hash function. By Week 8 you will have felt the cost of every Phase 1 structure in your own measurements — and only then are you allowed, in Phase 2, to let an agent build them while you make the architecture call.
1.2 — Big-O: The Language of Cost
You have seen Big-O notation before, probably as something to memorize for an exam. Forget the exam. Big-O is a language for talking about how cost grows as the problem gets bigger. That’s all it is, and that’s enormous.
Here is the only definition you need: Big-O describes how the work a program does grows as its input n grows, ignoring constant factors and lower-order terms. It answers one question — “if I make the input twice as big, what happens to the cost?” — and deliberately ignores everything else so the answer stays simple.
The five shapes you will meet for the rest of this book, with an everyday analogy for each:
| Big-O | Name | If n doubles, cost… | Everyday analogy |
|---|---|---|---|
| O(1) | constant | stays the same | grabbing the top book off a stack |
| O(log n) | logarithmic | grows by one step | finding a word in a dictionary by halving |
| O(n) | linear | doubles | reading every name on a roster once |
| O(n log n) | linearithmic | a little more than doubles | sorting a deck of cards properly |
| O(n²) | quadratic | quadruples | every guest shaking every other guest’s hand |
Look at the third column. That is the whole point of Big-O — not the formula, but the rate of change. When you double the input, O(1) doesn’t notice, O(n) doubles, and O(n²) quadruples. That last one is the killer. Quadratic code is fine on your laptop with ten items and catastrophic in production with a hundred thousand. The architect sees O(n²) and immediately asks: how big does n get in real life?
Here is each shape as the smallest honest Python snippet (code/complexity_demos.py has all five, runnable):
def first_item(data): # O(1) — one index, ignores the rest
return data[0]
def total(data): # O(n) — touch each item once
running = 0
for x in data:
running += x
return running
def has_duplicate(data): # O(n**2) — every pair
n = len(data)
for i in range(n):
for j in range(i + 1, n):
if data[i] == data[j]:
return True
return False
first_item does the same one operation whether data has ten items or ten million. total does n additions. has_duplicate does roughly n²/2 comparisons — every item against every later item. Read the nesting: a loop inside a loop, each running about n times, is the visual signature of O(n²). Train your eye to see it.
O(log n) is the one that surprises people, because it’s so good. Binary search on a sorted list halves the search space every step. To search a sorted list of a billion items takes about thirty steps — log₂(1,000,000,000) ≈ 30. Doubling the list to two billion adds one step. That is why “can I keep this sorted and binary-search it?” is a question the architect asks constantly.
def binary_search(sorted_data, target): # O(log n) — halve each step
lo, hi = 0, len(sorted_data) - 1
while lo <= hi:
mid = (lo + hi) // 2
if sorted_data[mid] == target:
return mid
if sorted_data[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1
O(n log n) is the cost of a good sort, and it’s the speed limit for comparison sorting — you cannot sort by comparing elements faster than that. Whenever you see sorted(data) in Python, you are paying O(n log n) and getting an extraordinary deal.
Coach’s Note — Big-O drops constant factors on purpose. O(n) and O(100n) are the same Big-O. This is a feature when you’re reasoning about scale and a trap when you’re reasoning about a real machine. A program that does 100 operations per item can be slower than one that does 5 operations per item even though both are “O(n).” Big-O tells you the shape of the curve, not its height. We will return to this — hard — in §1.6, because at realistic sizes the height is often what bites you.
1.3 — A Cost Table You Should Burn Into Memory
Abstractions become real when you put numbers on them. Suppose one “operation” takes one nanosecond — roughly one simple thing your CPU can do. Here is how long each complexity class takes as n grows:
n | O(1) | O(log n) | O(n) | O(n log n) | O(n²) |
|---|---|---|---|---|---|
| 10 | 1 ns | 3 ns | 10 ns | 33 ns | 100 ns |
| 100 | 1 ns | 7 ns | 100 ns | 664 ns | 10 µs |
| 1,000 | 1 ns | 10 ns | 1 µs | 10 µs | 1 ms |
| 1,000,000 | 1 ns | 20 ns | 1 ms | 20 ms | 17 minutes |
| 1,000,000,000 | 1 ns | 30 ns | 1 s | 30 s | 30+ years |
Read the bottom-right cell again. At a billion items, the quadratic program runs for decades. The linear one finishes in a second. The logarithmic one finishes before you’ve lifted your finger off the Enter key. Same problem, same input, three wildly different fates — and the only difference is the shape of the algorithm you chose. That choice is architecture. That choice is this book.
Coach’s Note — This table is why “it works on my test data” is the most dangerous sentence in software. Your test data has
n = 10. Every cell in the top row is under a microsecond; the quadratic disaster is invisible. Production hasn = 1,000,000, and the disaster is a server that falls over. The architect predicts the bottom of the table from the top of it. That prediction is exactly what Project 1 asks you to make — and then confirm.
1.4 — Measuring Real Time: time.perf_counter()
Big-O is a prediction. It tells you the shape of the curve. But predictions can be wrong — you can misread an algorithm, or a constant factor you ignored can dominate at the size you actually run at. So the architect does not stop at predicting. The architect measures.
The amateur guesses. The architect measures, then explains the gap between the measurement and the guess.
Python’s tool for measuring time is time.perf_counter() — the highest-resolution clock available, designed exactly for timing short durations. You call it before and after the thing you want to measure and subtract:
import time
start = time.perf_counter()
result = do_the_work(data)
elapsed = time.perf_counter() - start
print(f"{elapsed * 1000:.4f} ms")
That is the whole idea. But naive timing lies, and lies in four specific ways. A real harness — the one in code/timing_harness.py — defends against all four:
import gc
import time
def time_call(func, *args, repeats=5):
func(*args) # 1. WARMUP — pay one-time costs untimed
gc_was_on = gc.isenabled()
gc.disable() # 3. GC OFF — don't blame us for a GC pause
best = float("inf")
try:
for _ in range(repeats): # 2. REPEAT and take the MINIMUM
start = time.perf_counter()
func(*args)
elapsed = time.perf_counter() - start
best = min(best, elapsed)
finally:
if gc_was_on:
gc.enable()
return best # the minimum is the cleanest signal
The four defenses, and the pitfalls they fix:
- Warm up. The first call to a function pays one-time costs — caches are cold, lookups aren’t resolved, memory isn’t allocated yet. Time the second call onward. Timing the very first run measures startup, not the algorithm.
- Repeat and take the minimum, not the mean. Your operating system is constantly stealing your CPU to do other work. That noise only ever makes a run slower, never faster. So the fastest run is the one where the OS interfered least — it’s the cleanest measurement of your code. The mean averages in the noise; the minimum filters it out.
- Pause the garbage collector. Python periodically stops your program to reclaim memory. If a GC pause lands inside your timed region, you’ll blame your code for time the collector spent. Disable GC for the timed block, then turn it back on. (Be honest in your write-up that you did this; it’s a measurement choice.)
- Time the right thing. The most common pitfall of all: timing the setup instead of the work. If you time
make_data(n)andsort(data)together, you measured both. Build the input outside the timed region. Time only the operation under test.
And the fifth pitfall, which no code can fix for you: tiny n. At n = 10 every algorithm finishes in nanoseconds and the differences vanish into clock jitter. You cannot see the shape of a curve from one point near zero. You must measure across growing n — 1,000, then 2,000, then 4,000, doubling — and watch how the time responds to the doubling. That response is the Big-O, revealed empirically.
Here is the harness’s real output for the quadratic program, run on the author’s machine, on all-unique input (the worst case, where it never returns early):
O(n**2) has_duplicate_pairwise(data) [unique input]
n best (ms) ms / n
500 2.5186 0.00503717
1000 10.2010 0.01020100
2000 40.8989 0.02044946
4000 166.2673 0.04156681
Look at the best (ms) column. Each time n doubles, the time roughly quadruples — 2.5 → 10 → 41 → 166. That is O(n²) confirmed by measurement, not asserted by theory. (The ms / n column doubles too, which is the same fact seen sideways: per-item cost grows linearly in a quadratic algorithm.) Contrast the linear program on the same machine, where doubling n merely doubles the time: 1k → 0.0145 ms, 2k → 0.0250, 4k → 0.0495, 8k → 0.1020. Run the harness yourself; your numbers will differ in magnitude but the shape will be identical, because the shape is the algorithm and the magnitude is your hardware.
Coach’s Note — Your numbers will not match mine, and that is correct. A faster CPU shifts the whole table down; a busier machine adds noise. What must match is the ratio — the way the time responds when you double
n. When a junior says “my timings don’t match the book’s,” they’re usually comparing magnitudes. When a senior says it, they’ve found that the ratio is wrong, which means the algorithm isn’t what they thought. Measure ratios, not magnitudes.
1.5 — Measuring Space, Briefly
Time is one currency. Memory is the other, and Python hides it from you even more thoroughly. In C++ you sized your allocations; in Java the JVM was at least honest about heap pressure. In Python a list just… grows, and you never see the bytes. The architect makes them visible again with two tools.
sys.getsizeof(obj) reports the size of one object in bytes:
import sys
sys.getsizeof([]) # 56 — an empty list still costs something
sys.getsizeof(list(range(1000))) # 8056 — the internal pointer array
sys.getsizeof(0) # 24 — even a tiny int is a full object
sys.getsizeof(10 ** 100) # 72 — big integers cost more (no overflow!)
The first lesson here is just how expensive Python objects are compared to C++/Java primitives. In C++ an int is 4 bytes, full stop. In Python the integer 0 is a 24-byte heap object. This is the price Python pays for hiding the memory from you, and it is a real architectural cost — a million Python ints in a list is not 4 MB, it’s tens of MB.
But getsizeof has a sharp edge: it is shallow. sys.getsizeof(list(range(1000))) reports 8056 bytes — the size of the list’s internal array of pointers — not the 1000 integer objects those pointers point at. For the deep, total cost of a block of code, use tracemalloc:
import tracemalloc
tracemalloc.start()
before = tracemalloc.get_traced_memory()[0]
squares = [i * i for i in range(100_000)] # materialize a whole list
after = tracemalloc.get_traced_memory()[0]
tracemalloc.stop()
print(after - before, "bytes") # ~3,869,380 bytes
That same computation as a generator — (i * i for i in range(100_000)) — allocates 188 bytes, because a generator computes each square on demand instead of storing all hundred thousand. (code/space_demo.py runs both.) Same answer to the question “what are the squares?”; radically different memory cost. That is the time/space tradeoff in miniature: the list is instant to re-read but expensive to hold; the generator is cheap to hold but can only be walked once. Neither is right. The right one depends on the constraint — and naming the constraint is your job.
1.6 — Asymptotics Can Lie
Here is the theme that will haunt the whole book, introduced now so you’re never surprised by it:
Big-O describes behavior as
napproaches infinity. You do not run at infinity. You run at thenyour problem actually has — and at thatn, the constant factors and the hardware can dominate the asymptotics completely.
Two forces make the “worse” algorithm sometimes win at realistic sizes.
Constant factors. Big-O throws away constants. But an O(n) algorithm that does fifty expensive operations per item can be slower, at every realistic n, than an O(n log n) algorithm that does one cheap operation per item. The crossover point where the asymptotically-better algorithm finally pulls ahead might be at n = 10,000,000 — and if your n never exceeds 10,000, the “worse” algorithm is the right choice. The architect knows this and measures the crossover instead of trusting the exponent.
Cache effects. This one will genuinely surprise you, and you’ll prove it to yourself in Week 3. Modern CPUs are vastly faster than memory, so they keep a small, fast cache of recently-used data, and they fetch memory in contiguous chunks. An array stores its elements side by side, so walking an array is a parade of cache hits. A linked list scatters its nodes all over memory, so walking it is a parade of cache misses — each one a long stall while the CPU waits for memory. The result: an array often beats a linked list at operations the textbook says the linked list should win, purely because of memory layout the Big-O analysis can’t see. The asymptotics said “tie” or “linked list wins.” The cache said otherwise. The cache was right.
This is not a reason to distrust Big-O. Big-O is the indispensable first cut — it tells you which algorithms are even in the running. It is a reason to distrust Big-O alone, and to finish every analysis with a measurement. Big-O is a tool, not a god. Predict with it; confirm with the clock. Project 1’s Hard tier is built entirely around finding a program whose measured behavior contradicts its apparent Big-O and explaining why — your first real encounter with the gap between the theory and the machine.
Coach’s Note — When the measurement disagrees with the prediction, the measurement is not “wrong.” The measurement is the truth; your prediction was incomplete. The senior move is not “my timing must be broken” — it’s “what cost did my Big-O analysis ignore that the machine just charged me for?” That question — asked honestly, every time — is how you graduate from someone who recites complexity classes to someone who architects with them.
1.7 — Just Enough Python for a C++/Java Programmer
Python is the language of Phase 1. You have never written it, but you are not a beginner — you are an experienced programmer learning a new dialect, and the right move is to map it onto what you already know.
Dynamic typing. In Java you wrote int count = 0;. In Python you write count = 0 and the name count is just a label bound to an object; it can be re-bound to a string on the next line (don’t, but it can). There is no compiler checking types ahead of time. This is faster to write and easier to get subtly wrong — a discipline Coding 2’s testing muscle now has to cover, because the compiler won’t.
count = 0 # no type declared; count refers to an int object
count = count + 1 # still an int
name = "Maya" # a different name bound to a str object
total = 3.14 # a float; Python infers all of this at runtime
Indentation is the block structure. Java used { }. Python uses a colon and indentation. The whitespace is not cosmetic — it is the syntax. A misaligned line is a syntax error or, worse, a silently different program.
def greet(name):
if name:
return "Hello, " + name # this line is inside the if
return "Hello, stranger" # this line is outside it
list and dict exist — and we will not use them as black boxes. Python ships a list (a dynamic array) and a dict (a hash table) that are superbly engineered. You will use them freely in throwaway code. But the entire point of Phase 1 is that you will build these yourself — your own dynamic array in Week 2, your own hash map in Week 5 — so that you understand the cost of the ones Python gave you. When this book says “build a hash map,” it does not mean “use a dict.” It means build the thing dict is.
The mental model that makes cost visible: bring your C++/Java memory picture with you. This is the single most important sentence in the chapter for you specifically. Python hides the memory — there are no pointers, no new, no delete, no stack-versus-heap you can see. But the memory is still there, and the costs are still paid. The way you keep cost visible in Python is to mentally translate back to the explicit-memory model you learned in Coding 1: a Python list is a C++ vector (a pointer to a contiguous, growable array); a Python object reference is a pointer; appending to a list that’s full is a realloc that copies everything. Python won’t show you the pointer-chasing and the reallocations. Your C++/Java eyes will. Keep them open.
Coach’s Note — Do not let Python’s friendliness lull you into thinking the costs went away. They didn’t go away; they went invisible. The student who thinks “Python is slow and that’s just how it is” has given up the architect’s job. The student who thinks “this
list.insert(0, x)is secretly an O(n) shift of every element, the same as it was in my C++ vector” still has it. Phase 1 is eight weeks of refusing to let Python hide the bill.
1.8 — The Local Toolchain
Coding 1 and Coding 2 ran in a browser editor so you could focus on the language without fighting your machine. That ends now. A working architect runs real tools on a real machine: a Python interpreter, Node.js, a real text editor or IDE, and git for version control. You cannot build a server that listens on a port from inside a browser sandbox, and you cannot honestly measure performance on someone else’s shared cloud machine.
The full setup — installing Python 3 and Node, choosing an editor, configuring git, and verifying it all works — lives in Appendix A. Do it before Week 2. Every project from here forward is submitted as a public GitHub repository, not a pasted link, because that is how real software is shared and reviewed.
This chapter’s code runs with nothing but a Python 3 interpreter:
python3 complexity_demos.py
python3 timing_harness.py
python3 space_demo.py
If those three commands print tables on your own machine, your toolchain is alive. If they don’t, stop and fix that first — Appendix A is your map.
1.9 — Agentic AI: It Doesn’t Just Talk, It Acts
In Coding 2 you pair-programmed with AI the way a senior works with a junior: you asked, it answered, you read the answer critically, you decided what to keep. The AI talked. You did the acting — you ran the code, you ran the tests, you made the edits.
Coding 3 introduces agentic AI, and the difference is exactly one word: it acts. An agent doesn’t just suggest a function — it writes the file, runs your test suite, reads the failure, edits the code, and runs the tests again, looping toward a goal you set, taking many real actions on your machine without stopping to ask after each one. It is far more powerful than chat pairing. It is also far more dangerous, because a fast partner taking real actions can do real damage fast.
So there is one non-negotiable rule, and it is the spine of the entire AI thread in this book:
The human stays in the loop where the judgment lives. You own the architecture and the cost decisions. The agent owns the typing.
The agent can build the dynamic array once you’ve decided a dynamic array is the right tool. The agent cannot decide that a dynamic array — rather than a linked list, or a hash map, or a database table — is the right tool, because that decision requires knowing the constraints, and weighing the costs, and that is your job and the reason this course exists. Every Phase 2 project will require an agent-log.txt: every task you delegated, what the agent did, where it went wrong, and where you intervened. And every Phase 2 project is deliberately shaped so the agent cannot finish it alone — the architecture call is reserved for the human.
For Phase 1, the rule is simpler and stricter: AI is OFF. All eight weeks. You cannot reason about the cost of a structure you have never built, and you cannot direct an agent to choose well if you have never paid the price of choosing badly. So you will build every Phase 1 structure with your own hands, AI silent. The toolkit, the setup, and the full rules of engagement for agents are in Appendix C.
Coach’s Note — Here is the trap, stated plainly so you can avoid it for the next sixteen weeks: the agent is so fluent that it is easy to let it make the architecture decision by default — you accept whatever structure it reached for, and now it chose the tool and you just typed-by-proxy. That is vibe coding with extra steps, and it is exactly the failure this book exists to prevent. The agent reaches for what’s common. You reach for what’s right for the constraints. Those are often different, and the difference is your entire value.
1.10 — How This Book Works
The mechanics, so you can plan your sixteen weeks:
- Two phases. Phase 1 (Weeks 1–8) is cost: data structures and concurrency, built by hand in Python, AI off. Phase 2 (Weeks 9–16) is systems: servers, databases, and a front end, with agentic AI on and the right-tool decision foregrounded.
- Three tiers per project. Every project has a Normal tier (the core skill, graded out of 100), a Medium tier (+ up to 25% extra credit), and a Hard tier (+ up to 25% more). The Hard tier always demands judgment, not just more code — in Phase 1 it’s usually a measurement or a memo; in Phase 2 it’s an architecture call an agent cannot make for you.
- Each chapter has Reps and a Project. The reps (the exercises) are conditioning — small, hand-built, run-everything drills. The project is the week’s real deliverable.
- Two exams. The midterm is Project 8 — a 60-minute live, closed-AI, closed-internet build of a concurrent pipeline using the structures you built all of Phase 1. The final is Project 14 — a take-home full-stack application with a 60-minute live integration session, open-AI (agentic), graded most of all on the architecture document you write first.
Every Phase 1 project ends with a written measurement or memo about cost. That is not busywork. The thinking about cost is the assignment; the lines of code are how you earn the right to think it.
1.11 — Common Bugs (Measurement Edition)
These are the bugs your measurement introduces — places where you trust a number that is lying to you.
Bug: You timed at n = 10 and concluded two algorithms cost the same.
Example: has_duplicate and a hash-set lookup both finish in microseconds on ten items, so you “prove” they’re equivalent. At n = 100,000 one is instant and the other runs for seventeen minutes.
Fix: Never conclude a complexity class from one tiny n. Measure across doubling sizes and watch the ratio respond.
Bug: You timed the setup along with the work.
Example: Your timed region includes data = make_data(n), so you measured how long it takes to build the input, not how long the algorithm takes to process it.
Fix: Build all inputs outside the timed region. The clock starts immediately before the operation under test and stops immediately after. Nothing else lives between.
Bug: You took the mean of your timing runs and got a noisy, inflated number. Example: One run got interrupted by your browser updating, so your mean of five runs is 40% higher than the truth. Fix: Take the minimum across repeats. The OS only ever slows a run down, so the fastest run is the least-contaminated measurement of your code.
Bug: You forgot the first run is a warmup and timed cold-cache startup as if it were the algorithm. Example: Run one is 3× slower than runs two through five, and you reported run one. Fix: Call the function once, untimed, before the measured loop. Then time.
Bug: You trusted sys.getsizeof on a list and reported a number far too small.
Example: sys.getsizeof(list(range(1_000_000))) reports ~8 MB and you conclude a million ints cost 8 MB. They cost much more — getsizeof reported only the pointer array, not the million int objects.
Fix: Use tracemalloc for the deep cost of a data structure. Reserve getsizeof for single, flat objects whose shallow size is the whole story.
Bug: A garbage-collection pause landed inside your timed region and you blamed your code.
Example: Your O(n) function shows a single 50 ms spike at one size and a perfect line everywhere else — that spike is a GC pause, not your algorithm.
Fix: gc.disable() around the timed region, gc.enable() after (in a finally). Note in your write-up that you did it; it’s a measurement choice you’re accountable for.
1.12 — Reps
Open the exercises for the full set. This week’s reps are conditioning in the language of cost: you will read code and predict its Big-O, hand-build the timing harness, measure across growing n, and confront a case where the asymptotics lie. AI is OFF — Phase 1.
A preview:
- Rep 1 — Hello, Python. Translate a tiny Java method you already know into Python and run it.
- Rep 3 — Read five snippets and write the Big-O of each from the code alone.
- Rep 6 — Build the timing harness from scratch and time a linear program across doubling
n. - Rep 9 — Measure a quadratic program and confirm the time quadruples per doubling.
- Done? — Predict, measure, and confirm a program cold, the P1 move in miniature.
Do every one. The harness you build in the reps is the harness you’ll use in the project, and reuse for the rest of Phase 1.
1.13 — This Week’s Project
You’re ready for Project 1 — Measure, Predict, Confirm, in Project 1.
You’ll be given four small Python programs spanning four complexity classes. Before you run anything, you will predict in writing how each scales. Then you’ll instrument them with the timing harness, run them at growing input sizes, plot the results, and write a one-page report confirming or correcting each prediction against the curve you actually measured. The Medium tier adds memory measurement and a case where time and space point in opposite directions. The Hard tier asks you to find the program whose measured behavior contradicts its apparent Big-O — and explain why the asymptotics lied at that scale.
This is the first project in the book, and it is mostly thinking, not typing. Don’t underestimate it. A confident prediction confirmed by an honest measurement is the entire architect’s move, performed once, small. Everything after this is that move at larger scale.
1.14 — Coach’s Final Word for Week 1
Coding 1 made you a writer. Coding 2 made you a director. Coding 3 makes you an architect — and the architect’s first and last move is the same one Jesus put in the mouth of a tower-builder: sit down first, and count the cost.
This week you don’t build a system. You learn to count. You learn the language of cost — Big-O — and you learn to confirm it with a clock and a memory profiler, because a prediction you never check is a guess wearing a lab coat. You learn just enough Python to be dangerous, and you learn that the C++/Java memory model you already own is the lens that keeps Python’s costs visible. And you meet the agent that will build alongside you in Phase 2 — and the rule that you, not it, own every decision that requires judgment.
If you find this slow: that’s the gap. The thinking is the work. Close it.
If you find this easy: predict harder, measure more honestly, and don’t trust a single number that you haven’t earned with a clean measurement.
The amateur guesses. The architect counts the cost — first, on purpose, with the numbers in hand. Welcome to Coding 3.
See you on Monday.
Up next: Read the exercises and complete every rep — type every line, run everything, AI off. Then open Project 1 and make your first prediction-and-confirmation. Set up your toolchain with Appendix A and meet the agent rules in Appendix C before you go further. (New here? Start at the book home and the README.) After that, Chapter 2 — arrays and the memory you can feel.