Top 40 Python Interview Questions and Answers (2026 Edition)
Preparing for a Python backend interview? These are the 40 questions that come up most often — each with a crisp, interview-ready answer. Every answer links to a full deep-dive guide in this series when you want the code, the follow-up traps, and the trade-offs.
How to use this: read the question, say your answer out loud, then check. Interviewers score decision rules and failure modes, not definitions — "which pattern, what breaks, and what's the complexity?"
Scope note: this set targets Python backend interviews — DSA patterns, the language's data model, async/concurrency, and production Python. Preparing for data-science or ML roles instead? Start with Data Science Interview Questions and Machine Learning Interview Questions.
Coding Patterns & DSA
1. How would you find two numbers that add up to a target (Two Sum)?
Answer: the decision rule is: need complements in one pass → hash map. Store each value's index as you go; for each new number, check whether target - x is already in the map. O(n) time, O(n) space. The trap: sorting + two pointers also works, but preserving the original indices requires carrying them through the sort — sort (value, original_index) pairs — and costs O(n log n); the hash map gives expected O(n). Deep dive →
2. Longest substring without repeating characters — what's the pattern?
Answer: sliding window with a hash set: expand the right edge, and when a duplicate appears, shrink from the left until the window is valid again. O(n) time because each character enters and leaves the window once. The trap: a brute-force "check every substring" is O(n²) and dies on long inputs — the window is what makes it linear. Deep dive →
3. How do you find the top K frequent elements?
Answer: count with collections.Counter, then pick the top K — either most_common(k) or a min-heap of size K. After counting frequencies, a size-k heap avoids sorting all u unique values: O(u log k) selection instead of O(u log u). The trap: the Counter itself may hold up to O(u) unique elements, so overall memory isn't O(k) — the heap only bounds the selection step. That's the complexity distinction interviewers probe. Deep dive →
4. How would you group anagrams together?
Answer: hash map with a signature key: sort each word's letters (or count letter frequencies) to build a canonical key, and bucket words by it. O(n · k log k) with sorting, or O(n · k) with counting. The trap: comparing every pair of words is O(n²) — the signature key turns "are these anagrams?" into a single dict lookup. Deep dive →
5. How do you check whether parentheses are balanced (Valid Parentheses)?
Answer: stack: push opening brackets, pop on closing brackets and verify the match; the string is valid only if the stack ends empty. O(n) time and space. The trap: counting opens vs closes without a stack accepts ")(" — order matters, and only a stack tracks nesting. Deep dive →
6. How do you search in a rotated sorted array?
Answer: still binary search, with one extra decision: at each midpoint, determine which half is properly sorted, then check whether the target lies in that half. O(log n) time. The trap: the classic "is arr[mid] < target?" comparison breaks because the array isn't globally sorted — you must first find the sorted half. Deep dive →
7. How would you count islands in a grid (Number of Islands)?
Answer: DFS/BFS flood fill: scan every cell; when you hit unvisited land, flood-fill the whole island and increment the count. O(m·n) time, visiting each cell once. The trap: forgetting to mark cells visited causes infinite recursion or double-counting — mark on entry. And the Python-specific trap: CPython has a relatively low recursion limit by default, so deep recursion can raise RecursionError — an explicit stack (iterative DFS) is safer for potentially deep input. Deep dive →
8. Climbing Stairs / House Robber — how do you spot the DP?
Answer: the decision rule: the problem asks for an optimum over sequential choices with overlapping subproblems → dynamic programming. Define the recurrence first (e.g., dp[i] = max(dp[i-1], dp[i-2] + nums[i]) for House Robber), then compress to O(1) space with two variables. The trap: the naive recursion is exponential — memoization or bottom-up tabulation is the whole point, and interviewers want to hear you say "overlapping subproblems" out loud. Deep dive →
9. How do you merge overlapping intervals?
Answer: sort by start time, then sweep once, merging each interval into the running one when they overlap. O(n log n) dominated by the sort. The trap: trying to merge without sorting first leads to missed overlaps — sorting is what makes the single pass correct. Deep dive →
10. Container With Most Water — why two pointers?
Answer: two pointers from both ends, always moving the shorter side inward: the area is limited by the shorter line, so moving the taller side can never improve it. O(n) time, O(1) space. The trap: the O(n²) all-pairs check is the obvious brute force — the "move the shorter pointer" insight is the entire interview. Deep dive →
11. How would you design an LRU Cache?
Answer: hash map + doubly linked list: the dict gives O(1) lookup, the list tracks recency — move accessed nodes to the front, evict from the back. Both get and put are O(1). The Python-specific note: collections.OrderedDict (move_to_end/popitem) is another real implementation tool — but for the interview, implement the underlying dict + doubly linked list to demonstrate the data-structure reasoning. And functools.lru_cache is related but different: it caches function results, not the arbitrary key-value cache API this question asks you to design. Deep dive →
12. How do you find the Kth largest element without sorting everything?
Answer: min-heap of size K: push elements, pop whenever the heap exceeds K — the root is your answer. O(n log k) time, O(k) space. The trap: full sort is O(n log n) and fine for small inputs, but the heap wins on streams and huge arrays where K is small — and "quickselect" (average O(n)) is the follow-up they probe next. Deep dive →
Python Language & Data Model
13. What's wrong with def f(x, cache=[])?
Answer: Python's most famous trap: the default list is created once at function definition time, so every call shares the same list and mutations accumulate across calls. The fix is the None sentinel — def f(x, cache=None), then create the list inside. The trap answer they want: "defaults are evaluated at definition time, not call time." Deep dive →
14. Explain the GIL — and when does it actually matter in 2026?
Answer: in the traditional/default CPython runtime, the Global Interpreter Lock means only one thread executes Python bytecode at a time — that's why threads generally don't scale CPU-bound pure-Python work across cores, while they're still useful for I/O concurrency (threads release the GIL while waiting on I/O). Modern CPython also has free-threaded builds where the GIL can be disabled, so state which runtime/build you're discussing — don't present the GIL as an unconditional property of all Python execution. And don't reduce CPU parallelism to "use multiprocessing": the options include process pools, native libraries that release the GIL, free-threaded Python where appropriate, and distributing the work. The decision starts with the workload. Deep dive →
15. Write a decorator that takes arguments — what breaks without functools.wraps?
Answer: a decorator factory is three nested functions: retry(times) returns the decorator, which returns the wrapper. Without @functools.wraps(func), the decorated function presents the wrapper's metadata and can break introspection and tooling that expects the original function — wraps updates the wrapper's metadata and establishes __wrapped__ so inspection tools can recover the original callable and signature. The trap: candidates write the nesting correctly but forget wraps; interviewers treat that as the real test. Deep dive →
16. dataclass vs regular class vs namedtuple vs Pydantic model — when do you use each?
Answer: @dataclass generates __init__, __repr__, and equality for you — reach for it for data-carrying classes; frozen=True gives immutability, and field(default_factory=list) is how you dodge the mutable-default trap (straight back to Q13). A regular class when you need custom behavior and invariants; a namedtuple for tiny immutable records; a Pydantic model when you need validation and parsing at system boundaries. Decision rule: dataclasses for internal data carriers, Pydantic where untrusted input enters. Deep dive →
17. What are dunder methods — and __repr__ vs __str__?
Answer: dunders (double-underscore methods like __eq__, __len__) let your objects plug into Python's syntax and builtins. __repr__ should be unambiguous and developer-oriented — when practical, a constructor-like representation is useful, but that's a convention, not a requirement. __str__ is the human-friendly one. The trap: defining __eq__ without thinking about __hash__ — Python sets __hash__ to None when you define __eq__, silently breaking dict/set usage. Deep dive →
18. Why is is different from ==?
Answer: == tests value equality (via __eq__); is tests identity — whether two names point to the same object in memory. Use == for value equality; use is when identity itself is what you mean — most commonly is None. The trap: never rely on implementation interning or caching for value comparison — code that "works" for small ints and breaks for large ones is the classic bug. Don't memorize interning rules; just never depend on them. Deep dive →
19. Are Python type hints enforced at runtime?
Answer: no — Python remains dynamically typed; annotations are metadata consumed by static type checkers, IDEs, frameworks, and runtime validation libraries. The follow-ups interviewers probe: list[str] (parameterized generics), str | None (union syntax for optional), and Protocol — structural typing that gives you duck typing with static checking: "if it has these methods, it qualifies." Deep dive →
20. Instance method vs @classmethod vs @staticmethod — what's the real difference?
Answer: an instance method takes self and operates on the instance. A classmethod takes cls and operates on the class — the standard tool for alternative constructors (from_dict, from_json). A staticmethod takes neither — just a plain function namespaced under the class. The trap: using a staticmethod where a classmethod belongs breaks subclassing of alternative constructors, because the hardcoded class name won't follow the subclass. Deep dive →
21. What problem does a context manager solve — and how does with work?
Answer: with open(...) as f: guarantees deterministic setup and cleanup — the file closes even if the block raises. The protocol: __enter__ acquires and returns the resource, __exit__ releases it (and can suppress exceptions). For quick ones, @contextlib.contextmanager turns a generator into a context manager. The interview signal: reaching for with whenever you acquire something that must be released — files, locks, connections, temporary state. Deep dive →
22. When do else and finally execute — and what should you catch?
Answer: in try/except/else/finally: else runs only if no exception occurred; finally always runs. The more important half is judgment: catch specific exceptions you can meaningfully handle — don't blanket-catch Exception just to suppress failures, because you'll hide bugs and make debugging miserable. Let what you can't handle propagate. Deep dive →
Iterators, Generators & Functional Patterns
23. What's the difference between an iterable and an iterator?
Answer: the iterator protocol is two dunders: __iter__ returns the iterator object itself, __next__ returns the next item or raises StopIteration — that's the entire contract behind for loops. An iterable is reusable and defines __iter__ (lists, dicts); an iterator is single-pass and defines __next__ (generators, files). The trap: confusing the two — "can I loop over this twice?" is really asking "is it an iterable or an exhausted iterator?" Deep dive →
24. What makes generators special — are they faster?
Answer: a generator (a function with yield) is the easiest way to create an iterator — lazy, producing items one at a time instead of building a list. The classic use is processing data too large to fit in memory. The trap: "generators are faster" is wrong in general — they're lazier; the win is memory, and you can only iterate them once. Deep dive →
25. List comprehension vs generator expression — scope, evaluation, and memory?
Answer: [x for x in data] builds an eager list; (x for x in data) builds a lazy generator that computes nothing until iterated. In Python 3, comprehensions have their own scope, so the loop variable doesn't leak (unlike Python 2) — know that as the scope trap. The memory trap: passing a generator where a list is expected and iterating it twice — the second pass silently yields nothing. Deep dive →
26. What do *args and **kwargs actually do?
Answer: *args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dict. They're the mechanism behind flexible APIs and decorator wrappers (def wrapper(*args, **kwargs)). The trap: the names args/kwargs are pure convention — it's the * and ** that do the work; also know the reverse (unpacking at call time: f(*lst)). Deep dive →
Concurrency & Async
27. asyncio vs threads vs processes — how do you choose?
Answer: decide from the workload: many I/O operations with synchronous libraries → threads; many concurrent I/O operations with async-native libraries → asyncio; CPU-bound Python → processes / native parallelism / free-threaded where appropriate; mixed workload → separate the concurrency concerns. The trap: picking asyncio while using a blocking database driver — you pay the complexity cost of async and get the throughput of synchronous. Deep dive →
28. What does async/await actually do?
Answer: async def defines a coroutine — a function that can pause. await yields control back to the event loop while waiting, letting other coroutines run. It's single-threaded cooperative concurrency: nothing runs truly in parallel; coroutines interleave at await points. The trap: an async function with no awaits is just overhead — and calling a coroutine without await returns a coroutine object that never runs. Deep dive →
29. What happens if you call blocking code inside an async function?
Answer: you block the event-loop thread — every coroutine on that loop stalls until the blocking call returns. One time.sleep() or one blocking database/client call inside an async request handler freezes all unrelated requests sharing the loop. That's why async code demands async-native libraries for anything that waits — or explicit offloading to threads. This is a far more practical interview question than most syntax trivia. Deep dive →
30. How do you run CPU-bound or blocking work from async code?
Answer: offload it and keep the event loop for coordination: loop.run_in_executor (or anyio.to_thread) pushes blocking I/O to a thread pool; a process pool handles CPU-bound work. The trap: spawning unbounded threads from async code — bound the executor, or your "non-blocking" service dies from thread exhaustion instead. Deep dive →
31. How do threads communicate and share state safely?
Answer: use the right primitive: threading.Lock around compound operations on shared state, queue.Queue for thread-safe handoffs between producers and consumers. For processes there's no shared memory by default — communicate via queues/pipes and serialized messages instead. The trap: assuming "it's just one line" is safe (see next question). Deep dive →
32. Does the GIL prevent race conditions?
Answer: no — the GIL does not make operations atomic. A check-then-act sequence (if key not in d: d[key] = compute()) can still interleave between threads and race. The GIL protects the interpreter's internals, not your invariants. The rule: put a lock around compound operations on shared state, GIL or no GIL. Deep dive →
Testing, Typing & Production Python
33. In pytest, when do you use a fixture vs a mock — and what should you actually mock?
Answer: a fixture is reusable setup — dependency injection for tests (a test database, a sample payload). A mock replaces a collaborator you deliberately don't want to exercise: the network, the clock, a payment gateway. Mock at the boundary — the seam between your code and the outside world — and don't mock what you don't own or the logic you're actually testing. Over-mocked tests verify the mock, not the code. Deep dive →
34. How does Python manage memory — and what causes memory leaks?
Answer: reference counting frees objects the moment their count hits zero; a cyclic garbage collector reclaims reference cycles. Leaks come from references that never die: unbounded caches and lists, globals and listeners that accumulate, cycles involving __del__. Investigate with the gc module (gc.get_objects, gc.collect), tracemalloc for allocation hotspots, and objgraph for what's holding what. The standard fix for caches that shouldn't pin their values: weakref. Deep dive →
35. What is a virtual environment — and why do you pin dependencies?
Answer: a venv isolates a project's dependencies from the system and from other projects — no more "it worked on my machine." Pinning (pip freeze, pip-tools, uv lock) makes the environment reproducible: the same dependency tree in dev, CI, and production. The trap: deploying from unpinned requirements and debugging a transitive-dependency upgrade at 2 AM. Deep dive →
36. Your Python service is slow in production — where do you start?
Answer: profile before guessing: py-spy or cProfile for CPU hotspots — and first classify the bottleneck: CPU-bound, blocked on I/O, or waiting on a lock. Correlate with metrics and structured logs, then reproduce the hot path in a benchmark before optimizing. The interview signal: a methodical elimination, not "I'd rewrite it in a faster language." Deep dive →
37. Why does production Python use the logging module instead of print?
Answer: levels (debug/info/warning/error) let you filter by severity; handlers route output to files, aggregators, and alerting; structured formatting makes logs greppable and correlatable. print has none of that — no levels, no routing, and interleaved output under concurrency. The trap: print-debugging your way through a production incident at 3 AM. Deep dive →
Practical Gotchas
38. What does [lambda: i for i in range(3)] produce — and why [2, 2, 2]?
Answer: calling them gives [2, 2, 2], not [0, 1, 2] — each lambda closes over the variable i, not its value at creation time (late binding), and by call time i is 2 for all of them. The fix freezes the value with a default argument: lambda i=i: i. The trap: this bites in real code with callbacks and threads, not just puzzles — always bind loop variables you close over. Deep dive →
39. What happens with a = b = [] — the mutable aliasing trap?
Answer: both names point to the same list object, so a.append(1) also changes b — assignment never copies in Python. The same trap appears when passing a list into a function and mutating it. The fix: be explicit — b = a.copy() or list(a) when you want independence. The trap answer they want: "names are references, not boxes." Deep dive →
40. Shallow copy vs deep copy — when does it bite?
Answer: a shallow copy copies the outer container but shares the inner objects; deep copy (copy.deepcopy) recursively copies everything. It bites with nested structures — new = old[:] on a list of lists still aliases the inner lists. The trap: "I copied it, why did the original change?" — always ask how deep the nesting goes before choosing. Deep dive →
In this series
- Top 40 Python Interview Questions and Answers (2026 Edition) (this post) — start here.
- Python Coding Interview Patterns: Lists, Strings & Hash Maps — the essential patterns.
- Python Internals for Interviews: Decorators, Generators, GIL & OOP — how Python really works.
- Tricky Python Questions Interviewers Love — gotchas and edge cases.
- Top 50 Python DSA Problems by Pattern (With Solutions) — 50 problems, solved by pattern.
- 50 Python DSA Interview Questions & Answers — Solved by Pattern — the companion drill set.
Related: System Design Interviews: A Practical Primer — the 4-step framework these Python questions plug into.
Comments
Post a Comment