83 Python interview questions with detailed answers, sorted from basic to advanced. Free to practice, with progress saved automatically.
Python is a general-purpose language used for backend services, scripting, data work, and machine learning. Questions move from core language features and OOP with dunder methods to memory management, the GIL, threading versus asyncio, generators, decorators, context managers, the standard library, common gotchas such as mutable default arguments, and database access with testing.
and 9 more topics.
CPython manages memory automatically in two layers: reference counting frees most objects the moment nobody uses them, and the garbage collector (the gc module) cleans up reference cycles that reference counting misses. What just happened: - Reference counting: every object has a counter of how…
Mutable means it can be changed in place after creation; immutable means it cannot, and every "change" actually creates a new object. Mutable Immutable --------- Types list, dict, set, bytearray int, float, str, tuple, frozenset, bytes After a "change" Still the same object (id unchanged)…
Choose by two questions: do you need to modify the data (mutable or immutable) and do you access it by position or by value/key. - list: an ordered sequence you can add to, remove from and change. Use it for ordinary collections like query results…
A generator is a function that uses yield to return an iterator, computing values lazily: one element at a time, on request. It saves memory compared with a list because the whole dataset is never loaded into RAM. When it reaches yield, the function pauses…
A decorator is a higher-order function: it takes a function and returns a new one with extra behavior. @decorator is syntactic sugar for reassigning the name. Use @functools.wraps to keep the original function's metadata. @timer runs at definition time: timer(slow) returns wrapper, and the name…
A context manager is an object with two methods, enter and exit. The with statement calls enter on entering the block and always calls exit when leaving it, even if the block raises an exception. That way cleanup (closing a file, returning a connection, releasing…
A closure is a nested function that remembers variables from its enclosing scope even after the outer function has returned. A lambda is a one-line anonymous function, usually for simple throwaway logic. The two are not exclusive: a lambda defined inside a function that uses…
A shallow copy creates a new container but copies only the references inside it, so nested elements are still the same objects as in the original. A deep copy copies recursively, creating the nested objects too, so the two copies are fully independent. copy.copy(matrix) builds…
Four blocks, each with one job: - try: code that may raise an exception. - except: handles the exception. You can have several except blocks for different types; Python picks the first one that matches. - else: runs only if NO exception occurred in try.…
== compares value (calls eq). is compares identity — whether both variables point to the same object in memory. Note: CPython caches small integers (-5 to 256) and interns some strings, so the result of is on numbers and strings depends on implementation details. x…
lrucache (Least Recently Used cache) memoizes function results — subsequent calls with the same args return the cached result instead of recomputing. Arguments must be hashable. When the cache holds maxsize entries, the least recently used result is evicted. The cache lives as long as…
Use f-strings (Python 3.6+) — fastest, most readable, supports inline expressions. f"Name: {name!r}, Score: {score:.2f}". Use .format() for reusable templates. Avoid % formatting in new code, with one exception: logging. With logger.info("User %s logged in", userid) logging only formats the string when the message is…
The four OOP principles are encapsulation, inheritance, polymorphism and abstraction. Python supports all four, but favors "convention over enforcement": there is no real private, and polymorphism leans heavily on duck typing. - Abstraction: Shape extends ABC and marks area with @abstractmethod. Calling Shape() raises TypeError,…
An instance method receives self, so it can reach both the instance and the class. A @classmethod receives cls, which gives access to the class, and is often used as a factory. A @staticmethod receives neither self nor cls: it is a utility function related…
@property turns a method into a getter: callers read t.celsius like a plain attribute, but Python actually calls the function. Add @celsius.setter (and @celsius.deleter if needed) to run validation logic on every assignment. Why use a property instead of Java-style getcelsius()/setcelsius(): in Python you usually…
Use ABC + @abstractmethod to create an abstract class. Subclasses must implement every abstract method, or they cannot be instantiated. The TypeError happens when you create an instance, not when you declare the subclass. An abstract class is a shared blueprint: it declares the methods…
@dataclass is a decorator from the dataclasses module: it reads the fields declared with type hints in the class and generates init, repr, and eq for you, so you do not write those repetitive methods by hand. With a regular class, the same thing needs…
A dunder method (double underscore, like repr) is a method Python calls for you when you use built-in syntax: print(x) calls str, x == y calls eq, len(x) calls len. Implementing them is how your class behaves like a built-in type. - repr and str:…
The GIL (Global Interpreter Lock) is a mutex in CPython: only one thread can execute Python bytecode at a time. It exists because CPython manages memory with reference counting, and one shared lock is a simple way to stop threads from corrupting each other's counts.…
Choose by whether your program mostly waits on I/O (API calls, DB queries, file reads) or burns CPU (image processing, heavy number crunching). The reason is the GIL (Global Interpreter Lock): in default CPython, only one thread per process executes Python bytecode at a time.…
The event loop is a loop running on a single thread that keeps a list of pending work and resumes each piece in turn. A function declared with async def is a coroutine: calling it only creates a coroutine object; the code inside runs once…
FastAPI uses Pydantic models to auto-validate the request body, query params and path params. It returns 422 Unprocessable Entity when input is invalid. What happens on a request: - FastAPI sees that the user parameter is typed as a BaseModel, treats it as the JSON…
Middleware runs before and after every request. Use @app.middleware("http") for custom middleware. For CORS, GZip and TrustedHost, use the built-in middlewares. Note: wrapping callnext in try/except does not catch an HTTPException raised by a path operation. FastAPI has an exception-handling layer that sits inside, closer…
JWT auth in FastAPI has three parts: a login route that checks the password and signs a token, a dependency that reads and verifies the token, and protected routes that declare that dependency via Depends. Libraries per the current FastAPI docs: PyJWT to sign/verify, pwdlib…
BackgroundTasks is a built-in FastAPI feature (inherited from Starlette): you register a function to run inside the web server process, after the response has been sent to the client. Celery is a separate task queue system: the web server only pushes the task into a…
N+1: 1 query fetches N records, then N more queries fetch related data. Fix it with eager loading. Pick selectinload for one-to-many relationships like User.posts: a one-to-many JOIN repeats each user row once per post, so the result set balloons. joinedload fits when each user…
SQLAlchemy 2.0 async setup uses createasyncengine, asyncsessionmaker, and the new Mapped/mappedcolumn ORM style: the type in Mapped[...] is both a type hint for the editor and what defines the column, e.g. Mapped[str] is NOT NULL and Mapped[str None] is nullable. What each part does: -…
FastAPI's TestClient calls the app directly in the same process, with no real server, and lets you write ordinary synchronous tests even when the endpoints are async def. To keep tests away from the real database, you swap the dependency with app.dependencyoverrides. What happens here:…
Fixture scope determines when a fixture is created and destroyed: - function (default) — once per test. - class — once per test class. - module — once per test file. - package — once per package (a test directory with init.py). - session —…
pytest-mock is a plugin that provides the mocker fixture, a thin wrapper around unittest.mock. You take mocker as a test parameter, call mocker.patch("dotted.path.name") to replace an object with a mock, and the plugin undoes the patch when the test ends, with no with block or…
pydantic-settings reads environment variables automatically and validates their types. It also supports a .env file. How fields get their values: - Field names map to environment variable names, case-insensitive by default: databaseurl reads DATABASEURL, accesstokenexpireminutes reads ACCESSTOKENEXPIREMINUTES. For a shared prefix, set envprefix="APP" in SettingsConfigDict…
Type hints (PEP 484, since Python 3.5) annotate the types of variables, parameters and return values. Python does not check them at runtime; their value is that tools can read them and catch errors before the code runs. The second call still runs until the…
args lets a function accept any number of arguments passed by position, collected into a tuple. kwargs accepts any number of arguments passed by name (keyword arguments), collected into a dict. The and symbols also work in reverse when calling: createuser(data) expands the dict into…
Four built-in functions commonly used to loop over and transform sequences of data: Notes: - map, filter, and zip all return lazy iterators: nothing is computed until you iterate, so wrap them in list() if you want a list right away. - zip stops at…
The walrus operator (PEP 572, Python 3.8+) assigns a value to a variable inside an expression, and the expression evaluates to that same value. So you can store a result and test it right away, without a separate assignment line. Note: avoid overuse — walrus…
A type hint is a type annotation that static checkers (mypy, pyright, IDEs) read to catch bugs before the code runs. Python does not enforce types at runtime: def f(x: int) still accepts f("a") without an error. - Union[A, B] means the value is one…
Iterator: any object implementing iter() and next(). Generator: a function using yield — it implements the iterator protocol for you, lazily and statefully. The core difference is who keeps the state: - Hand-written iterator: you store state in attributes (self.current), update it in next, and…
Pydantic v2 rewrote the validation layer in Rust (the pydantic-core package), renamed many APIs, and made validation stricter than v1. FastAPI has supported v2 since 0.100, so these changes show up directly when you upgrade. The last print shows stringtype: v2 rejects the number 123…
FastAPI runs on Starlette (ASGI) and supports both def (sync) and async def endpoints: async def: use it when the endpoint performs async I/O (async database queries, async HTTP calls): def (sync): use it when the endpoint calls a blocking library that has no async…
@pytest.mark.parametrize runs the same test function against several data sets. Each set becomes its own test case: when one fails, the report names exactly that case, and the rest still run. The first argument is a string of argument names separated by commas; the second…
All three live in unittest.mock and differ in what they support out of the box: - Mock: a fake object that accepts any call and any attribute, recording them so you can assert. It does not configure magic methods such as len, iter, enter. -…
@dataclass(slots=True) (Python 3.10+) makes the dataclass generate slots from its fields. By default each instance has its own dict to hold attributes; with slots, attributes live in fixed slots on the object itself and there is no dict, so each instance is lighter. Measured with…
Python uses what is usually called pass-by-object-reference (or "call by assignment"): each parameter is bound to the same object the caller passed in — no copy is made, and it is not C++-style pass-by-reference. The effect depends on mutability: - Mutable (list, dict, set): mutating…
LEGB is the order Python resolves a name: Local (current function) → Enclosing (outer function) → Global (module) → Built-in. Lookup stops at the first match. By default, assigning a name inside a function creates a Local variable — shadowing any outer name. To assign…
A compact way to build a new collection with a transform + filter in one expression: Use them to map/filter an iterable — short and usually faster than a for loop with append. Avoid them for multi-branch logic, deep nesting, or side effects → a…
Every module has a name variable. When you run a file directly (python foo.py), that file's name is "main". When the file is imported elsewhere, name is the module name ("foo"). Put throwaway / CLI entry-point code in this block so it runs only when…
- list.sort(): sorts in place, returns None, lists only. - sorted(iterable): returns a new list, accepts any iterable (tuples, dict keys, …). Both take key= (a function extracting the comparison key) and reverse=. Both use Timsort and are stable — items with equal keys keep…
pickle serializes a Python object into a byte stream and deserializes it back (reconstructing the object). Unlike JSON, it preserves complex Python structures (custom objects, cyclic references) but is Python-only and not cross-language. The danger: unpickling can call arbitrary constructors/functions (via hooks like reduce), so…
It is not a plain value-comparing switch. match does structural matching: it can destructure sequences/mappings/objects, capture variables from inside them, and add an if guard. When a branch matches, its sub-parts are bound to variables. where matches on the shape of point: the tuple (0,…
Python has no real access modifiers (private/protected). Instead there is a convention plus a renaming mechanism: - name (single leading underscore): a "internal, do not touch from outside" convention — purely a convention, not enforced, still accessible. - name (two leading underscores, at most one…
A default value is evaluated exactly once, when the def statement runs — not on every call. That list is stored on the function object itself (add.defaults), so every call that omits bucket shares the same list. The standard fix: use None as a sentinel…
You get [[1, 0, 0], [1, 0, 0]] — both rows change. The operator on a list does not copy the elements, it duplicates references: the two "rows" are literally one object. [0] 3 alone behaves as expected because 0 is immutable: row[0] = 1…
A list comprehension materialises the whole list in RAM; a generator expression returns a lazy iterator that computes each item only as it is consumed. Practical differences: - Memory: a genexp holds one item at a time — ideal for large files, DB cursors, streamed…
Because CPython pre-creates and shares small integer objects in the range -5 to 256. A value outside that range may become a new object each time it is created, so is (an identity check: is it the same object?) returns False, even though == (a…
A parameterised decorator needs three levels of function: an outer function taking the config and returning the decorator, which takes the function and returns the wrapper. @retry(times=3) reads as: call retry(times=3) first, then apply its result to the function. What functools.wraps is for: without it…
The yielded values are the same, but yield from fully delegates to the sub-generator instead of merely forwarding values. Three differences: 1. Two-way forwarding: send(), throw() and close() from the caller reach the sub-generator directly. A manual for loop swallows them. 2. Access to the…
Two ways: a class with enter/exit, or @contextlib.contextmanager over a generator that yields exactly once. Cleanup must sit in finally, otherwise a raising block never cleans up. If exit returns True: the exception is swallowed entirely — execution resumes right after the with block as…
It raises TypeError: unhashable type. Python 3 automatically sets hash = None on any class that overrides eq without declaring hash. The reason is the hash/equality contract: - If a == b, then hash(a) == hash(b) must hold. - A hash must stay constant for…
They solve three different needs, best decided by where the data boundary is: NamedTuple — an immutable, lightweight, unpackable record that can be a dict key. Good for multi-value returns, read-only config, coordinates or time ranges. @dataclass — the internal domain object: mutable if needed,…
No. The Python runtime ignores annotations: def f(n: int) still accepts "abc" and runs without error. Type hints exist for static analysis tools (mypy, pyright, IDEs) and libraries that read metadata (pydantic, FastAPI, dataclasses, SQLAlchemy 2.0) — enforcement means running a checker in CI. Optional[str]…
Because Python closures capture the variable, not the value at creation time (late binding). All three lambdas point at the same i; by the time they are called the loop has finished and i is 2. Three fixes: The trap rarely appears as bare lambdas;…
asyncio.gather starts all coroutines at once. With 200 calls the remote side often replies 429, connections saturate, and if several requests do this concurrently the process opens thousands of sockets. The standard fix is asyncio.Semaphore (bounded concurrency). Three things to remember: - Share one AsyncClient…
Vectorization means writing an operation over a whole array (c = a b) instead of looping over elements in Python. The loop still exists, but it lives in NumPy's precompiled C code instead of the Python interpreter. Big O does not change — still O(n)…
Unpacking splits an iterable into several variables, or "spreads" it out as arguments/elements. spreads an iterable (list, tuple...) into positional items; spreads a dict into keyword pairs. Tuple unpacking — assigning several variables: and in calls and in collection literals: Conversely, when defining a function,…