Interview Bank · 2026
Idioms, OOP, generators, the GIL, and the gotchas that separate "I use Python" from "I know Python." Say each answer aloud before you reveal it.
Click a card. Answer first, in one breath, then reveal.
list vs tuple?is vs ==?== compares values; is compares identity (same object in memory).yield — produces values one at a time, no list in memory.*args vs **kwargs?*args = extra positional args (tuple); **kwargs = extra keyword args (dict).[...] builds the whole list now; (...) yields lazily, item by item.self?copy.deepcopy) duplicates all the way down.with — runs setup on enter, guaranteed teardown on exit (e.g. closing a file).@staticmethod vs @classmethod?cls (the class) and can build/alter class-level state.Automatically, via reference counting (each object tracks how many references point to it; at zero it's freed) plus a cyclic garbage collector for reference cycles that counting alone can't free. You don't free() anything; CPython does it for you.
A list holds arbitrary Python objects and is flexible but slow for math. A NumPy array is a typed, contiguous block — vectorised operations run in C, far faster. It matters the moment you do numerical work at scale (e.g. the embedding vectors in your RAG pipeline).
Immutable objects can't change after creation: int, float, str, tuple, frozenset. Mutable ones can: list, dict, set. This drives behaviour: strings return new objects on "change", and mutable objects passed to functions can be modified in place.
else/finally for?try runs risky code; except catches specific errors; else runs only if no exception was raised; finally always runs (cleanup), exception or not. Catch specific exceptions, not bare except:.
append() and extend()?append(x) adds one item (even if it's a list — it nests). extend(xs) adds each item of an iterable. [1,2].append([3,4]) → [1,2,[3,4]] vs extend → [1,2,3,4].
@dataclass auto-generates __init__, __repr__, __eq__ from typed fields — boilerplate-free data holders. They're the mental model for Pydantic models, which add validation on top.
A virtual environment (python -m venv .venv) isolates a project's packages; pip install adds them; pip freeze > requirements.txt pins them. In 2026, many teams use uv (a fast Rust-based installer/resolver) as a drop-in speed-up.
if __name__ == "__main__": do?It runs the block only when the file is executed directly, not when it's imported as a module. __name__ is "__main__" for the entry script, otherwise the module's name. Lets a file be both runnable and importable.
The Global Interpreter Lock lets only one thread execute Python bytecode at a time in CPython. So threads don't give you parallel CPU work — but they're fine for I/O-bound work (network, disk), where threads wait anyway. For CPU-bound parallelism, use multiprocessing (separate processes, separate GILs) or async for I/O. (2026: the experimental free-threaded build is changing this — see What's New.)
async/await actually work? theoryIt's cooperative concurrency on a single thread. An async def is a coroutine; await yields control back to the event loop while waiting on I/O, letting other coroutines run. No threads, no GIL fight — one worker handles thousands of waiting requests. It speeds up I/O-bound code, not CPU-bound.
Duck typing: "if it walks like a duck…" — Python cares what an object can do, not its class. EAFP ("easier to ask forgiveness than permission") = try the operation, catch the error — the Pythonic style. LBYL ("look before you leap") = check first with if. EAFP is usually preferred and avoids race conditions.
The order Python searches base classes for a method, used to resolve multiple inheritance. It uses the C3 linearisation algorithm; you can inspect it with ClassName.__mro__. super() follows the MRO, not just the literal parent.
An iterable has __iter__ and can produce an iterator (lists, strings). An iterator has __next__ and yields values one at a time, holding position. for calls iter() on the iterable, then next() until StopIteration. Generators are iterators.
Default values are evaluated once, at definition time, and shared across calls. def f(x, items=[]): reuses the same list every call, so it accumulates. Fix: def f(x, items=None): items = items or []. A classic interview filter.
0.1 + 0.2 == 0.3 return, and why? trickyFalse — floats are binary approximations, so 0.1 + 0.2 is 0.30000000000000004. Compare with math.isclose(), or use decimal.Decimal for money.
for i in range(3): funcs.append(lambda: i) then call them. trickyAll print 2 (the final i) — closures capture the variable, not its value at creation (late binding). Fix with a default arg: lambda i=i: i.
a = b = [] — then a.append(1). What's b? trickyb is [1] — a and b are two names for the same list. Assignment binds names to objects; it doesn't copy. Same trap with mutable function arguments.
is with small ints misleading? trickyCPython caches small ints (−5…256), so a = 256; b = 256; a is b is True, but 257 is 257 can be False across statements. Never use is for value comparison — only for None/singletons.
The iterator's internal index gets out of sync with the changing length, so you skip elements or hit IndexError. Iterate over a copy (for x in items[:]) or build a new list with a comprehension.
Python 3.13 shipped an experimental free-threaded build (PEP 703) that can run without the GIL, enabling true multi-core threading; 3.14 advanced it toward officially supported. It's opt-in and still maturing — single-thread code can be a bit slower — but it's the biggest concurrency shift in Python's history. Knowing it exists signals you follow the ecosystem.
CPython 3.13 added an experimental JIT compiler (PEP 744) that compiles hot code paths to machine code at runtime. Early days and modest gains so far, but it's the foundation for future speedups. Pair it in your answer with the free-threading work as "CPython getting seriously faster."
3.12 introduced cleaner generic type-parameter syntax (def first[T](xs: list[T]) -> T:) and the type alias statement. 3.14 added t-strings (template strings, PEP 750) for safe, deferred string processing, and made annotations lazily evaluated by default. Also: modern tooling — uv for envs/installs and ruff for lint+format — is now the de-facto fast standard.