24 Python Interview Questions & Answers (2026): GIL, Decorators, Code

24 Python interview questions with sample answers: dict hashing, is vs ==, decorators, generators, the GIL vs asyncio, MRO, plus coding tasks and pandas.

By OphyAI Team • • Updated October 16, 2026 • 4740 words

Last updated: October 2026

Quick Answer

Python interviewers test whether you understand how the language behaves at runtime, not whether you can recite syntax: mutability and hashing, references versus values, functions as objects (decorators, closures, generators), the GIL and concurrency choices, the object model, and clean idiomatic code under time pressure. Rehearse explaining each concept aloud in OphyAI Interview Practice, and drill live coding rounds in OphyAI Coding Interview. Expect short concept questions, one or two coding problems, and, for data roles, pandas.

#QuestionWhat it tests
1Mutable vs immutable typesData model basics
2List vs tupleDesign and hashability
3How a dict worksHashing internals
4is vs ==Identity vs equality
5Shallow vs deep copyReference semantics
6Mutable default argumentsEvaluation timing
7DecoratorsFunctions as objects
8Generators and yieldLazy evaluation
9The GILConcurrency model
10Threads vs processes vs asyncioChoosing a tool

The Questions, With Sample Answers

1. “What is the difference between mutable and immutable types?”

Why they ask. Almost every Python bug story, from aliasing to dictionary keys, traces back to mutability.

Sample answer. “An immutable object cannot change after it is created. Integers, floats, strings, tuples, frozensets and bytes are immutable. Lists, dicts, sets and most user-defined objects are mutable. When I ‘modify’ a string, Python builds a new object and rebinds the name, so other references still see the old value. With a list, every name that points to it sees the change. That matters in three places: immutable objects can be dictionary keys because their hash cannot change, mutable arguments can be changed by the function I pass them to, and immutable objects are safer to share. One subtlety: a tuple is immutable, but a tuple containing a list can still have that list mutated.”

Weak answers. Saying a variable is immutable, or claiming tuples can never change in any way.

2. “When would you use a list versus a tuple?”

Why they ask. It checks whether you see beyond “tuples are read-only lists”.

Sample answer. “A list is a mutable sequence, usually of items of the same kind, that grows and shrinks: a queue of jobs, a list of results. A tuple is an immutable, fixed-size record where position has meaning, like a coordinate or a database row. Because a tuple of hashable items is hashable, I can use it as a dict key or put it in a set, which a list cannot do. Tuples are also slightly lighter in memory. When the positions need names, I use a NamedTuple or a frozen dataclass so the code reads as point.x rather than point[0].”

Weak answers. Only saying “tuples are faster” with no mention of hashability or intent.

3. “How does a Python dictionary work internally?”

Why they ask. It is the most common internals question and separates users from engineers.

Sample answer. “A dict is a hash table. For a key, Python calls hash() and uses the result to find a slot. If the slot is taken by a different key, CPython probes other slots until it finds the key or an empty slot, so collisions are resolved by open addressing rather than chaining. It then confirms the match with ==, which is why equal objects must have equal hashes. The table resizes as it fills to keep lookups fast, so average lookup, insert and delete are O(1), with a worst case of O(n). Since Python 3.7 the language guarantees that dicts preserve insertion order. Keys must be hashable, which is why a list cannot be a key.”

Weak answers. “It stores key-value pairs” with no hashing, collisions or complexity.

Prepare the internals questions out loud with a follow-up-asking mock in Interview Practice.

4. “What is the difference between is and ==?”

Why they ask. It is a fast filter for identity versus equality.

Sample answer. “== compares values by calling __eq__. is compares identity: whether two names point to the same object in memory. Two separate lists with the same contents are == but not is. I use is only for singletons, mainly x is None, which is what PEP 8 recommends. A common trap is that CPython caches small integers and some strings, so a is b can appear to work for small numbers and then fail for larger ones. That caching is an implementation detail, so I never rely on is for value comparison.”

a = [1, 2]; b = [1, 2]
a == b   # True
a is b   # False

Weak answers. Saying they are interchangeable, or using is to compare strings.

5. “What is the difference between a shallow copy and a deep copy?”

Why they ask. Nested data structures plus aliasing produce some of the most confusing production bugs.

Sample answer. “Assignment copies nothing; it just adds another name for the same object. A shallow copy, from copy.copy(), list(x) or slicing, creates a new outer container but keeps references to the same inner objects. So if I shallow-copy a list of lists and append to an inner list, the change shows up in both. A deep copy, from copy.deepcopy(), recursively copies the inner objects too, and it handles cycles with a memo dictionary. Deep copies cost more time and memory, so I use them only when I truly need an independent nested structure, and I prefer designing with immutable data where possible.”

