skip to content

Python

14 roadmaps1,717 questionsupdated

Python as interviewers actually probe it: the value and container semantics you reason about daily, the function/OOP/decorator machinery, async and the GIL, gradual typing and error handling, and the stdlib, performance, testing, and packaging habits that make code shippable. It is the default language for backend services, data pipelines, and AI/ML work, so almost every stack asks about it.

on this pageshow

guide

overview

~2 min

Python interviews check whether you can predict what the interpreter will do, not whether you can write a loop. The opening minutes usually go to the object model under everyday code: names bound to objects, which types change in place, and why an aliased list or a default argument surprises people. From there the conversation moves to the machinery that makes code read as Python — functions as values, closures, decorators, generators, and the special methods a class implements to join in with the syntax — and then to concurrency, where the GIL is the first question and choosing between threads, processes and asyncio for a given workload is the real one. Senior rounds widen to what keeps a codebase shippable: gradual typing, the unhappy path, measuring before optimizing, packaging, and how a Python process behaves under a real operating system. The hub follows those seams. The language core is [Core Types and Syntax](/topics/lang-python-core-types), [Control Flow Constructs](/topics/lang-python-control-flow), [Collections and Data Structures](/topics/lang-python-collections), [Functions and Comprehensions](/topics/lang-python-functions-comprehensions), [OOP and Classes](/topics/lang-python-oop) and [Decorators and Generators](/topics/lang-python-decorators-generators). Correctness is [Errors and Context Managers](/topics/lang-python-errors-context), [Type Hints and Static Typing](/topics/lang-python-typing) and [Testing Idioms](/topics/lang-python-testing). Running work at once is [Concurrency and Async](/topics/lang-python-concurrency). The everyday batteries are the [Standard Library](/topics/lang-python-stdlib), with [Security and Safe APIs](/topics/lang-python-security) covering the calls whose defaults bite. The working-engineer layer is what happens after the source is correct: the [Interpreter Execution Model](/topics/lang-python-runtime), [Performance and Memory](/topics/lang-python-performance-memory), [Packaging and Environments](/topics/lang-python-packaging-envs), [Live Process Diagnostics](/topics/lang-python-diagnostics) and [Unix Runtime Behavior](/topics/lang-python-process). Learn the core first and in roughly that order: nearly every later answer assumes you can say which object a name refers to and whether an operation copies it. Add errors and context managers early, because the unhappy path comes up in every round. Leave asyncio until closures, generators and the iterator protocol feel routine — its questions look hard mainly when those are shaky. Typing and testing pay off from mid-level up; the runtime, diagnostics and Unix sections carry the most weight in backend and platform roles, where a question starts from a process that is slow, stuck or dead.

primer

### Names point at objects Assignment never copies. A name is a reference, a call binds each parameter to the object the caller passed, and whether a change shows up elsewhere depends on whether that object is mutable. Identity versus equality, hashability, shallow versus deep copies and the default-argument trap are this one idea seen from different sides, so interviewers open with it. ### Functions and classes are objects too Functions can be stored, passed and returned; a closure keeps the variables it captured; a class is an object created at runtime by executing its body. Decorators, `functools`, class decorators and metaclasses are ordinary code operating on those objects, which means a question about `@` syntax is really a question about when things are evaluated. ### Behaviour comes from protocols, not ancestry Python asks what an object can do rather than what it inherits from. `len()`, `for`, `with`, `in`, `+` and attribute access all dispatch to special methods, and any class that defines the right ones takes part. Iteration, context management, descriptors and the container ABCs are all protocols; static typing mirrors the stance with `Protocol`, where a class matches by shape. ### Laziness is a first-class tool Iterators hand out one item per request, generators suspend mid-function, and much of the standard library consumes and returns iterators rather than lists. Knowing where a pipeline stays lazy and where it quietly materializes decides both peak memory and whether the data can be read a second time. ### One lock shapes the concurrency story In the default CPython build, a global lock serializes bytecode execution, so threads help with waiting but not with pure-Python computing. That drives the model choice: threads for blocking I/O, processes for CPU-heavy work, asyncio for many concurrent waits on one thread with explicit `await` points. Free-threaded builds change the arithmetic, and interviewers increasingly ask what they break. ### Types are checked by tools, not the interpreter Annotations are metadata; a separate checker reads them and reports mismatches before the code runs. The system is gradual — `Any` turns checking off wherever it flows — so the craft lies in where to be precise, how narrowing follows control flow, and when a Protocol fits better than a base class. ### Failure and cleanup are explicit Exceptions form a class hierarchy, and the type you catch decides what escapes: a handler that is too broad swallows interrupts and bugs alike. `with` ties cleanup to a block so it runs on every exit path, which matters because CPython's prompt freeing of unreferenced objects is an implementation behaviour, not a language promise. ### CPython is the implementation you are asked about Most performance, memory and runtime questions assume CPython: reference counting backed by a cycle collector, source compiled to bytecode and run in frames, C code behind the builtins. Making Python fast usually means pushing the hot loop into C — a builtin, a standard-library routine, a native extension — after a profiler has said where the time actually goes.

