interviewpath
  • 0day streak
  • 10total XP
  • Level 1
0/6

Unit 2 · Software engineering · Lesson 8 of 15

Python fundamentals for interviews and research code

Python is the working language of most ML, research and applied-AI code and a common interview language. This topic covers the object model behind aliasing bugs, the iteration and resource-management protocols, the containers interviews lean on, what the GIL means for threads, processes and asyncio, and the NumPy semantics that decide whether array code is correct and fast.
About 5 hFocus mode

Step 1: Learn the ideas

  • Names, objects, identity and mutability
  • Mutable default arguments and late-binding closures
  • Shallow versus deep copies
  • Iterators, generators and generator pipelines
  • Context managers and contextlib
  • Exceptions: hierarchy, chaining and cleanup
  • collections (deque, Counter, defaultdict) and heapq
  • The GIL: threads versus processes versus asyncio
  • asyncio tasks, timeouts and blocking calls
  • Optional free-threaded CPython builds
  • NumPy broadcasting and vectorization
  • NumPy views versus copies and dtype pitfalls

Why it matters

Python is the default language for training code, data pipelines, evaluation harnesses and agent tooling, and a common choice for coding rounds. Interviewers rarely ask trivia; they notice when a candidate mutates a shared list by accident, calls list.pop(0) inside a BFS, or claims threads will speed up CPU-bound Python. In ML and research code the expensive bugs are silent: an in-place edit through a NumPy view, an integer array that overflows, an evaluation loop that blocks the event loop.

Core ideas

Names bind to objects. Assignment never copies; it makes another name refer to the same object. Mutability belongs to the object (lists, dicts, sets, most class instances), not to the variable. Three consequences come up repeatedly:

  • A default argument is evaluated once, when def runs, so def add(x, acc=[]) shares one list across calls. Use None as the default and create the list inside.
  • [[0] * n] * m holds m references to one inner list; build rows with a comprehension.
  • Closures capture variables, not values: lambdas created in a loop all see the loop variable's final value unless you bind it (lambda i=i: i).

copy.copy duplicates only the outer container, so nested objects stay shared; copy.deepcopy copies recursively, handles cycles, and can be expensive.

Iteration. An iterable returns an iterator from __iter__; the iterator produces items from __next__ until it raises StopIteration. A generator function returns a lazy iterator whose state lives in a suspended frame, so a pipeline such as sum(parse(l) for l in f if l.strip()) runs in constant memory. Generators are single-pass: iterating one a second time yields nothing.

Context managers pair setup with guaranteed cleanup. with calls __enter__, runs the body, then always calls __exit__, which receives any exception and suppresses it only by returning a true value. contextlib.contextmanager turns a generator with one yield inside try/finally into a context manager; ExitStack handles a variable number of resources.

Exceptions. Catch the narrowest type you can handle. except Exception does not catch KeyboardInterrupt or SystemExit; a bare except: does. When translating errors at a module boundary, raise ConfigError(...) from err keeps the original cause. finally runs on every exit path, including return.

Containers and costs. Appending to and popping from the end of a list are amortized O(1), but pop(0) and insert(0, x) shift every element; use collections.deque for queues. Counter counts hashable items and offers most_common; defaultdict(list) groups without key checks but inserts a key whenever you index a missing one, even just to read it. heapq keeps a min-heap inside a plain list; push (priority, count, item) tuples so ties never compare the items themselves.

Concurrency. In the default CPython build the global interpreter lock (GIL) lets one thread execute Python bytecode at a time. Threads still help with I/O-bound work, because blocking I/O releases the GIL, and many NumPy operations release it inside compiled loops. CPU-bound pure-Python work needs processes (ProcessPoolExecutor), paying for pickling and duplicated memory. asyncio runs many coroutines on one thread: it scales concurrent waiting, but one blocking call (time.sleep, a synchronous HTTP client, heavy computation) stalls every task, so offload it with asyncio.to_thread or an executor. Keep references to tasks you create, or use asyncio.TaskGroup, because the loop holds only weak references to tasks. CPython 3.13 added an experimental free-threaded build with the GIL disabled; 3.14 made it officially supported but still optional, and the standard build keeps the GIL. Extensions not marked as compatible can re-enable the GIL at import, and single-threaded code runs somewhat slower, so never assume a deployment uses it.

NumPy. Vectorized operations move loops into compiled code. Broadcasting aligns shapes from the trailing dimension; two dimensions are compatible when they are equal or one of them is 1. Basic slicing returns a view that shares memory; advanced (integer or boolean) indexing returns a copy. Fixed-width integer arrays wrap on overflow without raising, and an in-place operation cannot change an array's dtype.

import numpy as np
x = np.arange(6).reshape(2, 3)          # reshape returns a view when it can
row = x[0]                              # view: writing row[0] also changes x
picked = x[[0, 1], [0, 2]]              # advanced indexing: a copy
centered = x - x.mean(axis=0)           # (2, 3) minus (3,) broadcasts per row
np.array([250], dtype=np.uint8) + 10    # array([4], dtype=uint8): wrapped
# x += 0.5 raises: a float64 result cannot be cast into the int64 array

Common misconceptions

  • "Assignment copies." It binds another name to the same object; only explicit copies create new objects, and shallow copies still share nested ones.
  • "Threads never help in Python." They help when threads mostly wait on I/O or run C code that releases the GIL; they do not speed up pure-Python CPU work in the default build.
  • "async makes code faster." asyncio improves concurrency for I/O waits on a single thread. It adds overhead to CPU work, and one blocking call freezes the whole loop.
  • "Slicing always copies." True for lists, false for NumPy arrays: basic slices are views, so in-place edits propagate. Call .copy() when you need independence.
  • "is is a faster ==." is tests identity. Use it for None and sentinels; caching of small integers and some strings makes is on values appear to work by accident.