Weak answers. Thinking b = a makes a copy.

6. “Why is a mutable default argument a bug?”

Why they ask. It tests whether you know when Python evaluates code.

Sample answer. “Default values are evaluated once, when the def statement runs, not on each call. So a default list is shared across every call that uses it, and items pile up between calls. The fix is to default to None and create the object inside the function.”

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

“The same timing rule explains why a default like datetime.now() is frozen at definition time. Linters such as Pylint and Ruff flag this pattern, and I treat it as a review blocker.”

Weak answers. Knowing the fix but not the reason (definition-time evaluation).

7. “What is a decorator, and can you write one?”

Why they ask. Decorators combine closures, first-class functions and the @ syntax, so one question covers three topics.

Sample answer. “Functions are objects, so a function can take a function and return a new one. A decorator is exactly that, and @timer above def f is shorthand for f = timer(f). I use functools.wraps so the wrapped function keeps its name and docstring, which matters for logging and debugging.”

import functools, time

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            print(f"{func.__name__}: {time.perf_counter() - start:.3f}s")
    return wrapper

“Real uses include caching with functools.lru_cache, retries, authorization checks in web frameworks and registering routes. A decorator that takes arguments adds one more layer: a function that returns the decorator.”

Weak answers. Describing decorators abstractly without being able to write one, or forgetting *args, **kwargs.

Practice writing small helpers like this against a timer in OphyAI Coding Interview, the Premium product for live coding rounds.

8. “What do *args and **kwargs do?”

Why they ask. It is a quick check of function signatures, and it leads into decorators and API design.

Sample answer. “*args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dict. The names are convention; the stars do the work. On the calling side, the same stars unpack: f(*my_list, **my_dict). I use them in wrappers and decorators that pass everything through, and in functions that genuinely accept a variable number of items. I avoid them in ordinary public APIs, because explicit parameters are easier to read, type-check and autocomplete. Python also supports keyword-only parameters after a bare *, and positional-only parameters before a /, which I use to make a signature harder to misuse.”

Weak answers. Saying args and kwargs are keywords.

9. “What is a generator, and why use yield?”

Why they ask. It tests lazy evaluation and memory awareness, which matter in data pipelines.

Sample answer. “A function containing yield returns a generator object. Each call to next() runs the body until the next yield, hands back a value, and pauses with its local state intact. When the function ends, the generator raises StopIteration. Generators produce values on demand, so I can process a 20 GB log file line by line in constant memory instead of loading it into a list. A generator expression, (x * x for x in nums), is the lazy cousin of a list comprehension. The catch is that a generator can be consumed only once, so if I need to iterate twice I either recreate it or materialize a list.”

Weak answers. Saying a generator “returns multiple values” with no mention of laziness or single use.

10. “What is the difference between an iterable and an iterator?”

Why they ask. It shows whether you understand what a for loop actually does.

Sample answer. “An iterable is anything that can hand out an iterator, through __iter__; lists, dicts, strings and files are iterables. An iterator is the object that tracks position and returns the next item from __next__, raising StopIteration when it is exhausted. A for loop calls iter() on the iterable, then calls next() until StopIteration. A list can be looped over many times because each loop gets a fresh iterator, while an iterator, including a generator, is used up after one pass. The itertools module builds on this protocol for lazy tools like chain, islice and groupby.”

Weak answers. Treating the two words as synonyms.

11. “What is a context manager?”

Why they ask. Resource handling is where sloppy code leaks files, locks and connections.

Sample answer. “A context manager is an object used with with that runs setup on entry and cleanup on exit, even when an exception occurs. Under the hood, with calls __enter__, runs the block, then calls __exit__ with any exception details; if __exit__ returns a true value the exception is suppressed. with open(path) as f: guarantees the file closes. I write my own either as a class with those two methods or, more often, with contextlib.contextmanager, where code before yield is setup and code in a finally after it is cleanup. Typical uses are database transactions, locks, temporary directories and timing blocks.”

Weak answers. Only knowing with open(...) and not why it is safer than calling close().

12. “What is the GIL?”

Why they ask. It is the classic Python concurrency question, and the answer changed recently.

Sample answer. “The Global Interpreter Lock is a mutex in CPython that lets only one thread execute Python bytecode at a time. It simplifies memory management, because reference counts do not need fine-grained locks. The practical result is that threads do not speed up CPU-bound pure Python code, but they work well for I/O-bound work, since the GIL is released while waiting on network or disk, and many C extensions such as NumPy release it during heavy computation. This is changing: PEP 703 added an experimental free-threaded build in Python 3.13, and Python 3.14 made that build officially supported, though it is still optional and not the default, and some C extensions need rebuilding for it.”