Name binding
The link between an identifier and an object in a namespace; assignment, import, def, class and for all create bindings, and none of them copies the object.
Mutability
Whether an object's value can change after creation; lists, dicts and sets can, while ints, strings, tuples and frozensets cannot.
Hashable
Having a hash that never changes during the object's lifetime and an equality consistent with it; required of dict keys and set members.
Special method
A method named with double underscores on both sides that the interpreter calls to implement an operator, a builtin or a statement; also called a dunder method.
Closure
A nested function together with the variables it captured from an enclosing function, which stay alive as long as the inner function does.
LEGB rule
Python's name lookup order: local, enclosing function, global, then builtins. Assigning to a name makes it local unless declared global or nonlocal.
Decorator
A callable applied to a function or class at definition time; whatever it returns replaces the original under the same name.
Iterator protocol
The contract behind for loops: an iterable produces an iterator, and the iterator returns successive items until it signals exhaustion by raising StopIteration.
Generator
A function containing yield; calling it returns an iterator that runs the body in steps, pausing at each yield with its local state intact.
Descriptor
An object stored on a class that defines get, set or delete hooks and so controls attribute access; properties, methods and slots are built on it.
Method resolution order
The linearized list of classes, computed with the C3 algorithm, that attribute lookup searches and that super() follows; abbreviated MRO.
Metaclass
The class of a class, which controls how class objects are created; type is the default.
Dataclass
A class whose annotated fields let a decorator generate the initializer, repr and equality, plus ordering, hashing and immutability on request.
Context manager
An object that sets something up when a with block is entered and tears it down when the block exits, however it exits.
GIL
The global interpreter lock of standard CPython builds: a mutex a thread must hold to run bytecode, which serializes pure-Python execution across threads.
Free-threaded build
A CPython build without the global interpreter lock, letting threads execute Python code in parallel, at some single-threaded cost and with extension compatibility caveats.
Event loop
The asyncio scheduler that runs coroutines on one thread, resuming each when the I/O, timer or future it awaits is ready.
Coroutine
The object an async def function produces; it makes progress only when awaited or wrapped in a task, and gives up control at each await.
Type checker
A static analysis tool, such as mypy or pyright, that reads annotations and reports type errors without running the program.
Protocol
A typing construct for structural subtyping: any class with the listed methods and attributes satisfies it, with no inheritance required.
Reference counting
CPython's primary memory management: each object counts the references to it and is freed when the count reaches zero; a separate collector reclaims cycles.
Virtual environment
An isolated directory with its own interpreter entry point and site-packages, so each project installs dependencies without touching the system Python.
Wheel
The built distribution format for Python packages: an archive installed by unpacking, so the target machine needs no build step.

The sections form layers. At the bottom is the data model — [Core Types and Syntax](/topics/lang-python-core-types), [Collections](/topics/lang-python-collections) and [Control Flow](/topics/lang-python-control-flow) — which fixes what a value is, what it costs to store and search, and how code branches and loops over it. [Functions and Comprehensions](/topics/lang-python-functions-comprehensions) and [OOP and Classes](/topics/lang-python-oop) sit on top: a function is an object carrying its closure, and a class is an object whose special methods hook into the syntax the first layer defines. [Decorators and Generators](/topics/lang-python-decorators-generators) is where those two meet — a decorator is a higher-order function applied at definition time, a generator is a function that implements the iterator protocol for you — so gaps in either show up there first. The correctness sections run across the core rather than above it. [Errors and Context Managers](/topics/lang-python-errors-context) governs every exit path, and `with` is itself a protocol, often backed by a generator. [Type Hints](/topics/lang-python-typing) describes the same object model statically: Protocols formalize duck typing, typed dicts and dataclasses give shapes to records, generics describe containers. [Testing](/topics/lang-python-testing) leans on both, because patching is attribute rebinding and a mock object invents attributes on demand unless given a spec. Concurrency reuses all of it. asyncio turns the suspension generators introduced into a scheduling model, and its task groups apply the error layer to many tasks at once; [Executors and Sync/Async Interop](/topics/lang-python-concurrency-executors-interop) is where blocking code meets the event loop. The outer ring is the running process — [the interpreter](/topics/lang-python-runtime), [performance](/topics/lang-python-performance-memory), [packaging](/topics/lang-python-packaging-envs), [diagnostics](/topics/lang-python-diagnostics) and [Unix behaviour](/topics/lang-python-process) — where the questions start from an import that fails, a service that is slow, or a worker that will not exit. The [Standard Library](/topics/lang-python-stdlib) and [Security](/topics/lang-python-security) cut across every layer: most security questions are about a standard-library call used with its default settings. A small example where several layers meet: ```python from collections.abc import Iterable, Iterator from dataclasses import dataclass @dataclass(frozen=True) class Reading: sensor: str value: float def parse(lines: Iterable[str]) -> Iterator[Reading]: for line in lines: sensor, _, raw = line.partition(",") if raw.strip(): yield Reading(sensor, float(raw)) with open("readings.csv", encoding="utf-8") as fh: peak = max(parse(fh), key=lambda r: r.value, default=None) ``` The file object is already a lazy iterator of lines and the generator keeps it lazy, so memory stays flat however large the file is. The frozen dataclass is a hashable value type with generated equality. The annotations document the contract for a checker but are never enforced by the interpreter. `max` with a key function consumes the stream once, and the `with` block closes the file on every exit path — including a `ValueError` from a malformed number, which a real parser would have to decide how to handle.

  1. Core Types and Syntax →

    Binding, mutability, identity and copying explain most of Python's surprises, and every other section assumes you can reason about them.

  2. Collections and Data Structures →

    Lists, dicts and sets are the daily workhorses; interviewers probe their costs and hashing rules early and often.

  3. Functions and Comprehensions →

    Parameter kinds, scoping, closures and comprehensions are the evaluation model that decorators, generators and asyncio are built from.

  4. OOP and Classes →

    Construction, the MRO, special methods and dataclasses: how your own types join in with Python's syntax and builtins.

  5. Decorators and Generators →

    Decorators and the iterator protocol turn functions into tools; learn them before asyncio, which reuses suspension and wrapping.

  6. Concurrency and Async →

    With the core solid, take the GIL, threads, processes and asyncio, then practise defending one model for a concrete workload.

  • Explaining a surprise as pass-by-reference or pass-by-value: neither describes Python, and interviewers want the binding model from Core Types and Syntax.

  • Using is to compare numbers or strings because it worked in the REPL: object caching is an implementation detail, and the same test fails for other values.

  • Checking membership against a list inside a loop: every check scans the list, so the loop turns quadratic where a set would keep it linear.

  • Reading an iterator or generator twice and expecting the same items: the second pass sees nothing, often because a debug print consumed it first.

  • Writing a decorator without functools.wraps: the wrapper's name and docstring replace the original's, which confuses logging, introspection and tools that rely on them.

  • Adding threads to speed up a CPU-bound loop on a standard CPython build: the lock serializes the work, so the answer is processes or native code.

  • Calling a blocking function inside a coroutine: nothing else on that event loop runs until it returns; hand it to an executor or use an async client.

  • Wrapping a large block in a broad except that logs nothing: real bugs disappear along with the expected failure, and the traceback that explained them is gone.

  • Treating annotations as runtime validation: a wrong type passes straight through unless a checker runs in CI or a library validates explicitly.

  • Optimizing before profiling: guesses about which part of Python code is slow are often wrong, and interviewers expect a measurement before a rewrite.

  • Installing into the system interpreter or an unpinned environment: builds stop being reproducible, and a transitive upgrade can break production with no code change.

  • Building shell commands or SQL by formatting untrusted input into a string: the data becomes syntax; see Security and Safe APIs.

