Python interview questions in 2026 test three things: your grasp of the object model (mutability, references, scope), your fluency with idiomatic tools (generators, decorators, dataclasses, the standard library) under time pressure, and, for experienced roles, your judgment on concurrency and performance trade-offs like the GIL. The questions below are grouped by level, each with a runnable snippet, the output interviewers expect, and a note on how deep your answer should go.
Every snippet runs on CPython 3.11 or later unless a version is noted.
Key Takeaways
- Most "tricky" Python interview questions come from five mechanics: shared references, defaults evaluated once, late-binding closures, one-shot iterators, and the GIL. Learn those and you can predict nearly every gotcha output.
- Answer depth should scale with level. Juniors define and demonstrate. Mid-level candidates explain the mechanism. Seniors add the production consequence and a trade-off.
- The GIL still exists in default CPython. Python 3.14 made the free-threaded build officially supported but optional, so know both stories.
- In coding rounds,
collections,heapq,bisect,itertools, andfunctools.cachesave more time than any clever trick.
How Python Interviews Are Structured by Level
A Python technical interview usually mixes language questions with a coding problem solved in Python. The language questions get harder and more open-ended as the level rises. Use this table to calibrate how much to say.
| Level | Typical questions | What a strong answer includes | Answer length |
|---|---|---|---|
| Junior (0-2 yrs) | Mutability, list vs tuple, is vs ==, comprehensions, *args | Correct definition plus a 2-line example | 30-60 seconds |
| Mid (2-5 yrs) | Closures, decorators, generators, dunder methods, dataclasses, asyncio basics | The mechanism behind the behavior and a common bug it causes | 1-2 minutes |
| Senior (5+ yrs) | GIL and free-threading, memory management, descriptors, profiling, concurrency design | Mechanism, production consequence, alternative approach, how you would measure | 2-4 minutes, then follow-ups |
If you are still choosing a language for coding rounds, our guide on what language to use in a coding interview compares Python against Java, C++, and JavaScript.
Junior Python Interview Questions: Core Language
Junior questions check that you understand how Python names and objects work. Names are labels that point at objects; assignment never copies.
What is the difference between mutable and immutable types?
A mutable object can be changed in place after creation; an immutable one cannot. Lists, dicts, sets, and most user-defined objects are mutable. Ints, floats, strings, tuples, and frozensets are immutable. This matters because only immutable (more precisely, hashable) objects can be dict keys or set members.
The classic follow-up is a tuple that contains a list:
t = (1, [2])
try:
t[1] += [3]
except TypeError as e:
print("error:", e)
print(t)
Output: error: 'tuple' object does not support item assignment, then (1, [2, 3]). The += mutates the list in place first, then the tuple item assignment fails. Both halves happen.
What is the difference between is and ==?
== compares values by calling __eq__. is compares identity: whether two names point to the same object. Use is only for singletons like None.
Why does this list of lists behave strangely?
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
Output: [[1, 0, 0], [1, 0, 0], [1, 0, 0]]. The outer * 3 copies the reference three times, so every row is the same list. The fix is [[0] * 3 for _ in range(3)]. The same idea explains shallow versus deep copy: list(x) and x.copy() copy the outer container only, while copy.deepcopy(x) recursively copies nested objects.
What is the mutable default argument bug?
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
print(add_item(1))
print(add_item(2))
Output: [1], then [1, 2]. Default values are evaluated once, when def runs, so every call shares one list. The fix:
def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket
How does Python resolve variable scope?
Python looks up names using the LEGB rule: Local, Enclosing function, Global, then Built-in. The gotcha is that assignment anywhere in a function makes the name local for the whole function:
x = 10
def show():
print(x)
x = 5
show()
This raises UnboundLocalError: cannot access local variable 'x' where it is not associated with a value. The compiler saw x = 5 and marked x local before print ran. Use global x or, inside nested functions, nonlocal x when you truly mean to rebind the outer name.
Mid-Level Questions: Functions, Closures, and Decorators
Mid-level questions check that you understand functions as objects. A closure is a function that remembers variables from the enclosing scope where it was defined.
What do *args, **kwargs, /, and * mean in a signature?
*args collects extra positional arguments into a tuple and **kwargs collects extra keyword arguments into a dict. A bare * makes every later parameter keyword-only, and / (Python 3.8+) makes every earlier parameter positional-only.
def connect(host, /, port=443, *, timeout=5, **opts):
return host, port, timeout, opts
print(connect("db", 5432, timeout=1, ssl=True))
Output: ('db', 5432, 1, {'ssl': True}). Calling connect(host="db") raises TypeError because host is positional-only.
Why do these lambdas all return the same value?
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])
Output: [2, 2, 2]. Closures bind variables, not values. Each lambda looks up i when it is called, and by then the loop has finished at 2. Fix it by binding the current value as a default argument, lambda i=i: i, or with functools.partial.
How do you write a decorator, and why use functools.wraps?
A decorator is a callable that takes a function and returns a replacement. functools.wraps copies the original name, docstring, and other metadata onto the wrapper so debugging, logging, and introspection still work.
import functools
import time
def retry(times=3, delay=0.1):
def decorator(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return fn(*args, **kwargs)
except ConnectionError:
if attempt == times:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=2)
def fetch():
raise ConnectionError("down")
A decorator that takes arguments needs three layers: the factory (retry), the decorator, and the wrapper. Talk through each layer as you write it; our guide on how to think out loud in a coding interview shows how to narrate without losing pace.
Generators, Iterators, and Comprehensions
A generator is a function that uses yield to produce values lazily, one at a time, keeping its state between calls. An iterator is any object with __iter__ and __next__; generators are the easiest way to build one.
What is the difference between a list comprehension and a generator expression?
A list comprehension builds the whole list in memory immediately. A generator expression produces items on demand in roughly constant memory, which suits single-pass use like sum(x * x for x in nums).
Why is my generator empty the second time?
squares = (n * n for n in range(4))
print(list(squares))
print(list(squares))
Output: [0, 1, 4, 9], then []. Iterators are one-shot. The same bug appears when a function loops over a passed-in generator twice, such as computing a max and then a mean.
How would you stream a huge file with generators?
This is a staple Python generators interview question. Interviewers want a pipeline that never holds the full file in memory:
def read_lines(path):
with open(path) as f:
for line in f:
yield line.rstrip("\n")
def errors_only(lines):
for line in lines:
if " ERROR " in line:
yield line
itertools.islice(errors_only(read_lines("app.log")), 10) reads only as many lines as it needs. Mention yield from for delegating to a sub-generator.
OOP Questions: Dunder Methods, Dataclasses, and MRO
Python OOP questions focus on the data model, the set of dunder (double underscore) methods that let your objects work with built-in syntax.
Why does defining __eq__ make my objects unhashable?
If a class defines __eq__ without __hash__, Python sets __hash__ to None, so instances cannot go in sets or be dict keys. Objects that compare equal must have equal hashes. Define __hash__ over the same immutable fields you compare, or use a frozen dataclass.
Why use dataclasses?
A dataclass generates __init__, __repr__, and __eq__ from type-annotated fields. Options add more: frozen=True makes instances immutable and hashable, order=True adds comparisons, and slots=True (3.10+) uses __slots__ to cut memory per instance.
from dataclasses import dataclass, field
@dataclass(frozen=True, slots=True)
class Point:
x: int
y: int
@dataclass
class Team:
name: str
members: list[str] = field(default_factory=list)
print(Point(1, 2) == Point(1, 2), {Point(1, 2)})
Output: True {Point(x=1, y=2)}. Writing members: list[str] = [] raises ValueError at class definition, which is the dataclass version of the mutable default bug.
What is the MRO and how does super() work?
The method resolution order (MRO) is the order Python searches classes for an attribute, computed with the C3 linearization algorithm. super() calls the next class in the instance's MRO, not necessarily the parent you see in the source.
class A:
def hi(self): return "A"
class B(A):
def hi(self): return "B" + super().hi()
class C(A):
def hi(self): return "C" + super().hi()
class D(B, C):
pass
print(D().hi())
print([k.__name__ for k in D.__mro__])
Output: BCA, then ['D', 'B', 'C', 'A', 'object']. Inside B, super() resolves to C, because that is next in D's MRO. This cooperative behavior is why mixins work. For broader object design prompts, see our design patterns interview questions.
Gotcha questions are easy to read in an article and hard to trace live when an interviewer is watching you think. TechScreen is an invisible AI interview assistant that stays hidden during screen shares on Zoom, Google Meet, CoderPad, and HackerRank, and can walk you through an output or a decorator in real time. New users get 3 free tokens, no credit card required.
Concurrency: GIL, Threading, asyncio, and Multiprocessing
Concurrency is where Python interview questions for experienced developers get serious. The Global Interpreter Lock (GIL) is a mutex in CPython that allows only one thread to execute Python bytecode at a time.
What does the GIL actually mean for my code?
On the default build, threads do not speed up CPU-bound Python code. They still help with I/O-bound work, because the GIL is released while a thread waits on a socket, a file, or time.sleep, and many C extensions release it during heavy computation.
The 2026 nuance: Python 3.13 introduced an experimental free-threaded build (PEP 703), and per the Python 3.14 release notes, that build is now officially supported but still optional. It is installed separately (often as python3.14t), and some C extensions are not yet compatible. Saying "the GIL is gone" is wrong.
Which concurrency model should I pick?
| Workload | Best tool | Why | Watch out for |
|---|---|---|---|
| Thousands of network calls | asyncio | One thread, cheap tasks, cooperative switching | One blocking call stalls everything |
| I/O with blocking libraries | threading / ThreadPoolExecutor | GIL released during I/O | Shared-state races |
| CPU-bound pure Python | multiprocessing / ProcessPoolExecutor | Separate processes, separate GILs | Startup cost, pickling arguments |
| CPU-bound numeric work | NumPy or vectorized libraries | Work happens in C, often without the GIL | Copying large arrays |
| CPU-bound threads on 3.14t | Free-threaded build | Real parallel threads | Extension compatibility, single-thread overhead |
A related change: in Python 3.14, the default multiprocessing start method on Linux moved from fork to forkserver. Code that silently relied on forked global state can break, which makes a good senior follow-up.
Is counter += 1 thread-safe because of the GIL?
No. counter += 1 compiles to separate read, add, and store steps, and a thread switch can happen between them, losing updates. The GIL protects the interpreter's internals, not your invariants. Use threading.Lock, or avoid shared state with a queue.Queue. The general version of this question is covered in our concurrency and multithreading interview questions.
What is wrong with this asyncio code?
import asyncio
import time
async def fetch(i):
time.sleep(1)
return i
async def main():
return await asyncio.gather(*(fetch(i) for i in range(5)))
print(asyncio.run(main()))
It prints [0, 1, 2, 3, 4] but takes about five seconds, not one. time.sleep blocks the event loop, so the coroutines run one after another. Use await asyncio.sleep(1), or move blocking work off the loop with await asyncio.to_thread(blocking_fn). Bonus points for mentioning asyncio.TaskGroup (3.11+).
Python Coding Interview Tricks: Standard Library and Performance
In Python coding interview questions, standard library fluency is the cheapest speed boost available. These are the tools worth having in muscle memory.
| Need | Tool | Complexity note |
|---|---|---|
| Frequency counts | collections.Counter | most_common(k) uses a heap |
| Graph adjacency, grouping | collections.defaultdict(list) | Avoids key-exists checks |
| BFS queue, sliding window | collections.deque | O(1) append and pop at both ends |
| Top-k, Dijkstra, scheduling | heapq | Min-heap; push and pop O(log n) |
| Sorted insert, lower bound | bisect.bisect_left / insort | Search O(log n), insert O(n) |
| Memoized recursion | functools.cache | Unbounded; lru_cache(maxsize=...) to cap |
| Combinations, pairs, prefix sums | itertools | combinations, pairwise, accumulate |
Three traps catch Python candidates repeatedly:
list.pop(0)andlist.insert(0, x)are O(n). Use adequefor queues.heapqis a min-heap. Push-valuefor a max-heap, or(priority, counter, item)tuples when items are not comparable. Python 3.14 addedheappush_maxand related functions, but do not assume the assessment platform runs 3.14.- The default recursion limit is 1000. Deep DFS on a 10^5-node path graph raises
RecursionError. Convert to an explicit stack, or raise the limit withsys.setrecursionlimitand explain the risk.
Also remember that x in list is O(n) while x in set is O(1) on average. For the algorithm side, pair this with our coding interview patterns cheat sheet and Big O notation cheat sheet, both written with Python templates.
Senior Python Interview Questions
Senior Python interview questions are open-ended. The interviewer wants to see judgment, so structure each answer as mechanism, consequence, and alternative.
How does Python manage memory?
CPython frees an object the moment its reference count hits zero, and a cyclic garbage collector periodically cleans up reference cycles that counting cannot free. Strong answers mention weakref for caches that should not keep objects alive, __slots__ to shrink per-instance memory, and tracemalloc for finding leaks.
What is a descriptor, and where have you used one?
A descriptor is an object that defines __get__, __set__, or __delete__ and controls attribute access when stored on a class. property, classmethod, and bound methods are all built on descriptors, and ORM column fields are a classic real-world use.
When would you use a metaclass?
Rarely. A metaclass customizes class creation itself, but registering or validating subclasses is simpler with __init_subclass__ or a class decorator. Saying so is usually the senior answer.
How do you find and fix a slow Python service?
Measure first. Profile with cProfile or a sampling profiler such as py-spy, then fix in order: algorithm and data structure, repeated work (caching, batching I/O), vectorizing with NumPy, moving CPU work to processes, and only then compiled extensions. This question overlaps heavily with backend engineer interviews, where it often becomes a system design follow-up.
What does finally do with a return?
def f():
try:
return "try"
finally:
return "finally"
print(f())
Output: finally. A return in finally overrides the earlier return and silently swallows any in-flight exception. Since Python 3.14 (PEP 765), the compiler emits a SyntaxWarning for return, break, or continue that exits a finally block.
The Python Gotcha Cheat Sheet
Run through these the night before. If you can predict every output, you are ready for the tricky-output part of any Python technical interview.
| Snippet | Output | Root cause |
|---|---|---|
def f(x, l=[]): l.append(x); return l called twice | [1], [1, 2] | Defaults evaluated once |
[lambda: i for i in range(3)] called | [2, 2, 2] | Late-binding closures |
[[0] * 3] * 3, then set one cell | Every row changes | Repeated references |
list(gen) twice | Second is [] | Iterators are one-shot |
t = (1, [2]); t[1] += [3] | TypeError, list still mutated | In-place add, then failed assignment |
print(x) before x = 5 in a function | UnboundLocalError | Assignment makes name local |
0.1 + 0.2 == 0.3 | False | Binary floating point; use math.isclose |
D(B, C) diamond with super() | BCA | C3 MRO |
return in try and finally | Value from finally | finally overrides; warns in 3.14 |
How to Prepare for a Python Interview in Two Weeks
Two focused weeks are enough if you practice actively.
- Days 1-3: Core language. Re-type every snippet in this guide and predict the output before running it.
- Days 4-6: Write a retry decorator, a caching decorator, a generator pipeline, and a frozen dataclass from memory.
- Days 7-10: Solve 15-20 medium problems in Python using only the standard library tools in the table above. Time yourself.
- Days 11-12: Concurrency. Build the same downloader with threads, asyncio, and processes, and explain when each wins.
- Days 13-14: Mock interviews with spoken explanations. If your loop starts with a recruiter call, review our technical phone screen guide.
Even well-prepared Python developers blank on a closure output or an asyncio bug when the clock is running. TechScreen runs invisibly during your Python technical interview and gives real-time explanations, code, and complexity analysis on Zoom, Google Meet, Teams, CoderPad, and HackerRank. Start with 3 free tokens and try it on a mock round first.
Frequently Asked Questions
What are the most common Python interview questions?
The most common Python interview questions cover mutable versus immutable types, the mutable default argument bug, list copying, scope rules (LEGB), *args and **kwargs, closures and decorators, generators versus lists, dunder methods such as __eq__ and __hash__, dataclasses, method resolution order, and the GIL. Coding rounds add standard library fluency: Counter, defaultdict, deque, heapq, bisect, and functools.cache. Senior loops add memory management, descriptors, profiling, and concurrency design.
What is the GIL in Python and does it still exist in 2026?
The Global Interpreter Lock is a mutex in CPython that lets only one thread execute Python bytecode at a time, which stops CPU-bound threads from running in parallel. It still exists in the default CPython build in 2026. Python 3.13 added an experimental free-threaded build without the GIL, and Python 3.14 made that build officially supported but optional. Most production code and interview answers still assume the GIL is present.
Should I use threading, asyncio, or multiprocessing?
Use asyncio for many concurrent I/O-bound tasks, such as thousands of network calls, when your libraries support async. Use threading for I/O-bound work with blocking libraries, or a small number of concurrent tasks. Use multiprocessing or ProcessPoolExecutor for CPU-bound work that needs multiple cores on the standard GIL build. In an interview, name the workload type first, then pick the tool, then mention the cost: event loop discipline, shared-state races, or process startup and pickling overhead.
Why is a mutable default argument a bug in Python?
Default argument values are evaluated once, when the def statement runs, not each time the function is called. A default list or dict is therefore one shared object across every call that omits the argument, so mutations accumulate between calls. The fix is to default to None and create a fresh object inside the function body. Dataclasses guard against the same mistake by raising ValueError for unhashable defaults and requiring field(default_factory=list).
Is Python a good language for coding interviews?
Yes. Python is one of the most widely used coding interview languages because its syntax is short and its standard library covers most algorithm needs: heaps, deques, counters, binary search, and memoization. The trade-offs are slower raw execution, which rarely matters at interview input sizes, and a default recursion limit of 1000 that can break deep DFS. Choose Python if you write it daily; fluency matters more than the language itself.
How should I answer Python interview questions for experienced developers?
Experienced candidates are judged on trade-offs, not definitions. Give the one-sentence definition, then explain the mechanism, then describe a production consequence you would watch for. For example, explaining the GIL should lead to how you would parallelize a CPU-bound job, how you would measure it, and what the free-threaded build changes. Interviewers at senior level also expect you to mention testing, profiling, and typing without being asked.
What Python version should I know for interviews in 2026?
Know modern Python 3 features that have been stable for several releases: f-strings, dataclasses, type hints, the walrus operator, match statements (3.10), exception groups and asyncio.TaskGroup (3.11), and dict insertion ordering. Knowing what changed in 3.13 and 3.14, such as the free-threaded build and new heapq max-heap functions, is a bonus. Many online assessment platforms run slightly older versions, so avoid relying on the newest APIs in timed rounds.
Ready to use AI assistance in your next interview?
TechScreen is the invisible AI assistant trusted by engineers interviewing at Google, Meta, Amazon, and hundreds of other companies. Start with 3 free tokens — no credit card required.
Ace your next interview →