Weak answers. Saying Python cannot do concurrency at all, or that the GIL is gone in standard Python.

13. “Threading, multiprocessing or asyncio: how do you choose?”

Why they ask. It tests judgment, not definitions.

Sample answer. “I first ask whether the work is CPU-bound or I/O-bound. For CPU-bound pure Python work, I use multiprocessing or concurrent.futures.ProcessPoolExecutor, because separate processes each have their own interpreter and GIL; the cost is process start-up and pickling data between processes. For I/O-bound work with a moderate number of tasks, or with blocking libraries, a ThreadPoolExecutor is simple. For thousands of concurrent network calls, asyncio runs coroutines on a single-threaded event loop, switching at each await, which is cheap. The catch with asyncio is that one blocking call stalls the whole loop, so every library on the hot path must be async or pushed to a thread.”

Weak answers. Picking one tool for everything, or saying asyncio runs code in parallel.

When the interviewer pushes on tradeoffs like this, a voice mock in Interview Practice is the closest rehearsal, because it asks follow-up questions.

14. “When should you use a list comprehension?”

Why they ask. It tests idiomatic style and the judgment to stop.

Sample answer. “A comprehension builds a list, set or dict from an iterable in one readable expression, for example [u.email for u in users if u.active]. It is usually clearer than a loop with append and often a bit faster. I stop using one when it needs nested loops plus conditions, because it becomes harder to read than a plain loop. If I only need to iterate once or aggregate, I use a generator expression, as in sum(x * x for x in nums), to avoid building a list in memory. I do not use comprehensions for side effects like printing.”

Weak answers. Writing three-level nested comprehensions to look clever.

15. “Explain Python’s scope rules (LEGB).”

Why they ask. Closures, UnboundLocalError and global misuse all come from scope.

Sample answer. “Python resolves a name by searching Local, then Enclosing function scopes, then Global (module), then Built-in. The scope is decided at compile time: if a function assigns to a name anywhere in its body, that name is local everywhere in the function, which is why reading it before the assignment raises UnboundLocalError. To rebind a module-level name I would need global, and for an enclosing function’s variable, nonlocal. I rarely use global; passing values in and returning them is easier to test. Closures capture variables, not values, so lambdas created in a loop all see the loop variable’s final value unless I bind it as a default argument.”

Weak answers. Not knowing nonlocal, or being surprised by the loop-closure behavior.

16. “What is the difference between __new__ and __init__?”

Why they ask. It probes the object model beyond everyday classes.

Sample answer. “__new__ is a static method that creates and returns the new instance; __init__ receives that instance and initializes its attributes, returning None. In normal classes I only write __init__. I override __new__ when I subclass an immutable type such as int, str or tuple, because the value must be set at creation and cannot be changed afterward, or for patterns like caching instances. If __new__ returns an object that is not an instance of the class, __init__ is not called. Metaclasses are a further layer, and I reach for them rarely; class decorators or __init_subclass__ usually do the job.”

Weak answers. Calling __init__ the constructor and stopping there.

17. “What are dunder methods, and which ones have you implemented?”

Why they ask. It reveals whether you write Pythonic classes that work with built-in syntax.

Sample answer. “Dunder or ‘magic’ methods let my objects plug into Python’s syntax and built-ins. __repr__ and __str__ control how objects print, __eq__ and __hash__ control equality and use as dict keys, __lt__ enables sorting, __len__ and __getitem__ make a class behave like a sequence, and __enter__ and __exit__ make it a context manager. One rule I always remember: if I define __eq__, Python sets __hash__ to None unless I define it too, so the object becomes unhashable. For plain data classes I use @dataclass, which generates __init__, __repr__ and __eq__, and frozen=True adds hashing.”

Weak answers. Listing names without a use case.

18. “How does multiple inheritance work, and what is the MRO?”

Why they ask. It tests whether you can predict which method runs, especially with super().

Sample answer. “The Method Resolution Order is the sequence of classes Python searches for an attribute. Python computes it with the C3 linearization algorithm, which keeps each class before its parents and preserves the order bases are listed in. I can inspect it with ClassName.__mro__. super() does not mean ‘my parent’; it means ‘the next class in the MRO of the instance’, which is why cooperative multiple inheritance works when every class calls super().__init__(**kwargs). In practice I use multiple inheritance mainly for small mixins, and I prefer composition when classes carry state.”

