Track B — Python Internals and Runtime Behavior

Reported: Python internals surfaced during the systems-flavored coding round, specifically generators, async constructs, and iterators (../../research/source-report.md rows 24–27).

Rule for this entire track: no claim without a script that demonstrates it. Every statement below is backed by a file in experiments/ that you run and watch. Prose about the runtime is how you end up confidently wrong in an interview.

→ Study guide: WARMUP.md — the CPython runtime from the interpreter up. QUIZBANK.md — 150 questions with full mechanism-level answers.


Table of Contents


How These Questions Actually Arrive

This is the framing that makes the track efficient, and it is inference I4 in ../../research/findings.md.

The reported observations — "Coding 2 was systems-flavored: state management, concurrency, memory efficiency" and "Python internals came up: generators, async, iterators" — are not two separate facts. They are one fact: you are asked to build a stateful streaming component, and the internals questions arise from your own implementation choices.

Nobody asks "explain the descriptor protocol." They ask:

"You used a generator here instead of returning a list. What does that buy you, and what does it cost?"

"This is an async function but you called a blocking library. What happens?"

"You're holding all of these in memory. How much is that actually?"

So preparing internals as trivia is the wrong shape and will feel like wasted work when the round comes. Preparing them as justifications for choices you made thirty seconds ago is the right shape. Every drill in this track is phrased that way.


Concept Inventory

B1. Iterators and Generators

ConceptProven byThe interview form of the question
Iterable vs iterator; why iter(x) is x for oneexp01_generators.py"Why can I only loop over your object once?"
The iterator protocol and StopIterationexp01"What does for actually compile to?"
Generator functions as suspended framesexp01"Where does the local state live between yields?"
send() and primingexp01"Why does sending to a fresh generator raise?"
throw() and its three outcomesexp01"What happens if the generator catches it?"
close() and GeneratorExitexp01"Does your finally run if the consumer stops early?"
yield from delegation and the return valueexp01"Where does the sub-generator's return value go?"
Generators as state machinesexp01"Rewrite this class-based state machine as a generator"
itertools.tee as a memory hazardexp01"You teed the stream. What is that buffering?"
Async generators and aclosingexp02_async.py"Who runs the cleanup in your async generator?"

B2. Async and the Event Loop

ConceptProven byThe interview form
The loop as a ready-callback queueexp02_async.py"Walk me through what happens when you await"
Coroutine vs Task vs Futureexp02"Does calling a coroutine run it?"
gather orphans siblings on failureexp02"One of your five tasks raised. What happened to the other four?"
TaskGroup and structured concurrencyexp02"Why is TaskGroup a bug fix and not a style choice?"
ExceptionGroup and except*exp02"How do you handle three simultaneous failures?"
CancelledError inherits BaseExceptionexp02"Why doesn't except Exception catch cancellation?"
Cancellation is cooperativeexp02"How would you write an uncancellable task by accident?"
Fire-and-forget tasks get garbage collectedexp02"Why did that task never finish?"
A blocking call stalls the whole loopexp02"You called requests.get in a coroutine"
to_thread / executors as the escape hatchexp02"So how do you call a blocking library?"

B3. Concurrency and the GIL

ConceptProven byThe interview form
What the GIL guarantees, and what it does notexp03_gil.py"Is counter += 1 thread-safe?"
list.append atomic; x += 1 not — at the bytecode levelexp03"Show me the bytecode"
Threads give no CPU parallelism under the GILexp03"You added threads and it got slower. Why?"
Processes: cost of IPC and startupexp03"When is multiprocessing not worth it?"
Free-threaded builds: PEP 703, PEP 779, 3.14, Phase IIexp03"Is the GIL gone yet?"
sys._is_gil_enabled()exp03"How would you check at runtime?"
The threads/processes/async decision tablebelow"Which would you reach for here?"
asyncio sync primitives are not thread-safeexp02"Can I share an asyncio.Lock across threads?"

The decision table

ModelWins onThe specific cost that makes it lose
asyncioThousands of concurrent I/O waits; high-fan-out RPCOne blocking call stalls everything; needs an async call stack all the way down
ThreadsBlocking I/O through non-async libraries; moderate concurrencyNo CPU parallelism under the GIL; ~8MB stack each; shared-mutable-state bugs
ProcessesCPU-bound workSerialization on every call; memory duplication; slow startup; no shared objects

One line to remember: async for waiting, processes for computing, threads for when the library gives you no choice.

B4. Memory and the Object Model

ConceptProven byThe interview form
Reference counting + the cycle collectorexp04_memory.py"When is this freed?"
__del__ in cycles; PEP 442 changed this in 3.4exp04"Does your destructor run in a cycle?"
Why weakref.finalize beats __del__exp04"How do you run cleanup safely?"
__slots__: what it removes, what it breaksexp04"How much memory did that actually save?"
A non-slotted subclass regains __dict__exp04"Your subclass undid the optimization"
sys.getsizeof vs real footprintexp04"How much is this list of strings costing?"
tracemalloc for real attributionexp04"Show me, don't tell me"
memoryview and the buffer protocolexp04"How do you slice 100MB without copying it?"
Small-int and string interningexp04"Why is a is b True here and False there?"
pymalloc arenas: freed ≠ returned to the OSexp04"I freed everything and RSS didn't drop"

