← All articles
13 min read

Python Interview Questions for Software Engineers (2026)

The Python interview questions that actually come up in 2026, sorted by level, with the exact outputs interviewers expect and guidance on how deep each answer should go.

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, and functools.cache save 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.

LevelTypical questionsWhat a strong answer includesAnswer length
Junior (0-2 yrs)Mutability, list vs tuple, is vs ==, comprehensions, *argsCorrect definition plus a 2-line example30-60 seconds
Mid (2-5 yrs)Closures, decorators, generators, dunder methods, dataclasses, asyncio basicsThe mechanism behind the behavior and a common bug it causes1-2 minutes
Senior (5+ yrs)GIL and free-threading, memory management, descriptors, profiling, concurrency designMechanism, production consequence, alternative approach, how you would measure2-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.

Get started free →

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?

WorkloadBest toolWhyWatch out for
Thousands of network callsasyncioOne thread, cheap tasks, cooperative switchingOne blocking call stalls everything
I/O with blocking librariesthreading / ThreadPoolExecutorGIL released during I/OShared-state races
CPU-bound pure Pythonmultiprocessing / ProcessPoolExecutorSeparate processes, separate GILsStartup cost, pickling arguments
CPU-bound numeric workNumPy or vectorized librariesWork happens in C, often without the GILCopying large arrays
CPU-bound threads on 3.14tFree-threaded buildReal parallel threadsExtension 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.

NeedToolComplexity note
Frequency countscollections.Countermost_common(k) uses a heap
Graph adjacency, groupingcollections.defaultdict(list)Avoids key-exists checks
BFS queue, sliding windowcollections.dequeO(1) append and pop at both ends
Top-k, Dijkstra, schedulingheapqMin-heap; push and pop O(log n)
Sorted insert, lower boundbisect.bisect_left / insortSearch O(log n), insert O(n)
Memoized recursionfunctools.cacheUnbounded; lru_cache(maxsize=...) to cap
Combinations, pairs, prefix sumsitertoolscombinations, pairwise, accumulate

Three traps catch Python candidates repeatedly:

  1. list.pop(0) and list.insert(0, x) are O(n). Use a deque for queues.
  2. heapq is a min-heap. Push -value for a max-heap, or (priority, counter, item) tuples when items are not comparable. Python 3.14 added heappush_max and related functions, but do not assume the assessment platform runs 3.14.
  3. 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 with sys.setrecursionlimit and 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.

SnippetOutputRoot 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 cellEvery row changesRepeated references
list(gen) twiceSecond is []Iterators are one-shot
t = (1, [2]); t[1] += [3]TypeError, list still mutatedIn-place add, then failed assignment
print(x) before x = 5 in a functionUnboundLocalErrorAssignment makes name local
0.1 + 0.2 == 0.3FalseBinary floating point; use math.isclose
D(B, C) diamond with super()BCAC3 MRO
return in try and finallyValue from finallyfinally overrides; warns in 3.14

How to Prepare for a Python Interview in Two Weeks

Two focused weeks are enough if you practice actively.

  1. Days 1-3: Core language. Re-type every snippet in this guide and predict the output before running it.
  2. Days 4-6: Write a retry decorator, a caching decorator, a generator pipeline, and a frozen dataclass from memory.
  3. Days 7-10: Solve 15-20 medium problems in Python using only the standard library tools in the table above. Time yourself.
  4. Days 11-12: Concurrency. Build the same downloader with threads, asyncio, and processes, and explain when each wins.
  5. 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.

Get started free →

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 →