Weak answers. Saying Python searches depth-first, or that super() always calls the direct parent.

19. “How do you handle exceptions well in Python?”

Why they ask. Error handling shows production maturity.

Sample answer. “I catch the narrowest exception I can handle, never a bare except:, which also catches KeyboardInterrupt and SystemExit. I keep the try block small so I know which line failed. else runs only if nothing was raised, and finally always runs for cleanup, though a context manager is often cleaner. When I translate a low-level error into a domain error, I use raise NewError(...) from err so the original traceback is preserved. Python favors EAFP, easier to ask forgiveness than permission, so try: d[key] is idiomatic, but I do not use exceptions for ordinary control flow on hot paths. I define custom exception classes for errors callers should handle differently.”

Weak answers. except Exception: pass.

20. “How does Python manage memory?”

Why they ask. It tests whether you can debug memory growth in long-running services.

Sample answer. “CPython frees most objects through reference counting: each object counts the references to it, and when the count reaches zero it is deallocated immediately. Reference counting cannot free cycles, such as two objects pointing at each other, so CPython also runs a cyclic garbage collector, exposed through the gc module, that finds unreachable groups and frees them. Other implementations like PyPy use different strategies, so I do not rely on immediate cleanup and close resources explicitly with with. To investigate memory growth I use tracemalloc to compare snapshots, look for caches or global lists that only grow, and use weakref for caches that should not keep objects alive.”

Weak answers. “Python has a garbage collector, so leaks are impossible.”

21. “What are type hints, and does Python enforce them?”

Why they ask. Most professional Python codebases now use them, and it is a good test of tooling awareness.

Sample answer. “Type hints, introduced by PEP 484, annotate parameters and return values, for example def total(prices: list[float]) -> float:. The interpreter does not enforce them at runtime; static checkers such as mypy or Pyright read them and flag mismatches before the code runs, and editors use them for autocomplete. Frameworks like Pydantic and FastAPI do read annotations at runtime to validate data, which is a library choice, not a language feature. I use Optional or X | None, TypedDict or dataclasses for structured data, and Protocol for duck-typed interfaces. I add hints at module boundaries first, where they give the most value.”

Weak answers. Claiming hints make Python statically typed or faster.

You can rehearse explaining the concept questions in this list in 2 minutes or less with a timed mock in OphyAI Interview Practice.

22. “Reverse a string, then check whether it is a palindrome.”

Why they ask. It is a warm-up that shows fluency, edge-case thinking and how you talk while coding.

Sample answer. “Slicing with a step of -1 reverses any sequence, so s[::-1] is the idiomatic answer, and ''.join(reversed(s)) is equivalent. For a palindrome check that ignores case and punctuation, I normalize first, then use two pointers so I can stop early and avoid an extra copy.”

def is_palindrome(s: str) -> bool:
    chars = [c.lower() for c in s if c.isalnum()]
    i, j = 0, len(chars) - 1
    while i < j:
        if chars[i] != chars[j]:
            return False
        i += 1; j -= 1
    return True

“Both run in O(n) time. I would mention the empty string and a single character, which return True, and ask whether Unicode normalization matters.”

Weak answers. Building the string with += in a loop and not discussing edge cases.

23. “Two-sum: return the indices of two numbers that add up to a target.”

Why they ask. It is the canonical test of trading memory for time with a dict.

Sample answer. “The brute force checks every pair, which is O(n squared). Instead I walk the list once, storing each value’s index in a dict. For each number I check whether its complement is already stored. That is O(n) time and O(n) space.”

def two_sum(nums: list[int], target: int) -> list[int]:
    seen = {}
    for i, n in enumerate(nums):
        if target - n in seen:
            return [seen[target - n], i]
        seen[n] = i
    return []

“Checking before inserting handles duplicates like [3, 3] with target 6 and stops me pairing a number with itself. I would confirm with the interviewer what to return when there is no answer.”

Weak answers. Jumping to the nested loop and never mentioning complexity.

Rehearse problems like this one against the clock in OphyAI Coding Interview, and see more worked problems in coding interview examples and solutions.

24. “Flatten an arbitrarily nested list.”

Why they ask. It tests recursion, generators and type checks in one problem.

Sample answer. “A recursive generator is the cleanest answer: yield items, and delegate to itself with yield from when an item is a list.”

def flatten(items):
    for item in items:
        if isinstance(item, (list, tuple)):
            yield from flatten(item)
        else:
            yield item

list(flatten([1, [2, [3, 4]], 5]))  # [1, 2, 3, 4, 5]