B5. The Data Model

ConceptProven byThe interview form
Attribute lookup orderexp05_datamodel.py"Rank: instance dict, data descriptor, non-data descriptor"
Data vs non-data descriptorsexp05"Why can't I shadow a @property?"
__getattr__ vs __getattribute__exp05"Which one is the performance hazard?"
The __getattribute__ recursion bugexp05"Why does this hang?"
MRO and C3 linearizationexp05"Which __init__ runs?"
super() is not "the parent class"exp05"What does super() actually resolve to?"
Context managers and __exit__'s return valueexp05"How do you suppress an exception?"
weakref and what cannot be weak-referencedexp04"Build a cache that doesn't leak"

B6. Performance

ConceptProven byThe interview form
Generators vs lists: measured memoryexp04"Why a generator here?"
functools.lru_cache and its keyingexp05"What's the cache key? What if an arg is unhashable?"
Streaming file/network processingTrack A streaming-parser"The file is 40GB"
When to reach for a C extensionthis file"This loop is the bottleneck. Now what?"
dis as a debugging toolexp03"Prove x += 1 isn't atomic"

The Experiments

cd tracks/python-internals/experiments
python3 exp01_generators.py     # iterator protocol, send/throw/close, yield from
python3 exp02_async.py          # loop mechanics, gather vs TaskGroup, cancellation
python3 exp03_gil.py            # GIL, bytecode, threads vs processes, free-threading
python3 exp04_memory.py         # refcounts, cycles, __slots__, memoryview, tracemalloc
python3 exp05_datamodel.py      # descriptors, MRO, __getattr__ vs __getattribute__

Each script prints a claim, then the evidence for it, then the interview-form question it answers. They are meant to be read and modified, not just run — the drill is to predict each output before it prints.


Drill Set

DrillCadenceWhat it trains
Predict-then-runDaily, 10 minOpen an experiment, predict every output, then run. Every miss goes to ../../review/ at 1 day
Justify the choiceWith every Track A problemAfter solving, answer out loud: why a generator here? what does this cost in memory? what breaks if this is called from two threads?
Break it deliberatelyWeeklyIntroduce the bug (blocking call in a coroutine, except Exception swallowing cancellation, a non-slotted subclass) and observe the symptom. Recognizing symptoms is what you need in a room
Bytecode readWeeklydis something you wrote and explain the interpreter's steps
Measure, don't assumeWeeklyAny memory or speed claim you make gets a tracemalloc or perf_counter script before you say it out loud
Quiz passWeekly20 questions from the bank, closed book, confidence-marked

Quiz Bank

The 20-question diagnostic in ../../diagnostics/d3-python-internals.md is the seed, with full answers in ../../diagnostics/ANSWER-KEY.md.

The bank grows to 150+ across the program, one section per inventory area above, generated in batches as each area is drilled. Every question carries: the answer, the mechanism (not just the outcome), and the runnable proof.

The scoring rule that matters: track confident-wrong separately from correct. A gap you know about is a study item. A gap you are confident about is what you will assert in an interview and be corrected on — and the correction costs far more than the admission would have. Confident-wrong answers enter the review queue at the 1-day interval, ahead of everything else.


Failure Modes

FailureSymptomFix
Trivia framingYou can define a descriptor but cannot say why your code needed oneReframe every item as a justification for a choice
Confidently wrongHigh confidence, wrong answer, especially on GIL and asyncPredict-then-run, daily
Version stalenessAnswering the free-threading question from a 2023 memoryRe-verify with sys._is_gil_enabled() and the PEPs; this changes yearly
Prose belief"Generators save memory" with no numberMeasure it. exp04 does
Async cargo cultasync on everything, including CPU-bound codeRead the decision table; run exp02's blocking-call demo
Swallowed cancellationexcept Exception in a coroutine, tasks that will not dieexp02's cancellation section
__slots__ theatreAdding slots without measuring, and with a non-slotted subclassexp04 measures both

Self-Assessment Rubric

LevelStandard
L0≤7/20 on the quiz, or ≥4 confident-wrong
L18–12/20; can state outcomes but not mechanisms
L213–16/20; explains mechanisms; ≤1 confident-wrong
L317–20/20; explains mechanisms, knows the version-dependent answers, and reaches for a measurement rather than an assertion

Hire-bar translation

VerdictWhat it looks like
No hire"Generators are lazy" and nothing beneath it
Hire (senior)Correct outcomes; some mechanism; honest about gaps
Strong hire (senior)Mechanism-level for generators, async, and the GIL; correct on version-dependent facts
Hire (staff)Above, plus connects each to a design decision made under real constraints
Strong hire (staff)Above, plus reaches for dis or tracemalloc unprompted to settle a question rather than asserting

References