This guide assumes CPython 3.12 or later. Most answers read the same on 3.9–3.11, but interviewers still ask about the releases that changed how everyday code is written: - **3.7** — dicts preserving insertion order became a language guarantee (CPython 3.6 already behaved that way); dataclasses, `contextvars` and `asyncio.run` arrived. - **3.8** — assignment expressions (the walrus operator) and positional-only parameters. - **3.9** — built-in collections usable as generics in annotations, and `zoneinfo`. - **3.10** — structural pattern matching and the `X | Y` union syntax. - **3.11** — exception groups with `except*`, `asyncio.TaskGroup`, and a large interpreter speed-up from the Faster CPython work. - **3.12** — dedicated syntax for type parameters and the `type` statement. - **3.13** — an experimental free-threaded build and an experimental JIT compiler. - **3.14** — the free-threaded build became officially supported, though still an opt-in build rather than the default, and annotations are evaluated lazily. The typing spelling is the change that most confuses candidates reading older material: ```python # 3.8-era spelling from typing import List, Optional, TypeVar T = TypeVar("T") def first(items: List[T]) -> Optional[T]: ... # 3.12 onward def first[T](items: list[T]) -> T | None: ... ``` The meaning did not change, only the syntax. Python 2 reached end of life in 2020; questions about it are now about migrating legacy code, not writing it.

CPython, the reference implementation, is developed by the core developers under the Python Software Foundation, and interviewers expect you to place the language in three settings. On the **backend**, it runs web services through Django, Flask and FastAPI, and pre-fork application servers are the usual way to use more than one core. In **data and machine learning**, it is the glue language over native libraries such as NumPy, pandas and PyTorch: the Python layer orchestrates, and the heavy numeric work runs in compiled code. In **automation and tooling**, it replaces shell scripts once they need data structures, tests or error handling. Around the language sits a tooling layer an interviewer may ask you to choose from: pip, venv, Poetry and uv for environments and dependencies; pytest as the common third-party runner beside the standard library's `unittest`; mypy and pyright for type checking; Ruff for linting and formatting. PyPy is an alternative implementation with a JIT, and Cython or Rust extensions are the usual native escape hatches. The comparisons that come up most are with **Go** and **Java** for services — Python trades raw throughput and a single deployable artifact for development speed and library reach — and with **R** for analysis, where Python wins on general-purpose engineering and R on statistical depth.

explore

report an issue with this guide →

questions

1,717 · 17 sections

What does `a < b < c` mean in Python, and how many times is `b` evaluated?

level: juniorimportance: must knowfreq 75%
basics
~10 s

Python expands a < b < c into a < b and b < c, but evaluates the middle expression exactly once. If the first comparison is false, the second comparison is never performed.

open as a page

Why does `b = a` on a Python list leave both names pointing at one object?

level: juniorimportance: must knowfreq 76%
basics
~20 s

Assignment never copies in Python; it binds a second name to the same object. After b = a both names label one list, so b.append(4) is visible through a. Duplicate explicitly with a.copy(), a[:] or list(a).

