Advanced Python: question bank¶
Cross-topic graded questions, production scenarios and rapid-fire drills. Per-topic questions live at the bottom of each topic page (see the track index). Answer aloud first, then open the model answer. Python behaviour below was checked on 3.14 unless noted.
Levels: L1 recall, L2 apply (including "what does this print" and "fix this bug"), L3 design and trade-offs, L4 Staff-level ambiguity.
Graded questions¶
L1 - Recall¶
G1. What is the difference between uv sync --locked and uv sync --frozen?
Answer
--locked verifies that uv.lock is up to date with pyproject.toml and errors if not (use in CI to catch forgotten lock updates). --frozen uses the lockfile as-is without checking that it matches pyproject.toml. Prefer --locked in CI, and --frozen only when you deliberately skip the check.
G2. What does __hash__ become when a class defines __eq__ but not __hash__?
Answer
None, so instances are unhashable (TypeError: unhashable type). Define __hash__ over immutable fields, or use @dataclass(frozen=True).
G3. Which is looked up first for obj.x: a data descriptor on the class or obj.__dict__['x']?
Answer
The data descriptor (has __set__/__delete__). A non-data descriptor (only __get__, such as a plain function or cached_property) loses to the instance dict.
G4. Which PEP gave the def f[T](x: T) syntax, and what else came with it?
Answer
PEP 695 (3.12): inline type parameters for functions and classes, the type X = ... alias statement (lazily evaluated), and inferred variance for class type parameters. PEP 696 (3.13) added defaults such as class Box[T = str].
G5. What does model_validate_json do that model_validate(json.loads(s)) doesn't?
Answer
Parses and validates in one pass inside Rust (jiter + pydantic-core) without building an intermediate Python object graph. It is faster and also gives JSON-specific error locations and handling (for example strict-mode behaviour for JSON types).
G6. Name three differences between asyncio.gather and asyncio.TaskGroup.
Answer
(1) On first failure TaskGroup cancels siblings and waits, gather doesn't cancel the others. (2) TaskGroup raises an ExceptionGroup (catch with except*), gather raises the first exception. (3) TaskGroup guarantees no child outlives the async with block (structured concurrency), gather can leave orphans. Also: return_exceptions=True exists only on gather.
G7. Why is CancelledError a BaseException?
Answer
So that except Exception blocks don't accidentally swallow cancellation. Catch it only to clean up and re-raise.
G8. Free-threaded build: what does sys._is_gil_enabled() return, and can it change at runtime?
Answer
False when the GIL is off. On a free-threaded build the GIL can be re-enabled at runtime if an imported C extension doesn't declare support (with a warning), and PYTHON_GIL=0/-X gil=0 forces it off. On a normal build it always returns True.
G9. What is a Protocol's advantage over an ABC for ports?
Answer
Structural typing: implementations and test fakes needn't inherit from or import the port, which keeps dependencies pointing inward and lets you wrap third-party classes. Cost: no runtime enforcement of behaviour, so use contract tests.
G10. What does @functools.wraps copy, and why do frameworks care?
Answer
__name__, __qualname__, __doc__, __module__, __dict__ and sets __wrapped__, so inspect.signature follows through to the original signature. FastAPI DI, Pydantic AI tool schemas and pytest introspect signatures.
G11. What are TypeIs and TypeGuard, and which do you prefer?
Answer
Both are return annotations for user-defined narrowing functions. TypeIs (3.13) narrows in both branches and requires consistency with the input type, TypeGuard narrows only when True and is looser. Prefer TypeIs.
G12. When does __init_subclass__ run?
Answer
On the parent class whenever a subclass is created (at class definition/import time), for direct and indirect subclasses. It replaces most metaclass uses for registries and contract validation.
G13. What are the standard signals of observability and the key correlation field?
Answer
Traces, metrics, logs. Correlate through trace_id/span_id injected into log records, and exemplars linking metrics to traces.
G14. Why does Polars prefer scan_parquet over read_parquet for big data?
Answer
scan_* builds a lazy plan, so the optimiser pushes filters and column selection into the file scan, and the streaming engine can process larger-than-memory data. read_* loads everything eagerly.
L2 - Apply¶
G15. What does this print?
Answer
[2, 2, 2]. The lambdas share the variable i and see its final value. Bind it with lambda i=i: i.
G16. What does this print?
Answer
False. __bool__ takes precedence over __len__.
G17. What does this print, and what's the practical lesson?
class A:
def __init__(self, d): self.d = d
def __len__(self): return len(self.d)
def __getitem__(self, i): return self.d[-1 - i]
a = A([1, 2, 3])
print(list(a), 2 in a)
Answer
[3, 2, 1] True. Without __iter__, iter() falls back to the legacy sequence protocol: __getitem__ with 0, 1, 2, ... until IndexError. in falls back to iteration too. Lesson: define __iter__ explicitly, or inherit collections.abc.Sequence.
G18. What does this print?
from pydantic import BaseModel
class M(BaseModel):
x: int
m = M(x=1)
m2 = m.model_copy(update={"x": "oops"})
print(repr(m2.x), M.model_construct(x="bad").x)
Answer
'oops' bad. model_copy(update=...) and model_construct skip validation. Re-validate with M.model_validate({**m.model_dump(), "x": "oops"}) for untrusted changes.
G19. What does this print (Python 3.14)?
async def stubborn():
try:
await asyncio.sleep(10)
except asyncio.CancelledError:
print("swallowed")
return "done"
async def main():
t = asyncio.create_task(stubborn())
await asyncio.sleep(0)
t.cancel()
print(await t)
asyncio.run(main())
Answer
swallowed then done. The task swallowed the cancellation and finished normally, so awaiting it returns a value instead of raising. Re-raise CancelledError after cleanup.
G20. What does this print?
async def tick():
for _ in range(3):
print("tick", round(time.perf_counter() - t0, 1)); await asyncio.sleep(0.1)
async def blocker(): time.sleep(0.5)
t0 = time.perf_counter()
await asyncio.gather(tick(), blocker())
Answer
tick 0.0, tick 0.5, tick 0.6. The blocking time.sleep(0.5) freezes the loop, so the second tick is delayed until the sleep finishes. Fix with await asyncio.sleep or asyncio.to_thread.
G21. Fix this dataclass.
Answer
Raises ValueError: mutable default <class 'list'> for field messages is not allowed: use default_factory. Use messages: list[str] = field(default_factory=list).
G22. What does this print?
from contextlib import contextmanager
@contextmanager
def res():
print("open"); yield; print("close")
try:
with res(): raise RuntimeError
except RuntimeError: pass
Answer
Only open. Without try/finally around yield, the exception thrown into the generator skips the cleanup. Wrap yield in try/finally.
G23. What does this print?
def inner():
yield "a"; return 42
def outer():
r = yield from inner()
print("inner returned", r)
print(list(outer()))
Answer
inner returned 42 then ['a']. yield from evaluates to the sub-generator's return value.
G24. Why does this raise, and how do you fix it?
Answer
AttributeError: 'P' object has no attribute 'b' and no __dict__ for setting new attributes. Slotted classes have no instance dict. Add the name to __slots__ (or include "__dict__" if dynamic attributes are required).
G25. In what order are M.__call__, Q.__new__, Q.__init__ invoked for Q() when Q uses metaclass M?
Answer
M.__call__ first (via Q()), which calls Q.__new__, then Q.__init__ if __new__ returns a Q instance. Verified output: meta call, new, init.
G26. A Dockerfile re-installs all dependencies on every code change. Give the fix in three lines of intent.
Answer
Bind-mount pyproject.toml and uv.lock and run uv sync --locked --no-install-project --no-dev first (dependency layer). Then COPY the source and run uv sync --locked --no-dev. Add a cache mount for /root/.cache/uv.
G27. A TaskGroup is used with 10,000 items and Semaphore(20); memory spikes. Why?
Answer
10,000 tasks are created immediately (each with a coroutine frame and captured payload) even though only 20 run. Bound the task creation too: a fixed worker pool pulling from a bounded Queue, or process in chunks. A semaphore alone limits concurrency, not memory.
G28. lru_cache on an async def raises RuntimeError: cannot reuse already awaited coroutine on the second call. Explain and fix.
Answer
The cache stores the coroutine object, which can be awaited only once. Cache the result (async-aware cache library) or cache a Task per key (tasks.setdefault(key, asyncio.create_task(fn(key)))) so concurrent callers share one in-flight call.
G29. Which of these is safe on the free-threaded build without a lock: counter += 1, lst.append(x), if k not in d: d[k] = v?
Answer
Only lst.append(x) is safe as a single operation (built-in containers are internally locked to avoid crashes). counter += 1 is read-modify-write and races. The check-then-set on the dict is a race (duplicate work or lost updates). Use a lock, or dict.setdefault, or per-thread aggregation.
G30. Your FastAPI service gets sporadic 502s from the load balancer under low traffic. First hypothesis?
Answer
Keep-alive mismatch: uvicorn's idle keep-alive (default 5 s) shorter than the LB idle timeout, so uvicorn closes a connection the LB is about to reuse. Set --timeout-keep-alive above the LB's idle timeout.
L3 - Design & trade-offs¶
G31. Choose asyncio, threads, processes, or 3.14t threads for: (a) 5,000 concurrent LLM calls, (b) parsing 2M PDFs, © in-process reranker with a 3 GB model shared by workers.
Answer
(a) asyncio: cheapest per connection, supports cancellation and backpressure. (b) Processes (or a worker fleet), since parsing is CPU-heavy and isolates crashes and memory leaks in native libs. © Free-threaded threads if all dependencies support 3.14t (share the model without pickling), otherwise a separate model-serving process with batched requests. Support with benchmarks and a canary.
G32. Strict vs lax Pydantic validation at three boundaries: public API input, LLM tool arguments, internal service events.
Answer
Public API: lax parsing is friendlier for clients ("3" for ints in query/JSON) but constrain values tightly. LLM tool args: lax plus tight constraints reduces retries, strict for side-effecting tools. Internal events: strict, extra="forbid", frozen, since both sides are code you control and silent coercion hides bugs.
G33. When would you use TypedDict vs dataclass vs Pydantic model?
Answer
TypedDict for JSON-shaped dicts you pass through or **kwargs typing (no runtime cost, no validation). Dataclass (frozen, slots) for internal domain values. Pydantic for untrusted input, LLM structured output, and anywhere you need validation plus JSON Schema.
G34. Where do timeouts belong in an agent turn: per call, per step or per turn?
Answer
All three, with a deadline model. A turn-level deadline (contextvar) bounds worst-case latency, each step uses the remaining budget (asyncio.timeout_at), and per-call timeouts (connect/read) protect against hung sockets. Otherwise N sequential steps x per-call timeouts blow the SLO.
G35. BackgroundTasks vs a durable queue vs asyncio.create_task for post-response work such as audit logging and embedding a new document.
Answer
Audit logging that must not be lost: durable queue/outbox. Embedding a new document: durable job (queue plus worker) with retries and idempotency, since it is slow and must survive deploys. BackgroundTasks for small, best-effort work. Bare create_task needs a held reference and graceful-shutdown draining, and still dies with the process.
G36. Monorepo with a uv workspace vs polyrepo for shared Pydantic contracts.
Answer
Monorepo gives atomic contract changes, one lock, easy refactors. Cost: CI scaling (affected-only builds), ownership granularity, coupled upgrades. Polyrepo isolates cadence but drifts and makes contract changes painful. With fast-moving shared contracts, choose the monorepo plus CODEOWNERS and import-linter boundaries, and publish versioned schemas for non-Python consumers.
G37. Repository/UoW/message bus: when is it over-engineering?
Answer
For CRUD services with little domain logic, one entrypoint and no need to fake infrastructure. Introduce a service layer and ports when logic is duplicated, multiple entrypoints share it, tests are slow due to DB coupling, or infra needs swapping. Add the message bus only when handlers multiply or side effects must be decoupled.
G38. Compare mypy, pyright and ty as the org CI gate.
Answer
mypy: reference behaviour and plugin ecosystem (Django, SQLAlchemy). pyright: fast, strong inference, mature. ty: fastest, strong diagnostics and editor integration, but beta (0.0.x as of Sept 2026), so pin and run advisory first. Choose one CI gate with a pinned version and a ratchet, allow others in editors.
G39. Pydantic models everywhere (API, DB, LLM schema) vs separate models per layer.
Answer
One model couples three change rates: public API contract, persistence, and LLM-facing schema (description tuning, flattening). Separate models with explicit mappers are more code but let each evolve independently. For small internal tools, one model is fine, but design so the split is cheap.
G40. Head vs tail sampling for LLM traces.
Answer
Head sampling is cheap but drops rare failures and slow/high-cost runs. Tail sampling in the Collector keeps errors, slow, high-token, and flagged traces plus a baseline percentage, at the cost of stateful infrastructure. For agent traces (large and costly) tail sampling is usually worth it.
G41. to_thread vs ProcessPoolExecutor vs a separate service for CPU-heavy work called from an async API.
Answer
to_thread helps only if the work releases the GIL (NumPy, Polars, tokenizers-rs) or the build is free-threaded. ProcessPoolExecutor gives true parallelism for pure Python at the cost of pickling and startup, good for medium tasks. A separate service is right when the workload is heavy, needs different scaling/hardware (GPU), or isolation. Decide by payload size, task duration, and scaling needs.
G42. Design the error contract for an LLM extraction endpoint that must return validated JSON.
Answer
Success returns the validated Pydantic model and metadata (model, attempts, tokens). Failure classes: input invalid (4xx), model output invalid after N retries (422 or a domain error with the validation errors summarised and a job ID for review), provider unavailable/rate limited (503 with Retry-After), timeout (504). Never leak raw prompts or provider errors. Emit metrics per class, and send terminal failures to a dead-letter queue.
G43. Explain when Hypothesis beats example-based tests, with two concrete targets in an LLM service.
Answer
When the input space is large and invariants are clear: parsers of LLM output (round-trips, idempotence, never-crash on arbitrary text), chunkers (lossless, bounded size), reducers/state machines (stateful testing). It finds edge cases (empty strings, unicode, boundaries) that hand-written examples miss and shrinks to a minimal repro.
G44. What would make you avoid functools.cache/lru_cache on a method?
Answer
The cache key includes self, so it holds strong references to every instance seen (a memory leak for long-lived caches). Use cached_property for per-instance values, an instance-level cache, or a module-level pure function.
G45. Choose a log/trace content policy for prompts and completions in production.
Answer
Default: metadata only (model, token counts, latency, finish reason, tool names, hashes). Content captured only in a sampled, access-controlled, TTL'd store (errors, low eval scores, opted-in tenants), redacted at the SDK processor level. Document retention and legal basis. Gives debuggability without blanket PII exposure.
G46. Explain trade-offs of uvloop and multiple uvicorn workers for an LLM gateway.
Answer
uvloop reduces loop overhead (2-4x on raw loop microbenchmarks) but matters little when each request waits seconds on the LLM. Multiple workers use more cores but multiply memory and split in-process semaphores/caches, so effective global limits scale with worker count. In Kubernetes prefer one worker per pod plus more replicas, and use a shared limiter (gateway or Redis) for provider quotas.
L4 - Staff-level ambiguity¶
G47. Three teams use poetry, pip-tools and conda. Propose convergence.
Answer
ADR with goals (reproducibility, CI time, security scanning). Pilot uv on one repo and publish before/after numbers. Golden-path template (uv + ruff + chosen type checker + Docker + pre-commit). Keep conda/pixi where native binaries demand it. Migrate opportunistically with a deprecation date and support channel. Metrics: % repos on template, CI p50, time to patch a CVE across repos.
G48. Leadership proposes rewriting the Python agent platform in Go/Java for performance. Respond.
Answer
Reframe to bottlenecks with data. Agent latency is dominated by model and tool I/O, and asyncio overhead per await is microseconds. Typical culprits (blocking calls, no backpressure, CPU-bound serialisation) are fixable in Python (to_thread, process pools, 3.14t, native libs). The AI ecosystem is Python-first. Extract a CPU-heavy component only if profiling proves a ceiling. Document criteria and revisit in an ADR.
G49. 25 services on Pydantic v1 need Python 3.14. Plan the migration.
Answer
The pydantic.v1 shim is unsupported on 3.14+, so v2 migration gates the Python upgrade. Inventory usage, order by risk/traffic (shared libs first), snapshot JSON Schema and dump outputs before migrating for diffing, run the codemod plus manual validator review, and track progress. Timebox with office hours and a compatibility matrix.
G50. Design a shared, typed SDK for defining agent tools that 12 teams use.
Answer
Tools are typed functions. Schemas derive from signatures, Annotated[..., Field(description=...)] and docstrings (single source of truth). Restrict to JSON-representable params with clear registration errors. A typed RunContext[Deps] injected via Concatenate stays out of the LLM-visible schema. Snapshot-test generated schemas, version them, and fail CI on breaking changes. Explicit toolset composition instead of import-time global registries.
G51. Your org has recurring 'event loop blocked' incidents. Build the prevention and detection system.
Answer
Prevention: ruff ASYNC rules in the template and CI, shared async adapters wrapping sync SDKs with sized executors. Detection: loop-lag histogram per service with alerts (p99 > 100 ms), debug mode in staging, py-spy runbook. Enablement: guidance that sync def endpoints run in the threadpool. Metrics: incident count and loop-lag p99 per service over time.
G52. You must adopt free-threaded Python across a 40-service estate. What is your rollout and stop condition?
Answer
Inventory dependency support for cp314t, add a 3.14t CI job failing if the GIL re-enables, pilot 2-3 CPU-bound thread-heavy services, canary with p99, RSS and error-rate guards, keep GIL builds as rollback. Stop condition: dependency gaps in your critical path, unacceptable memory growth, or no measured cost/latency win for the workload class.
G53. Telemetry cost is 30% of the bill after adding agent traces. What now?
Answer
Attribute by signal and service. Tail sample (keep errors, slow, expensive, plus 1-5% baseline), drop health-check and noisy internal spans, enforce metric cardinality budgets, move verbose content to cheap TTL'd storage, shorten debug-log retention, and give teams budgets and dashboards. Run fire drills to confirm on-call can still debug.
G54. Define a testing standard for 12 teams shipping LLM features.
Answer
Ports and fakes for models/tools, table tests for parsers and prompt assembly, Hypothesis for serialisation/reducers, one real-dependency integration suite (testcontainers), recorded provider contract tests refreshed weekly, and a golden eval set run nightly with regression gates. Ship a shared testkit package so compliance is cheap, and review via checklist rather than coverage thresholds.
G55. CI takes 25 minutes and 4% of runs flake, so people rerun until green. Fix the system.
Answer
Measure durations and flake rates from CI history, parallelise (xdist, sharding), remove sleeps/network, session-scope containers, split fast and slow suites, cache uv. Quarantine flakes with owner/ticket/SLA, root-cause top offenders (order dependence, time, ports, shared state), enforce pytest-randomly. SLOs: PR feedback under 8 minutes, flake rate under 0.5%.
G56. Define a 'metaprogramming budget' for a 200-engineer Python org.
Answer
Ladder of least power: functions and composition, decorators, __init_subclass__/descriptors, then metaclasses (design review required). Any magic must preserve signatures and types (ParamSpec, dataclass_transform), have no import-time I/O or hidden global mutation, be testable, debuggable, and documented with failure modes.
Scenario questions¶
Each scenario gives inputs, constraints, and asks for a decision. State assumptions, then answer.
S1. Your FastAPI service calls an LLM and p99 spikes from 3 s to 25 s every few minutes, while p50 is unchanged and CPU is low. Diagnose.
Answer
Look for queueing and tail causes, not average latency. Check: (1) provider 429/5xx with retries and backoff inflating tail (metrics on retry counts, Retry-After), (2) semaphore/connection-pool exhaustion (wait time before the call; httpx pool timeout), (3) event-loop blocking (loop-lag metric, py-spy dump) if other endpoints also spike, (4) a slow tenant sending huge prompts (token histogram by tenant), (5) missing timeouts, so a hung provider connection holds slots until the 60-120 s default. Fixes: per-turn deadline, jittered capped retries honouring Retry-After, separate bulkheads per tenant/priority, load shedding (--limit-concurrency) and fallback model. Verify with traces showing where the 25 s goes (span for semaphore wait vs provider call).
S2. A nightly job embeds 3M chunks using a provider limited to 300 RPM and 1M TPM. It takes 30 hours and sometimes dies at hour 20. Redesign.
Answer
Batch inputs (embedding APIs accept arrays), so RPM stops being the constraint and TPM becomes the limit: 3M chunks x ~400 tokens = 1.2B tokens, at 1M TPM that is 1,200 minutes (20 hours) at best, so negotiate quotas or use multiple deployments/regions. Design: a bounded async pipeline (producer to batch builder to N workers under a token-bucket for TPM/RPM), idempotent writes keyed by content hash so restarts skip done work, checkpointing by shard, retries with jittered backoff, and a dead-letter output for poison chunks. Report progress and ETA. Consider a batch/offline API tier if available at lower cost.
S3. Memory of your agent worker grows 1 GB per day and is OOM-killed weekly. Find and fix.
Answer
Compare RSS with tracemalloc to distinguish Python heap growth from native or fragmentation, then tracemalloc snapshot diffs or memray over a soak test. Likely suspects: unbounded caches (prompt/response dicts, lru_cache(maxsize=None)), conversation histories in globals, stored exceptions retaining tracebacks and big locals, per-request AsyncClients or unclosed streams, functools.cache on methods. Fix with bounded TTL/LRU caches, storing formatted error strings, lifespan-scoped clients, aclosing. Add an RSS alert and a soak test in CI. If it's fragmentation, recycle workers (max_requests) as mitigation.
S4. Users see tokens arrive in one burst instead of streaming in production, but local dev streams fine. Diagnose.
Answer
Buffering between app and client: nginx/ingress proxy buffering (X-Accel-Buffering: no, proxy_buffering off), gzip middleware buffering the stream, a CDN or APM agent buffering, or BaseHTTPMiddleware wrapping the streaming route. Verify with curl -N directly against the pod, then through each hop. Fix at the offending hop and add SSE heartbeats to keep idle timeouts from cutting streams.
S5. Clients disconnect mid-stream, yet your provider bill shows full completions. Quantify and fix.
Answer
Upstream generation continues because the provider stream isn't closed on cancellation. Instrument tokens billed vs tokens delivered to estimate waste. Fix by consuming the provider stream inside the response generator with aclosing/async with client.stream, avoiding detached consumer tasks, and testing disconnect behaviour (fake provider asserting aclose()). Also set max_tokens and per-request deadlines. Track the waste ratio as a metric.
S6. An LLM extraction step returns invalid JSON 6% of the time. Product wants under 0.5%. What do you try, in order?
Answer
1) Use provider structured-output/constrained decoding with the Pydantic-derived schema (adjusted to the supported subset). 2) Simplify the schema (flatter, Literal enums, fewer optionals) and improve field descriptions. 3) Validation retry with error feedback (max 1-2), measuring first-pass vs post-retry validity. 4) Repair pass for known failure classes (fence stripping, trailing commas) before re-calling the model. 5) Escalate hard cases to a stronger model. 6) Dead-letter plus human review for the residue. Measure on a labelled set. Validity is not accuracy, so track field-level accuracy too.
S7. Your 3.14t pilot shows no speedup for a chunking service. Why?
Answer
Check: sys._is_gil_enabled() (an incompatible extension may have re-enabled the GIL), the workload isn't CPU-bound (I/O or lock contention), a global lock or shared mutable structure serialises threads, the threadpool size is capped (AnyIO default 40 tokens, executor max_workers), dependencies release the GIL anyway (so the GIL build was already parallel), or task granularity is too small. Profile with py-spy --gil/--native, and compare to a process pool baseline.
S8. A junior's PR adds requests calls inside async def handlers and passes all tests. Which controls should have caught it?
Answer
Static: ruff ASYNC210/ASYNC rules in CI. Dynamic: a load test with a loop-lag gate, and asyncio debug mode in staging. Review: PR checklist and the golden-path template using a shared async HTTP client. Tests pass because functional tests don't measure concurrency, so add a concurrency smoke test asserting other requests aren't delayed.
S9. Your team's Pydantic model for tool arguments works locally but the provider rejects the JSON Schema in strict mode. What now?
Answer
Strict structured-output modes support a subset (all fields required, additionalProperties: false, limited formats/patterns, restricted recursion, no oneOf variants in some providers). Use the SDK's schema transformation helper or post-process model_json_schema() (inline $defs, set additionalProperties, convert optionals to nullable required). Keep the full-constraint Pydantic model for server-side validation. Snapshot-test the transformed schema per provider and re-validate outputs.
S10. Two engineers propose different ways to share a 2 GB embedding index between 8 worker processes. Compare.
Answer
Fork/COW: simple but refcount writes and GC dirty pages, and forkserver is the 3.14 default on Linux so fork inheritance can't be assumed. Memory-mapped file (NumPy memmap/Arrow IPC/FAISS mmap): shared page cache, no copies, best for read-only. multiprocessing.shared_memory: explicit, works with NumPy arrays. Threads on 3.14t: one copy, no IPC, requires dependency support. A dedicated search service: isolates memory, scales independently, adds a network hop. For read-only vector data, mmap plus a single-process-per-pod service is usually simplest.
S11. Your pytest suite passes locally and fails in CI with 'attached to a different event loop'. What changed?
Answer
Async fixture/test loop scope mismatch (for example a session-scoped async client used from function-scoped test loops), or a different pytest-asyncio mode/default in CI config. Align loop_scope for fixtures and tests (or set asyncio_default_fixture_loop_scope/asyncio_default_test_loop_scope), or create loop-bound resources per test. Pin plugin versions.
S12. You inherit a service using @lru_cache on instance methods and BackgroundTasks for billing events. Prioritise your fixes.
Answer
Billing events first: BackgroundTasks lose work on restart, which is a correctness/revenue risk. Move to a transactional outbox or durable queue with idempotent consumers. Then the lru_cache-on-methods memory leak (and stale-data risk): replace with per-instance or bounded TTL caches keyed by hashable inputs. Add tests for both (crash-and-recover for billing, RSS soak for caches).
S13. You need per-tenant fairness in an LLM gateway so one tenant can't starve others. Design.
Answer
Bulkheads and weighted fair queuing: per-tenant concurrency limits (semaphores) plus a global limit, a token-bucket per tenant for RPM/TPM, and a scheduler picking from per-tenant queues round-robin or weighted by tier. Shed load with 429 plus Retry-After when a tenant's queue exceeds bounds. Track per-tenant queue wait and rejects as metrics (bounded label: tier, not tenant ID, plus per-tenant logs/traces). Global limits across replicas need shared state (Redis) or a gateway.
S14. The p99 of your model_validate step is 40 ms for a 5 MB payload at 200 RPS, blocking the loop. Options?
Answer
Confirm with py-spy. Options in order: validate less (trusted internal data, only needed fields), model_validate_json from bytes, remove Python-level validators, move to a def handler (threadpool) or to_thread so the loop stays responsive (on 3.14t it can also use multiple cores), paginate/stream the payload (NDJSON), or use a leaner parser (msgspec) for that path. Keep contract tests when bypassing validation.
S15. A Python 3.14.0 fleet shows RSS regressions after upgrade. Where do you look?
Answer
Known: 3.14.0-3.14.4 shipped an incremental GC, reverted in 3.14.5 due to memory-pressure reports in production. Check the exact patch version, upgrade to 3.14.5+, compare RSS and GC stats (gc.get_stats) before and after. Also check dependency changes and free-threaded/mimalloc differences if on 3.14t. Verify with a canary before fleet rollout.
S16. Design the local-dev-to-prod path for a Python LLM service so 'works on my machine' disappears.
Answer
pyproject.toml + uv.lock (universal), .python-version pinned, uv sync --locked in dev/CI/Docker, pre-commit (ruff, uv-lock), the same multi-stage Dockerfile for CI and prod, config via pydantic-settings, docker compose (or testcontainers) for Postgres/Redis, recorded provider cassettes for offline tests, a wheel-install smoke test job, and identical base image digests. A make dev or just entrypoint documents it.
Rapid-fire (30)¶
Answer in one or two sentences. Aim for under 20 seconds each.
R1. python -m venv replacement in the uv world?
Answer
uv venv, or just uv sync/uv run (creates .venv implicitly).
R2. Which file records exact resolved versions for a uv project?
Answer
uv.lock (export to pylock.toml or requirements for interoperability).
R3. What does hash(-1) return in CPython?
Answer
-2. -1 is reserved as the C-level error sentinel.
R4. __repr__ vs __str__ audience?
Answer
__repr__ for developers (unambiguous, used in logs and debugging), __str__ for end users. repr is the fallback when __str__ is missing.
R5. What does returning NotImplemented from __add__ trigger?
Answer
Python tries the reflected other.__radd__(self), then raises TypeError if that also declines.
R6. Covariant, contravariant, invariant: which is list[T]?
Answer
Invariant.
R7. What replaces Optional[int] in modern typing?
Answer
int | None.
R8. What does @override (3.12) catch?
Answer
A method that claims to override but doesn't match any base method (for example after a rename in the base class).
R9. Annotated[int, Field(ge=0)]: who reads the metadata?
Answer
Runtime libraries such as Pydantic and FastAPI. Type checkers ignore it and treat it as int.
R10. Which Pydantic API validates a non-model type such as list[int]?
Answer
TypeAdapter(list[int]). Create it once at module level.
R11. What does M(x=True) give for x: int in lax mode?
Answer
x=1. In strict mode it fails.
R12. Pydantic SecretStr: what does it protect?
Answer
Repr and default dumps show **********, so secrets don't leak in logs. Use .get_secret_value() at the point of use.
R13. asyncio.timeout raises what on expiry?
Answer
The builtin TimeoutError (asyncio.TimeoutError is an alias since 3.11).
R14. What does Queue.shutdown() (3.13) do?
Answer
Marks the queue closed: put raises QueueShutDown immediately, and consumers drain remaining items and then get QueueShutDown. It ends worker pools without sentinels.
R15. Which asyncio tool bounds in-flight operations, and which bounds buffered items?
Answer
Semaphore bounds in-flight operations. Queue(maxsize=n) bounds buffered items (and makes put wait, which gives backpressure).
R16. Why keep a reference to create_task() results?
Answer
The loop holds only weak references, so an un-referenced task can be garbage-collected mid-flight.
R17. What replaced on_event('startup') in FastAPI?
Answer
The lifespan async context manager passed to FastAPI(lifespan=...).
R18. Where do plain def FastAPI endpoints run?
Answer
In AnyIO's worker threadpool (default 40 tokens).
R19. Which start method is the Linux default for multiprocessing in 3.14?
Answer
forkserver.
R20. Which module gives an InterpreterPoolExecutor in 3.14?
Answer
concurrent.futures (with concurrent.interpreters from PEP 734).
R21. What flag enables an extension-safe check for the GIL being off in CI?
Answer
Assert sys._is_gil_enabled() is False on the 3.14t job (or run with PYTHON_GIL=0/-X gil=0 to force and surface incompatibilities).
R22. pytest: how do you run only fast tests?
Answer
Mark slow ones (@pytest.mark.integration) and run pytest -m "not integration" with --strict-markers registered.
R23. Hypothesis: what does shrinking do?
Answer
Reduces a failing input to a minimal counterexample, and stores it in the example database for replay.
R24. itertools.batched first appeared in which version?
Answer
3.12 (with strict= added in 3.13).
R25. Why wrap async generators in aclosing?
Answer
Deterministic aclose() on early exit, so upstream streams and finally blocks run promptly instead of at GC/loop finalisation.
R26. cached_property on a class with __slots__?
Answer
Doesn't work (needs an instance __dict__), unless you include __dict__ in slots or use another caching approach.
R27. Which tool would you reach for to see what a live production Python process is doing without code changes?
Answer
py-spy dump/top/record (needs ptrace permission), or in 3.14 python -m asyncio ps/pstree PID for asyncio task trees.
R28. What is Arrow's role between Polars and DuckDB?
Answer
The shared columnar memory format that lets them exchange tables zero-copy.
R29. Why is time.sleep in async def a bug, and which ruff rule flags it?
Answer
It blocks the event loop for all tasks. Ruff ASYNC251 (blocking sleep in async function), part of the ASYNC family.
R30. Metrics label you must never use?
Answer
Unbounded/high-cardinality values (user ID, prompt hash, raw URL with IDs). Use bounded labels and keep identifiers in traces/logs.