How to approach it

Rebuild the model with predict-then-run experiments in a REPL rather than rereading. At graduate level, interviewers mostly probe aliasing, the cost of built-in operations and clean generator or context-manager code. Mid-level and senior conversations add choosing a concurrency model for a workload, explaining why an asyncio service stalled, or finding a hidden copy or dtype problem in array code. While coding, state the cost of each container operation as you use it and name the edge cases: empty input, an exhausted iterator, ties in a heap.

Suggested order

  1. 1Read "3.1. Objects, values and types" in the Data model reference, then predict and run five aliasing puzzles: a mutable default, [[0] * 3] * 3, lambdas created in a loop, copy.copy versus copy.deepcopy on nested lists, and is versus ==.
  2. 2Read the Iterators and Generators sections of the Functional Programming HOWTO; write a generator pipeline that streams a large log file and counts events per key in constant memory.
  3. 3Read "3.3.9. With Statement Context Managers" and the contextlib page; write a timing context manager twice (as a class and with @contextmanager) and test that cleanup runs when the body raises.
  4. 4Skim the collections and heapq pages, including the priority-queue notes; solve Group Anagrams with defaultdict, Top K Frequent Elements with Counter and heapq, and Sliding Window Maximum with deque.
  5. 5Read "Coroutines and tasks" and "Developing with asyncio"; simulate twenty I/O calls with asyncio.sleep under a TaskGroup with a timeout, then insert a blocking time.sleep and watch debug mode report the slow callback.
  6. 6Time a CPU-bound function under ThreadPoolExecutor and ProcessPoolExecutor and explain the result; then read the free-threading HOWTO to see what the optional build changes and what it costs.
  7. 7Read the NumPy Broadcasting, Copies and views and Data types chapters; rewrite a pairwise-distance double loop as one broadcasted expression, check np.shares_memory for slices versus fancy indexing, and reproduce a silent uint8 overflow.

Step 2: Check off what you can do

Tap each one you can genuinely do or explain. Each earns 10 XP.

Step 3: Go deeper

  • Python Language Reference: Data modelStart here

    Python Software Foundation (docs.python.org)

    Section: 3.1. Objects, values and types

    Identity, type, value and mutability as the language defines them; continue with 3.3.9 for the protocol behind the with statement.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • Python HOWTOs (official documentation)

    Python Software Foundation (docs.python.org)

    Section: Functional Programming HOWTO: Iterators

    The iterator protocol, generators and itertools; the free-threading HOWTO in the same collection covers the optional build without the GIL.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • The Python Standard Library (module reference)

    Python Software Foundation (docs.python.org)

    Section: collections — Container datatypes

    deque, Counter and defaultdict semantics and costs; open the heapq, copy and contextlib pages listed under this resource for heaps, copying and context-manager helpers.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • asyncio — Asynchronous I/O (Python documentation)

    Python Software Foundation (docs.python.org)

    Section: Coroutines and tasks

    Tasks, TaskGroup, timeouts, cancellation and the weak-reference pitfall; then Developing with asyncio for debug mode and blocking calls.

    Open(opens in a new tab)Link responded, 5 Oct 2026
  • NumPy user guide: NumPy fundamentalsStart here

    NumPy project (numpy.org)

    Section: Broadcasting

    Broadcasting rules, then the Copies and views and Data types chapters for view semantics and dtype pitfalls.

    Open(opens in a new tab)Link responded, 5 Oct 2026

Step 4: Practice

Solve these in deliberately idiomatic Python: defaultdict for Group Anagrams, Counter plus heapq.nlargest for Top K, deque for Sliding Window Maximum, a size-k heap for Kth Largest, two heaps with negated values for the running median, OrderedDict (then a hand-built doubly linked list) for LRU Cache, bisect for the time-based store, and threading.Event, Lock or Semaphore for the two concurrency problems. Then do three exercises outside LeetCode: write a context manager that times a block and always logs, even when the block raises; write a generator that yields fixed-size batches from any iterable without materializing it; and vectorize a pairwise-distance loop in NumPy, confirming with np.shares_memory which intermediates are views.

Record attempts on each problem’s page (+15 XP, +10 more when solved on your own).

Step 5: Test yourself

Answer each one out loud or on paper before you finish the lesson.

  1. Question 1Why does a mutable default argument accumulate state across calls, and what is the idiomatic fix?
  2. Question 2What does copy.copy share with the original that copy.deepcopy does not?
  3. Question 3What happens when you iterate the same generator twice?
  4. Question 4When does __exit__ suppress an exception?
  5. Question 5Why is collections.deque preferred over a list for a BFS queue?
  6. Question 6Why can threads speed up a downloader but not a pure-Python prime sieve in the default CPython build?
  7. Question 7Is x[::2] a view or a copy for a NumPy array? What about x[[0, 2]]?
  8. Question 8What value does a uint8 element holding 250 have after adding 10?

You’re done when you can

  • Predict the output of five unseen aliasing or closure puzzles correctly and explain each with the name-binding model.
  • Write a correct context manager with contextlib.contextmanager from memory, including cleanup when the body raises.
  • Solve two collection-heavy problems in idiomatic Python in under 25 minutes each, stating the cost of every container operation used.
  • Explain in under two minutes when to use threads, processes or asyncio, including what the GIL does and does not prevent.
  • Rewrite a nested Python loop as broadcasted NumPy and say which intermediate arrays are views and which are copies.

Finish the lesson

How confident do you feel?

Lesson complete!

+50 XP

Reviews will come up on the Practice tab so it sticks.

Unlocks next: Training loops, optimizers and model debugging, PyTorch and autograd fluency

Your notes and study time

Log study time