open as a page

Why use decimal.Decimal instead of float for currency amounts in Python?

level: juniorimportance: must knowfreq 62%
basics
~20 s

A float stores a binary fraction, so an amount written as 0.10 is not held exactly and the tiny errors accumulate as you add. decimal.Decimal stores base-10 digits, so 0.10 built from the string '0.10' is exact and money totals stay exact.

open as a page

Why does 0.1 + 0.2 == 0.3 evaluate to False in Python?

level: juniorimportance: must knowfreq 85%
basics
~20 s

Python's float is IEEE-754 binary64, which stores exactly only fractions with a power-of-two denominator. 0.1, 0.2 and 0.3 each become nearby approximations, and the sum of the first two lands one step above the stored 0.3.

open as a page

What is the difference between `is` and `==` in Python?

level: juniorimportance: must knowfreq 88%
basics
~20 s

is asks whether two names are bound to one and the same object, the identity test behind id(). == asks whether the objects compare equal, dispatching to eq. Compare values with ==; reserve is for None, True, False and sentinels.

open as a page

What do *args and **kwargs collect in a Python function definition?

level: juniorimportance: must knowfreq 82%
basics
~10 s

*args collects the leftover positional arguments into a tuple; **kwargs collects the leftover keyword arguments into a dict. Both types are fixed, and both are empty rather than None when nothing extra is passed.

open as a page

What does functools.lru_cache do to a function, and how does functools.cache differ?

level: juniorimportance: must knowfreq 70%
basics
~20 s

functools.lru_cache stores each call's result in a dictionary keyed by the arguments, so a repeat call returns the stored value without running the body. functools.cache, added in 3.9, is lru_cache(maxsize=None): unbounded, and it never evicts.

open as a page

What is a free variable in a Python nested function, and how does a cell keep it alive?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A free variable is a name an inner function reads but does not bind, whose binding belongs to an enclosing function. CPython moves that variable out of the frame into a one-slot cell object, and the inner function holds the same cell, so the value outlives the enclosing call.

open as a page

Why does UnboundLocalError appear when a function does `x = x + 1` and x is a module-level global?

level: juniorimportance: must knowfreq 78%
basics
~20 s

Python decides scope at compile time: an assignment to x anywhere in the body makes x local for the whole function, so the read on the right-hand side hits an unset local slot instead of the module-level global.

open as a page

In the comprehension [x for row in rows for x in row], which for clause is the outer loop?

level: juniorimportance: must knowfreq 70%
basics
~20 s

The leftmost one. Clauses read left to right in the same order you would write nested for statements, so for row in rows is the outer loop and for x in row the inner one. The result is one flat list.

open as a page

What is the difference between a class attribute and an instance attribute in Python?

level: juniorimportance: must knowfreq 78%
basics
~20 s

A class attribute is created in the class body and stored on the class, so every instance sees the same object. An instance attribute is created by assigning to self.name and lives in that one object's own namespace.

open as a page

How do a plain method, a @classmethod and a @staticmethod differ in Python?

level: juniorimportance: must knowfreq 82%
basics
~20 s

A plain method takes the instance as its first argument, self. A classmethod takes the class as cls, so a subclass gets its own class. A staticmethod receives nothing implicitly; it is just a function stored in the class namespace.

open as a page

In Python, what does `_name` mean versus `__name` on a class attribute?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A single leading underscore is pure convention: it marks an attribute as internal and the interpreter ignores it. A double leading underscore inside a class body is rewritten by the compiler to _ClassName__name, which avoids subclass collisions rather than blocking access.

open as a page

In a typing.NamedTuple class, how do you give fields defaults and add methods?

level: juniorimportance: must knowfreq 65%
basics
~20 s

Write the record as a class: an annotated name with an assigned value becomes that field's default, and ordinary def statements become real methods on the generated tuple subclass. Defaulted fields must come last, exactly as with function parameters.

open as a page

What does the @dataclass decorator generate, and which class attributes become fields?

level: juniorimportance: must knowfreq 72%
basics
~10 s

@dataclass writes init, repr and eq from the class's annotated attributes, in declaration order. Only annotated names become fields; a plain assignment with no annotation stays an ordinary class attribute.

open as a page

What does the @ decorator syntax above a Python def actually do?

level: juniorimportance: must knowfreq 85%
basics
~20 s

@deco written above def f() is shorthand for defining f and then rebinding that name: f = deco(f). A decorator is any callable that takes the function object and returns the object the name will point at.

open as a page

What do the maxsize and typed arguments to functools.lru_cache control?

level: juniorimportance: must knowfreq 50%
basics
~20 s

maxsize caps how many results are stored - 128 by default, None for unbounded, 0 for none - and a full table drops the least recently used entry. typed=True adds argument types to the key, so 1 and 1.0 stop sharing.

open as a page

What object does a Python class decorator receive, and what does the class name end up bound to?

level: juniorimportance: must knowfreq 45%
basics
~20 s

A class decorator is called with the finished class object, after the class body has already executed, and whatever it returns is bound to the class name. Most decorators mutate the class and return the same object.

open as a page

Why must a decorator wrapping an `async def` function define its wrapper with `async def` and await inside?

level: juniorimportance: must knowfreq 60%
basics
~20 s

