Skip to content

Python roadmap

Ordered runs of problems, and nothing else: no reading, no progress bar, just the sequence. For a syllabus that interleaves written explainers and can be worked through with friends, see tracks.

Python: the full path

Every python track in course order, grouped into the syllabus's four waves. Foundations first, then the design-heavy tracks, then systems, then depth.

195 problems

1 Reading a mypy error: codes, reveal_type, assert_type Easy 2 Placement 1/6: generics and variance Medium 3 Placement 2/6: Protocol vs ABC Easy 4 Placement 3/6: exception boundary design Medium 5 Placement 4/6: generators are consumed once Easy 6 Placement 5/6: async cancellation Hard 7 Placement 6/6: import-cycle detection Medium 8 Modernise legacy annotations Easy 9 Flatten a union at runtime Easy 10 Coerce to int at an untyped boundary Medium 11 assert_never and the event that was never handled Medium 12 Literal fit modes and the Final default Medium 13 A @final rate limiter Easy 14 ClassVar, field(), and the counter that must reset Easy 15 Amortise cents without mixing up the units Medium 16 A recursive Json alias, depth and flatten Medium 17 Generic partition and chunk Medium 18 A generic Result[T, E] Medium 19 Bounds versus constraints: clamp and dedupe Hard 20 Why list[Dog] is not a list[Animal] Medium 21 Producer, Consumer, Cell: variance by member set Hard 22 Accept broad, return narrow: group_by and merge_counts Medium 23 Retry, and the predicate that reads backwards Hard 24 Self and the fluent query builder Medium 25 @override and the notifier that stopped notifying Easy 26 Narrowing without the truthiness trap Medium 27 TypeIs, TypeGuard, and a predicate that lies Hard 28 runtime_checkable is presence, not a signature Hard 29 Modernisation kata: inventory.py, 2019 to 2026 Hard 30 Counter: top n-grams with deterministic ties Easy 31 defaultdict: group rows, hand back a plain dict Easy 32 deque: rolling median and the maxlen narrowing Medium 33 ChainMap: resolve config and keep the provenance Medium 34 itertools: take n across chunks without over-consuming Easy 35 groupby: summarise rows without losing half of them Medium 36 batched: page a stream, and validate before you yield Easy 37 accumulate and pairwise: max drawdown in one pass Easy 38 cache: memoised edit distance behind a validated wrapper Medium 39 lru_cache on a method: fix the unbounded instance leak Hard 40 cached_property: compute once, release with the instance, invalidate on demand Medium 41 singledispatch: a JSON encoder callers can extend from outside Hard 42 partial: a signature-preserving retrier, and the 3.14 descriptor change Medium 43 total_ordering: SemVer, where pre-release sorts before release Medium 44 contextmanager: temporarily patch an attribute, and put it back Medium 45 ExitStack: all-or-nothing acquisition of N resources Hard 46 nullcontext and suppress: use the stream you were given, close only what you opened Easy 47 aclosing: release the connection on break, error and cancellation Hard 48 pathlib: safe_join, or how to not serve /etc/passwd Easy 49 walk: prune the subtree, not just the results Medium 50 StrEnum: migrate a module of string constants without breaking callers Medium 51 Flag: a permission mask that prints, iterates and refuses bad bits Medium 52 tomllib: turn dict[str, Any] into a typed config, or a named error Medium 53 datetime: reject naive input, and find the next local wall clock Hard 54 heapq: a priority queue that is FIFO among equal priorities Medium 55 Exception hierarchies as API design Medium 56 Exception chaining: raise ... from Medium 57 Bare except, blind except Exception, and where to catch Medium 58 ExceptionGroup, split() and except* Hard 59 Failure aggregation over batches Medium 60 Retry, backoff, and retryable vs terminal Medium 61 Deprecating public API: @warnings.deprecated Medium 62 Typing the error path: Result, attempt and Never Hard 63 Dataclasses: slots, frozen, order Easy 64 asdict() and astuple(): the deep-copy tax Hard 65 Serializing dataclasses properly: round-trips, paths and schema versions Hard 66 Converters and validate-on-assignment: what attrs buys you Medium 67 dataclass_transform (PEP 681): making your own decorator type-checkable Hard 68 @dataclass is a code generator: what it emits Easy 69 eq, hash, and the eq/frozen/unsafe_hash truth table Hard 70 replace(), copy.replace(), and the mypy gap between them Hard 71 field(): the seven knobs that change generated behaviour Medium 72 frozen=True: what it buys, what it costs, and what it does not do Medium 73 Writing generic code over dataclasses under --strict Hard 74 kw_only, KW_ONLY and the inheritance ordering wall Medium 75 The mutable-default trap — and why the guard is weaker than you think Easy 76 NamedTuple: when a record should be a tuple Medium 77 Parse at the boundary, dataclasses in the core Hard 78 __post_init__ and InitVar: parameters that are consumed, not stored Medium 79 Runtime introspection after PEP 649: Field.type is not a type Hard 80 Generic dataclasses, Self, and the covariance question Hard 81 order=True, the 3.13 __eq__ change, and orderings that are not tuples Hard 82 __slots__, part 2: the eight things it breaks Hard 83 Protocols: what structural subtyping actually asserts Easy 84 The protocol attribute invariance trap Hard 85 abc.ABC: abstract properties and classmethods, in the right order Easy 86 @runtime_checkable checks names, not types Medium 87 Subclassing a Protocol: what Python will and will not stop you doing Medium 88 Callback protocols: closing the Callable[..., Any] hole Medium 89 Variance in generic protocols Hard 90 @overload: one implementation, several honest signatures Hard 91 ParamSpec and Concatenate: decorators that keep the signature Hard 92 TypeVarTuple: generics over an arbitrary number of types Hard 93 TypedDict: Required, NotRequired and the ReadOnly unlock Medium 94 Unpack[TypedDict]: typing **kwargs precisely Medium 95 Annotated: metadata the type checker deliberately ignores Medium 96 Self-types, and restricting a method to one parameterisation Medium 97 Auditing the Any surface of a module Medium 98 Replacing cast() with a TypeIs predicate Medium 99 Suppression hygiene: planning the minimum set of type: ignore Easy 100 __all__, re-export rules, and the accidental public API Medium 101 Writing a minimal .pyi for an untyped dependency Hard 102 Fixture setup and teardown order Medium 103 A rate limiter with an injected clock Medium 104 Reimplementing pytest's parametrize expansion Medium 105 Four properties for one codec Hard 106 Minimal counterexamples for an LRU cache Hard 107 Mutation testing: scoring a test suite Hard 108 A typed factory fixture Hard 109 Detecting leaked tasks and forcing a race Medium 110 Async iterators: the paginator you can actually close Medium 111 Batched async stream: never yield inside a cancel scope Hard 112 Bounded concurrency and backpressure Hard 113 Lazy record batches: what laziness moves Medium 114 Iterator, Iterable, Generator, Sequence — and what the annotation promises Medium 115 Streaming pipelines end to end Hard 116 The streaming sort-merge join Hard 117 The single-consumption contract Medium 118 zip(strict=True) and the silent-truncation bug Easy 119 BoundedCounter: check-then-act under 64 threads Medium 120 BoundedBuffer: a blocking queue from one Condition Medium 121 run_workers: results, errors and a join deadline Medium 122 map_with_failures: partial success on a thread pool Medium 123 run_sync: the sync boundary a library should expose Easy 124 A Fetcher Protocol three implementations can satisfy Medium 125 BackgroundTasks: strong references, and a drain that raises Medium 126 fetch_all: structured fan-out with TaskGroup Medium 127 with_cleanup: bounded cleanup on every exit path Hard 128 Deadline: one budget, many nested scopes Medium 129 process_stream: a bounded pipeline that cannot hang Medium 130 asyncify: a ParamSpec wrapper that does not block the loop Medium 131 Service.serve: a shutdown that is bounded and returns Hard 132 request_context: an id that survives tasks and threads Medium 133 Capstone: a typed, cancellable, observable worker pool Hard 134 Which copy of the package wins? Easy 135 What goes on sys.path first Medium 136 What does this module actually export? Hard 137 Every import cycle, deterministically Hard 138 The layering contract, as a test Hard 139 A rate limiter you can test without sleeping Hard 140 Own what you create, borrow what you are given Medium 141 Forbidden import chains, with the route Hard 142 Does this version satisfy this specifier? Medium 143 Flattening a dependency group Medium 144 Parsing an entry-point object reference Medium 145 Auditing a library for import-time side effects Hard 146 The __exit__ contract: return the resource, propagate the exception Medium 147 Walking an object graph with gc.get_referents Medium 148 A registry that does not prevent collection Medium 149 Identity vs equality: fix the planted `is` Easy 150 Selective deep copy: one memo, and the sharing you meant to keep Medium 151 deep_sizeof: count every distinct object exactly once Medium 152 find_growth: measure retention, and stop the tracer on every path Medium 153 Order-preserving dedupe and a real sliding window Easy 154 render_report: join for str, bytearray for bytes Medium 155 Accidental quadratics: an index-backed join and a streaming top-k Medium 156 timeit: take the minimum, and put the collector back Medium 157 hot_functions: the column you sort by is the answer you get Medium 158 Moving average twice: naive, cumulative-sum, and knowing which to use Medium 159 Sizing the Pool: process_cpu_count, cgroups, Oversubscription Medium 160 Process Is a Typing Black Hole — Prefer Executors Easy 161 Migrating a Fork-Dependent Codebase to Forkserver Hard 162 Free-Threading: Writing Code That Survives It Hard 163 Typing Multiprocessing: Where the Annotations Lie to You Medium 164 Zero-Copy IPC With Pickle Protocol 5 Out-of-Band Buffers Hard 165 Graceful Shutdown of a Process Pool Hard 166 What Actually Crosses a Process Boundary Medium 167 Queue Mechanics: The Feeder Thread and the Join Deadlock Hard 168 Manager and Proxies: Convenience at 275x the Price Medium 169 shared_memory, the Resource Tracker, and track= Hard 170 The Three Start Methods, and What 3.14 Changed Medium 171 Subinterpreters: PEP 734 and the Typed Queue Medium 172 Worker Recycling: max_tasks_per_child Without the Silent Downgrade Medium 173 Parse, don't validate: the boundary returns a narrower type Medium 174 Typed settings: parse the environment once, report every problem Medium 175 Injection: the list form, and a SQL type your checker enforces Hard 176 The configuration boundary in practice: file, environment, argv, one Config Medium 177 Logging in libraries: the hierarchy, the effective level, and whose configuration it is Medium 178 Structured logging: events, not sentences Hard 179 PEP 686: UTF-8 by default, and the migration hazard Hard 180 The f-string format specification mini-language Easy 181 PEP 701: what f-strings can finally do in 3.12 Medium 182 Self-documenting f-strings: f"{x=}" Easy 183 match/case fundamentals: literal, capture, wildcard Medium 184 Class patterns, __match_args__ and an expression simplifier Medium 185 Exhaustive matching with assert_never Hard 186 Guards, OR patterns, AS patterns and the binding rules Medium 187 Sequence and mapping patterns: the two rules everyone gets wrong Medium 188 Positional-only `/` and keyword-only `*` Medium 189 Template strings (t-strings) and interpolation safety Hard 190 The walrus operator, tastefully Medium 191 Capstone: the upload that cannot be misused Hard 192 Capstone: break the monolith, then enforce the layering Hard 193 Capstone: an immutable domain model that survives review Hard 194 Capstone: a fully typed, cancellable, observable async worker pool Hard 195 Capstone: five defects, and which tool would have found each one Hard