Course
Python — The Complete Course
From language fundamentals to concurrency, the standard library, and interviewing in Python.
Language course · 7 units
Unit 1: Unit 1 — Syntax, Types & Built-ins
Python is dynamically typed with strong typing; the interpreter, not the compiler, enforces types. Know the built-in sequence/set/mapping types, comprehensions, and the fact that everything is an object (even functions).
Teach: What makes Python code idiomatic
Idiomatic Python reads like prose. Prefer a list comprehension to append-loops, use enumerate() when you need indices, and reach for dict.get(k, default) / collections.Counter for counting. Worked: count word frequencies of ['a','b','a'] -> Counter -> {'a':2,'b':1}. O(n) build. Note: strings are immutable; joining is linear.
Exercises
- FizzBuzz in idiomatic Python — Print 1..n, replacing multiples of 3 with Fizz, 5 with Buzz, both with FizzBuzz.
- Valid Anagram (idiomatic) — Return True if two strings are anagrams, False otherwise.
- Rotate Array k steps — Rotate an array to the right by k steps in-place and O(1) space.
Practice problems
- FizzBuzz
- Two Sum (dict)
- Group Anagrams
Unit 2: Unit 2 — OOP: classes, dataclasses, dunder methods
Python OOP is protocol-based: behaviour comes from dunder methods (__eq__, __lt__, __hash__, __repr__) and classes are first-class objects. Prefer dataclasses for value objects, Protocol for duck-typed interfaces, and slotted classes for tight loops.
Teach: dataclasses + value semantics
A Point dataclass with __eq__ auto-generated means p1 == p2 compares fields. If you put a dataclass in a set/dict you must ALSO define __hash__ — which dataclass(frozen=True) gives you automatically. Frozen = immutable = safely hashable.
Exercises
- Rational number class — Implement a Rational with + and equality, normalized, immutable.
- Design a thread-safe counter using a class with a lock — A Counter class supporting inc() and value(), safe under threads.
Practice problems
- Design LRU Cache (OrderedDict)
- Design a Stack with getMin (tuples)
Unit 3: Unit 3 — Concurrency: threads, asyncio, GIL reality
Python threads are fine for I/O (GIL releases around blocking calls) but not for CPU-bound work — use multiprocessing or asyncio for I/O-bound. Know: GIL, ThreadPoolExecutor, asyncio.run/await, and the awaitable primitive. Interviewers probe 'which tool for which bound'.
Teach: The GIL and the I/O vs CPU distinction
The GIL lets only one thread run Python bytecode at a time, but it is released during blocking I/O (socket, file) — so many threads genuinely parallelize I/O. For CPU-bound Python, threads give no speedup; use multiprocessing or a C extension. State this distinction explicitly: 'this is I/O-bound so threads help'.
Exercises
- Fetch N URLs concurrently — Given N URLs, fetch them concurrently and return (url -> status).
- asyncio: run 3 sleeps concurrently — Show concurrent (not serial) execution of 3 async sleeps and measure it takes ~max not ~sum.
Practice problems
- StampedLock / rate limiter with a lock
- Thread-safe LRU
Unit 4: Unit 4 — Standard library & Python interviewing
Interviewers love to see you reach for the right stdlib: collections (Counter, defaultdict, deque, OrderedDict), heapq, bisect, itertools, functools (lru_cache), re. Knowing these signals real-world fluency and keeps your solutions compact and correct.
Teach: engineering speed for the standard patterns
Top-k in one line: heapq.nlargest(k, stream). Sliding-window max: a deque holding candidate indices, O(n). Memoized recursion: @lru_cache(None) on a pure function. Knowing these is the difference between 'works' and 'elegant, O(n), no bugs'.
Exercises
- Sliding Window Maximum — Return the max of every k-sized window.
- Memoized Fibonacci — Compute fib(n) with memoization, then bottom-up O(1) space.
Practice problems
- Top K Frequent (heapq)
- Merge Intervals (sort)
- LRU via OrderedDict
Unit 5: Unit 5 — Data structures & the collections module
Interview Python leans on the standard data structures and the collections module. Know when each shines: list (ordered, O(1) append), dict (O(1) lookup), set (O(1) membership, dedupe), deque (O(1) both ends), heapq (min-heap ops), bisect (sorted insertion/search), defaultdict/Counter (counting), OrderedDict (insertion order + move_to_end). The right container often turns an O(n^2) solution into O(n log n) or O(n).
Teach: choosing the right container
Ask: what is the dominant operation? If you need the smallest/largest repeatedly → heap. If you need O(1) both-ends append/pop → deque. If you need a running sorted order with insert/search → bisect on a list (or a heap if you only need the extreme). If you group items by a key → defaultdict. If you count occurrences → Counter. Worked example — Top K Frequent Elements: count with Counter (O(n)), then heapq.nlargest(k, counts.items(), key=itemgetter(1)) → O(n log k). State the space cost of each container.
Exercises
- Sliding Window Maximum — Return the max of every window of size k over an array.
- K Closest Points to Origin — Return the k points closest to the origin.
- Group Anagrams — Group strings that are anagrams of each other.
Practice problems
- Top K Frequent (heapq + Counter)
- Sliding Window Maximum (deque)
- Merge K Sorted Lists (heapq)
Unit 6: Unit 6 — Functional Python: generators, itertools, functools
Python is multi-paradigm; interviewers probe whether you can write clear functional-style code. Generators (yield) give lazy, memory-efficient iteration and are the backbone of pipelines. itertools (chain, product, combinations, permutations, groupby, accumulate) and functools (reduce, lru_cache, partial, cmp_to_key) replace hand-rolled loops. Know when a generator beats a list: when you stream, when you compose, when memory matters.
Teach: generators are lazy pipelines
A generator function with yield produces values on demand — it does not build the whole sequence. This is O(1) memory for streaming. Compose generators: (x*x for x in range(n)) is lazy; [x*x for x in range(n)] is eager. Worked example — first n primes: a generator yields primes forever; itertools.islice takes the first n. Memory stays O(primes so far) instead of O(whole sieve). Mention lru_cache for memoized recursion and cmp_to_key to sort with a comparator.
Exercises
- Powerset via itertools — Return all subsets of a list.
- Memoized Fibonacci with lru_cache — fib(n) with memoization, then O(1) space bottom-up.
- Running product via accumulate — Return an array where out[i] = product of all elements except i, without division.
Practice problems
- Powerset (itertools.combinations)
- LRU cache (functools)
- Group consecutive runs (itertools.groupby)
Unit 7: Unit 7 — Performance: profiling, __slots__, C extensions & numpy
Staff-level Python means reasoning about where time and memory actually go. Profile before optimizing: cProfile / timeit identify the hot path; the GIL limits CPU-bound threads (use multiprocessing or a C extension); __slots__ cuts per-instance memory; numpy vectorizes numeric loops; C extensions (ctypes/cffi/Cython) escape Python overhead for the critical inner loop. Always quantify: measure before and after, and state the complexity.
Teach: measure first, optimize the hot loop
Never guess the bottleneck. Run cProfile on the workload, find the function with the most cumulative time, and optimize THAT. Common wins: replace a Python loop with a numpy op or a comprehension; move the inner loop into a C extension; cache repeated work; reduce allocation. For CPU-bound parallelism the GIL blocks threads — use multiprocessing or a C extension. State the before/after numbers.
Exercises
- Explain __slots__ and when it helps — When would you add __slots__ to a class, and what does it cost?
- Why doesn't threading speed up CPU-bound work? — A CPU-bound loop is slow with 8 threads. Diagnose and fix.
- Vectorize a sum-of-squares loop — Given a large list, compute sum(x*x for x in xs) faster.
Practice problems
- Profile a hot function with cProfile
- Vectorize a numeric loop with numpy
- Design a __slots__ value object