Calling an async def function runs none of its body; it returns a coroutine object. A plain def wrapper only holds that unstarted coroutine, so anything it does around the call observes nothing. The wrapper must be async def and await inside.

open as a page

Why does @retry(times=3) need one more function layer than a bare @retry?

level: juniorimportance: must knowfreq 60%
basics
~20 s

@retry(times=3) calls retry(times=3) first and decorates with whatever that call returns. So retry must be a factory that returns a decorator, which then takes the function and returns the wrapper - three nested levels instead of two.

open as a page

What does Python's `async with` statement do that a plain `with` cannot, and which methods does it call?

level: juniorimportance: must knowfreq 68%
basics
~10 s

async with awaits aenter before the block and awaits aexit after it, so setup and teardown can perform I/O. A plain with calls enter and exit synchronously and cannot await anything.

open as a page

Why use asyncio.Lock instead of threading.Lock inside a coroutine?

level: juniorimportance: must knowfreq 58%
basics
~20 s

asyncio.Lock suspends only the awaiting task, so the event loop keeps running everything else. threading.Lock blocks the entire thread the loop runs on, freezing every task on that loop — including the one that would release the lock.

open as a page

What must an object implement for Python's `async for` to iterate it?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Its type must define __aiter__, an ordinary method returning an async iterator, and that iterator's type must define __anext__, a coroutine that resolves to the next item or raises StopAsyncIteration when the stream ends.

open as a page

Why does asyncio.create_task() require you to keep a reference to the Task it returns?

level: juniorimportance: must knowfreq 55%
basics
~20 s

The event loop registers running tasks only in a weak set, so the Task object create_task() hands back may be the only strong reference. Drop it and the task can be garbage-collected while still suspended, and the work never finishes.

open as a page

When asyncio.wait_for() times out, what happens to the wrapped coroutine?

level: juniorimportance: must knowfreq 65%
basics
~10 s

asyncio.wait_for cancels the wrapped awaitable when the deadline passes, waits for that cancellation to finish unwinding, then raises TimeoutError to the caller. The inner work is stopped, not left running in the background.

open as a page

What does a `[project.scripts]` entry in pyproject.toml create when the package is installed?

level: juniorimportance: must knowfreq 60%
basics
~20 s

It declares a console command. At install time the installer generates a small wrapper in the environment's script directory that imports the named module and calls the named function, so typing the command runs your code.

open as a page

What does `pip install mypkg[redis]` do, and where is that extra declared?

level: juniorimportance: must knowfreq 55%
basics
~20 s

It installs mypkg itself plus the optional dependency set named redis. That set is declared in the project's pyproject.toml under the [project.optional-dependencies] table, where each key is an extra name and its value is a list of requirement strings.

open as a page

Why does pip compile a package with a C extension from source, and what does that build need?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Because no prebuilt wheel matched this machine's interpreter version, ABI and platform, pip fell back to the source archive. Compiling it needs a C compiler and linker, CPython's development headers (Python.h), and the project's declared build backend.

open as a page

How do you ship a JSON schema file inside your Python package's wheel?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Put the file inside the importable package directory and declare it as package data in your build backend's configuration. Setuptools needs a package-data entry, or include-package-data together with MANIFEST.in; hatchling and flit ship non-code files under the package by default.

open as a page

What does a single leading underscore on a Python module or class attribute name mean?

level: juniorimportance: must knowfreq 62%
basics
~20 s

A single leading underscore marks a name as internal: not part of the public API and free to change without notice. It is a convention, not enforcement — outside code can still import and call it.

open as a page

How does array.array store integers differently from a Python list?

level: juniorimportance: must knowfreq 40%
basics
~20 s

A Python list stores pointers to full int objects scattered on the heap. array.array stores raw machine values of one typecode packed back to back, so it holds only numbers of that type and costs far less memory.

open as a page

Why does slicing a memoryview of a bytearray avoid the copy that slicing the bytearray itself makes?

level: juniorimportance: must knowfreq 35%
basics
~20 s

Slicing a bytearray allocates a second bytearray and copies the bytes. A memoryview slice only records an offset and length into the same storage, so it costs the same tiny amount whatever the slice size.

open as a page

Which special methods let a custom class support len(obj), obj[k] and obj[k] = v?

level: juniorimportance: must knowfreq 70%
basics
~10 s

Python calls len for len(obj), getitem for reading obj[k], and setitem for assigning obj[k] = v. All three are looked up on the type, not the instance, and len must return a non-negative int.

open as a page

How do `dict.get` and `dict.setdefault` differ from indexing a Python dict with `d[key]`?

level: juniorimportance: must knowfreq 78%
basics
~20 s

Indexing a missing key raises KeyError. dict.get returns a default instead - None unless you pass one - and never changes the dict. dict.setdefault returns the existing value or inserts your default and returns that, so it mutates.

open as a page

Why does using a list as a dict key raise TypeError, and what works instead?

level: juniorimportance: must knowfreq 76%
basics
~20 s

A dict key must be hashable. Lists deliberately have no hash, because their contents can change and a key's hash must stay stable, so Python raises TypeError. Use a tuple of hashable items, or a frozenset when order is irrelevant.

open as a page