“I check for list and tuple explicitly, because a string is also iterable and would recurse forever on single characters. For very deep nesting, recursion can hit the interpreter’s recursion limit, so I would offer an iterative version with an explicit stack. Because it is a generator, it streams results without building intermediate lists.”

Weak answers. Handling only one level of nesting, or recursing into strings.

Bonus for data roles: “How do you filter, group and join data in pandas?”

Why they ask. Data analyst, data scientist and data engineer screens often move from core Python to pandas.

Sample answer. “I filter with boolean masks, df[df['amount'] > 100], and select with .loc for labels and .iloc for integer positions. To aggregate I use groupby, for example df.groupby('region')['amount'].agg(['sum', 'mean']). To join tables I use merge with an explicit how and on, and I check row counts afterward because a many-to-many key silently multiplies rows. I handle missing values deliberately with isna, fillna or dropna, depending on what a missing value means. For speed I prefer vectorized operations over apply with a Python function, which loops row by row. I also watch for the chained-assignment warning and assign with .loc.”

Weak answers. Iterating with iterrows for everything, or merging without checking for duplicate keys.

For the SQL half of a data screen, use the SQL interview questions page and the data analyst interview questions.

How to Practice These

Run a voice or text mock in OphyAI Interview Practice, which asks follow-up questions and gives a transcript and per-answer feedback, so you can tell whether your GIL or dict answer holds up under a second “why?”. For live coding and system design rounds, OphyAI Coding Interview (Premium) lets you screenshot a problem and get a streamed approach and code to study. For a live video interview, OphyAI Interview Copilot gives real-time suggestions on Zoom, Microsoft Teams and Google Meet, but only where the employer allows it; check the tracker of companies that allow AI in interviews and ask your recruiter. Start practicing →

A simple routine: pick five concept questions a day, answer each aloud in under two minutes, then write one coding problem from scratch in a plain editor without autocomplete. Pair this page with the technical interview prep guide for software engineers and the Java interview questions if your target role is polyglot.

Frequently Asked Questions

What Python topics come up most in interviews?

Interview guides and candidate reports most often cover data types and mutability, dict and set internals, list comprehensions, functions and decorators, generators, object-oriented features like dunder methods and inheritance, exceptions, and the GIL with concurrency. Coding rounds lean on dicts, sets, sorting and string handling. Data roles add pandas and NumPy.

Is Python a good language for coding interviews?

For most general software engineering interviews, yes, if the company lets you choose your language, which many large tech employers do. Python’s concise syntax means less typing, and built-ins like collections.Counter, defaultdict, heapq and bisect cover common patterns. Know their complexity, because interviewers will ask about it rather than accept them as magic.

Which Python version should I prepare for?

Prepare for a recent Python 3 release; Python 2 reached end of life in 2020 and is not interview material. Know f-strings, type hints, dataclasses, the walrus operator and match statements from Python 3.10. Being able to say that Python 3.13 introduced an experimental free-threaded build and 3.14 made it officially supported shows you follow the language.

How do I answer Python questions without sounding memorized?

Give a one-sentence definition, then a short example from code you wrote, then a tradeoff or a gotcha. For example, define a generator, mention a log or ETL job where it kept memory flat, then note that generators are single use. Interviewers follow up on your example, so pick one you can discuss for two minutes.

Do data science interviews ask the same Python questions?

Partly. Data science and analyst screens still ask core questions like mutability, comprehensions and dicts, but spend more time on pandas (filtering, groupby, merges, missing values), NumPy vectorization and SQL. Expect a take-home or notebook exercise in some processes. Software roles go deeper on internals, concurrency and object design instead.

How long does it take to prepare for a Python interview?

If you already write Python daily, one to two weeks of focused practice on concepts plus a daily coding problem is a common plan. If you are coming from another language, allow three to four weeks to get idiomatic. These are typical ranges from prep guides, not a rule; adjust based on the role’s level and the number of rounds.

Can I use AI during a Python coding interview?

Only if the employer allows it. Rules vary by company and by round, and some online assessments are proctored. Read the invitation, ask your recruiter, and check the tracker of employers and their AI rules. Practicing with AI tools beforehand, to get feedback on your explanations and code, is always allowed.

Sources and verification notes

Sources checked October 2026. Direct page fetches failed from the publishing environment, so the Python 3.14 release facts were confirmed through search summaries of python.org and the PEP 779 discussion; the remaining language facts come from the standard Python documentation linked below and should be verified against it. The sample answers are original templates, not employer-published questions.

Tags:

python interview questions coding interview technical interview software engineer interview data science interview

Turn the advice into a realistic practice session

Run a role-specific mock interview, review feedback across four scoring areas, and repeat the answers that need work.