How do `typing.Any` and `object` differ as annotations to a type checker?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Any switches checking off for a value: any attribute, call or assignment is allowed. object is the top type, so every value fits it, but you may use only what every object supports until you narrow with isinstance.

open as a page

What does `Optional[str]` mean in a Python type annotation?

level: juniorimportance: must knowfreq 72%
basics
~20 s

Optional[str] from the typing module means the value is either a str or None - exactly the union str | None. It says nothing about omitting an argument: such a parameter is still required unless it also has a default.

open as a page

Are Python type annotations enforced at runtime, and what does the interpreter do with them?

level: juniorimportance: must knowfreq 75%
basics
~20 s

No. CPython never compares a value against its annotation. It only records annotations as metadata in the annotations dictionary on functions, classes and modules; enforcement comes from a separate static type checker or from library code that reads them.

open as a page

In Python typing, how does a parameter annotated `type[Digest]` differ from one annotated `Digest`?

level: juniorimportance: must knowfreq 45%
basics
~10 s

type[Digest] accepts the class object itself, Digest or any subclass, so the function can call it to build instances. A bare Digest annotation accepts an already-built instance instead.

open as a page

What does typing.TypedDict express that a plain dict[str, str] annotation cannot?

level: juniorimportance: must knowfreq 60%
basics
~20 s

typing.TypedDict names each key of a dictionary and gives every key its own value type, so a type checker can flag a misspelled, missing or undeclared key. dict[str, str] only says string keys, string values.

open as a page

What does Python's `assert` statement do, and what happens to it under `python -O`?

level: juniorimportance: must knowfreq 48%
basics
~20 s

assert expr, msg raises AssertionError with msg when expr is falsy and does nothing otherwise. Starting the interpreter with -O, or with PYTHONOPTIMIZE set, removes every assert statement at compile time, so the check never runs.

open as a page

What does logging.exception() record that logging.error() alone does not?

level: juniorimportance: must knowfreq 58%
basics
~10 s

logging.exception() logs at ERROR level and attaches the traceback of the exception currently being handled, because it passes exc_info=True for you. logging.error() logs only the message unless you pass exc_info=True yourself.

open as a page

Why translate a third-party library's exception into your own exception class at a module boundary?

level: juniorimportance: must knowfreq 55%
basics
~20 s

So callers depend on your abstraction rather than on the library. If a database driver's error class escapes your function, every caller must import that driver to catch it, and replacing the driver breaks all of them.

open as a page

How does contextlib.asynccontextmanager turn an async generator function into an async context manager?

level: juniorimportance: must knowfreq 45%
basics
~20 s

contextlib.asynccontextmanager decorates an async generator function that yields exactly once. Calling it builds an object for use with async with: everything before the yield is the setup, the yielded value is what as binds, and everything after the yield is the teardown.

open as a page

How does @contextlib.contextmanager turn a generator function into a context manager?

level: juniorimportance: must knowfreq 70%
basics
~10 s

contextlib.contextmanager decorates a generator function that yields exactly once. Everything before the yield runs on entry, the yielded value is what the as clause binds, and everything after the yield runs on exit.

open as a page

Why does base64.b64encode reject a str, and what do you write instead?

level: juniorimportance: must knowfreq 60%
basics
~10 s

base64.b64encode is a bytes-to-bytes function: it needs a bytes-like object and returns bytes. Encode text first with str.encode('utf-8'), then call .decode('ascii') on the result to get a str for JSON.

open as a page

How do struct.pack and struct.unpack convert Python values to and from bytes?

level: juniorimportance: must knowfreq 40%
basics
~20 s

struct.pack takes a format string plus values and returns a bytes object laid out exactly as that format describes. struct.unpack reverses it and always returns a tuple, even when the format has a single field.

open as a page

How do Python's uuid.uuid4, uuid.uuid1 and uuid.uuid5 differ in where their bits come from?

level: juniorimportance: must knowfreq 62%
basics
~20 s

uuid.uuid4 fills 122 bits from the operating system's random source. uuid.uuid1 encodes a timestamp plus the host's 48-bit node address. uuid.uuid5 hashes a namespace UUID together with a name, so the same name always produces the same UUID.

open as a page

Why is str.split(',') wrong for parsing a CSV line, and what does csv.reader do instead?

level: juniorimportance: must knowfreq 58%
basics
~20 s

A comma inside a quoted field is data, not a separator, so str.split(',') tears such a row apart. csv.reader runs the quoting state machine over the file object and yields each record as a list of strings.

open as a page

How do json.load, json.loads, json.dump and json.dumps differ?

level: juniorimportance: must knowfreq 75%
basics
~20 s

The trailing s means string. json.loads parses JSON text already in memory and json.load reads it from an open file object; json.dumps returns a JSON string, while json.dump writes that text straight into a file object.

open as a page

Why can reference counting alone never free two Python objects that point at each other?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Each object in the cycle holds a reference to the other, so neither count drops to zero even after every outside name is gone. CPython runs a second, tracing collector — the gc module — to find and free such groups.

open as a page

In CPython, what does `del x` actually do — does it free the object?

level: juniorimportance: must knowfreq 62%
basics
~20 s

del x only unbinds the name x from its namespace and drops one reference to the object. CPython deallocates the object at the moment its reference count reaches zero, so if a list, attribute or another name still holds it, nothing is freed.

open as a page

What does weakref.ref give you that an ordinary Python reference does not?

level: juniorimportance: must knowfreq 40%
basics
~10 s

A weakref.ref points at an object without counting toward the references that keep it alive. Call the ref like a function to get the object back, or None once the object has been collected.

open as a page

Why slice a memoryview of a large bytes object instead of the bytes itself?

level: juniorimportance: must knowfreq 45%
basics
~20 s

Slicing a bytes object allocates a new bytes and copies the requested range. A memoryview exposes the same memory through the buffer protocol, so slicing it returns another view over the original block in constant time, copying nothing.

open as a page

What happens to the GIL while zlib.compress runs on a large buffer?

level: juniorimportance: must knowfreq 55%
basics
~20 s

The C code inside zlib drops the global interpreter lock before it starts deflating and takes it back when the call finishes. Other Python threads run real bytecode during that window, so threaded compression genuinely overlaps.

open as a page

Why does a test that calls an async def function without awaiting it still pass?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Calling an async def function only builds a coroutine object; the body never runs. Assertions then inspect that object rather than a result, so the test passes vacuously. CPython only warns, at garbage-collection time, that the coroutine was never awaited.

open as a page

Why is a collaborator constructed inside a Python class harder to test than one passed in?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A collaborator built inside the class gives a test no way in, so the only override left is patching the name the module imported. Take the collaborator as a constructor parameter and the test simply passes a small fake object.

open as a page

Why does assigning over a module attribute in a test leak into later tests?

level: juniorimportance: must knowfreq 62%
basics
~20 s

An imported module exists once per process, cached in sys.modules, so rebinding one of its attributes changes it for every user until something restores it. A plain assignment has no rollback, and a failing assertion skips the restore line.

open as a page

What does Python's doctest module treat as a test inside a docstring?

level: juniorimportance: must knowfreq 40%
basics
~20 s

Any docstring line that starts with the >>> prompt. doctest runs that statement and compares whatever it prints to standard output against the lines beneath it, character for character, up to the first blank line.

open as a page

What does calling a unittest.mock.AsyncMock return, and how does that differ from Mock?

level: juniorimportance: must knowfreq 60%
basics
~10 s

Calling an AsyncMock runs nothing and hands back a coroutine object; awaiting that coroutine produces the mock's return_value. A plain Mock returns its return_value immediately, and awaiting a Mock raises TypeError.

open as a page

How does hashlib's update, digest and hexdigest cycle hash data incrementally?

level: juniorimportance: must knowfreq 65%
basics
~20 s

A hashlib hash object accumulates bytes across repeated update() calls, so feeding data in chunks matches hashing it in one call. digest() returns the raw bytes, hexdigest() the same value as a hex string; reading either does not reset the object.

open as a page

Which hashlib functions are built for password storage, and why not hashlib.sha256?

level: juniorimportance: must knowfreq 68%
basics
~20 s

hashlib offers two password functions: pbkdf2_hmac and scrypt. Both take a per-user salt and a work factor you choose, so one guess costs real time. hashlib.sha256 is built to be fast, which is exactly wrong for passwords.

open as a page

Why must a password-reset token come from `secrets`, not `random`?

level: juniorimportance: must knowfreq 70%
basics
~20 s

The random module's default generator is a Mersenne Twister whose entire internal state can be reconstructed from a few hundred observed outputs, so later tokens become predictable. secrets draws each token from the operating system's cryptographic source instead.

open as a page

Why does `hmac.compare_digest` exist when `==` already compares two bytes objects?

level: juniorimportance: must knowfreq 50%
basics
~20 s

== on two bytes objects stops at the first differing byte, so the time it takes reveals how much of a secret an attacker guessed right. hmac.compare_digest always does the same work; secrets.compare_digest is the same function.

open as a page

What does setting ssl.SSLContext.verify_mode to ssl.CERT_NONE actually give up?

level: juniorimportance: must knowfreq 60%
basics
~10 s

It turns off certificate verification, so a Python client accepts any certificate at all, including one an attacker generated. The connection stays encrypted but is no longer authenticated, which defeats the point of TLS.

open as a page

In Python's `a if cond else b`, what is evaluated and in what order?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Python evaluates cond first. If it is truthy, only a is evaluated and becomes the value; otherwise only b is. The branch that is not chosen never runs, so its side effects and its exceptions never happen.

open as a page

Why does a Python dict dispatch table store `handler` rather than `handler()` as its values?

level: juniorimportance: must knowfreq 60%
basics
~20 s

A dict literal evaluates every value eagerly while the dict is being built, so handler() would run the handler right there and store its return value. Storing the bare name keeps the function object, which you call after the lookup.

open as a page

How does Python decide which branch of an if/elif/else chain executes?

level: juniorimportance: must knowfreq 80%
basics
~10 s

Conditions are tested top to bottom and the first truthy one wins: its block runs and the whole rest of the chain is skipped. If none is true, the optional else block runs.

open as a page

What does Python's walrus operator `:=` do, and how does it differ from `=`?

level: juniorimportance: must knowfreq 65%
basics
~20 s

:= is an assignment expression: it binds a name and also evaluates to the assigned value, so it can be used inside a condition or a call. Plain = is a statement, which produces no value at all.

open as a page

What is the difference between `break` and `continue` in a Python loop?

level: juniorimportance: must knowfreq 82%
basics
~20 s

break ends the innermost enclosing loop immediately and control resumes after it. continue abandons only the rest of the current pass and moves on to the next item or condition test. Neither statement touches an outer loop.

open as a page

What is the difference between Python's eval() and exec()?

level: juniorimportance: must knowfreq 55%
basics
~10 s

eval() evaluates one expression and returns its value. exec() runs statements - assignments, loops, imports, definitions - and always returns None. Hand eval() a statement and it raises SyntaxError.

open as a page

What is Python's __pycache__ directory and when does CPython reuse a .pyc file?

level: juniorimportance: must knowfreq 55%
basics
~20 s

CPython caches each module's compiled bytecode in a pycache directory next to the source. It reuses that .pyc only when the magic number matches the interpreter and the recorded source timestamp and size still match.

open as a page

Why does a Python set of strings iterate in a different order on each run?

level: juniorimportance: must knowfreq 45%
basics
~20 s

CPython salts str and bytes hashes with a seed picked at interpreter start-up, so the same strings land in different slots of the set's hash table each run. Set iteration follows slot order, so the order changes.

open as a page

Why does a local file named logging.py shadow the standard library's logging module?

level: juniorimportance: must knowfreq 55%
basics
~10 s

The interpreter prepends the running script's own directory to sys.path, so import searches it before the standard library and takes the first match. Run with -P, or set PYTHONSAFEPATH=1, to drop that prepended entry.

open as a page

What does Python's -O flag actually do to assert statements and __debug__?

level: juniorimportance: must knowfreq 52%
basics
~20 s

Running the interpreter with -O compiles away every assert statement and sets the built-in constant debug to False, so if debug blocks vanish too. It performs no other optimization and does not make code faster.

open as a page

How does warnings.warn() differ from raising an exception in Python?

level: juniorimportance: must knowfreq 45%
basics
~20 s

warnings.warn() reports a problem without stopping execution: the call returns and the code keeps running. It also passes through the filter list in warnings.filters first, so the same call may print, be silenced, or be turned into a raised exception.

open as a page

What does Python's "coroutine was never awaited" RuntimeWarning mean?

level: juniorimportance: must knowfreq 62%
basics
~20 s

Calling an async def function only builds a coroutine object; none of its body runs until something awaits it or wraps it in a task. When that unstarted object is garbage collected, CPython emits the warning.

open as a page

What causes a Python RecursionError, and what do sys.getrecursionlimit and sys.setrecursionlimit control?

level: juniorimportance: must knowfreq 58%
basics
~20 s

CPython counts the Python frames stacked on the current thread and raises RecursionError once that count passes the ceiling sys.getrecursionlimit() reports, which is 1000 on a fresh interpreter. sys.setrecursionlimit() moves the ceiling; it does not enlarge the thread's real stack.

open as a page

What does Python's built-in breakpoint() do, and how does PYTHONBREAKPOINT control it?

level: juniorimportance: must knowfreq 45%
basics
~20 s

Calling breakpoint() pauses the program and drops into a debugger. It dispatches through sys.breakpointhook, which by default calls pdb.set_trace. Setting PYTHONBREAKPOINT=0 makes every call a no-op; setting it to a dotted callable path routes calls there instead.

open as a page

In a Python traceback, what do the `~~~^^^` marks under the source line point at?

level: juniorimportance: must knowfreq 58%
basics
~10 s

They mark the exact sub-expression that failed. Since Python 3.11 (PEP 657) every bytecode instruction carries start and end column offsets, so the traceback underlines the failing operation instead of blaming the whole line.

open as a page

Why can open() without a with block leak a file descriptor in Python?

level: juniorimportance: must knowfreq 70%
basics
~20 s

A file object holds a kernel descriptor until something closes it. CPython usually closes it when the last reference disappears, but that is a refcounting implementation detail. A with block closes deterministically, even when the body raises.

open as a page

Why do pre-fork Python application servers run several worker processes instead of one?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Because one CPython process executes Python bytecode on one core at a time under the global interpreter lock. Forking several workers, each with its own interpreter and its own lock, puts real work on every core and isolates a crash.

open as a page

How do you bound a blocking call with signal.alarm when it takes no timeout?

level: juniorimportance: must knowfreq 42%
basics
~10 s

Install a SIGALRM handler with signal.signal that raises an exception, call signal.alarm(seconds) just before the blocking call, and call signal.alarm(0) in a finally block. The handler's exception unwinds the blocked call.

open as a page

How do signal.SIGINT and signal.SIGTERM differ in a Python process by default?

level: juniorimportance: must knowfreq 60%
basics
~20 s

CPython installs its own default handler for signal.SIGINT that raises KeyboardInterrupt in the main thread, so finally blocks and atexit hooks still run. signal.SIGTERM keeps the operating system default, which terminates the process immediately with no Python cleanup.

open as a page

Why does Python's print output appear instantly in a terminal but stall when the process is piped?

level: juniorimportance: must knowfreq 65%
basics
~10 s

CPython picks buffering per stream at startup: a terminal gets line buffering, so every newline flushes; a pipe or file gets a block buffer, 128 KiB on Python 3.14, that waits until it fills.

open as a page
Python interview questions & primer · KataJob