Software Engineer | 53 questions | 2026
Python Interview Questions and Answers
Python interview questions for freshers 25
Q1FresherMost askedAnswerHideWhat is the difference between a list and a tuple in Python?
A list is mutable and a tuple is immutable. That is the core difference, and everything else follows from it.
| list | tuple | |
|---|---|---|
| Syntax | [1, 2, 3] | (1, 2, 3) |
| Mutable | Yes (append, remove, assign by index) | No |
| Hashable | No | Yes, if all items are hashable |
| Use as dict key or set member | No | Yes |
| Typical use | Homogeneous collection that changes | Fixed record, e.g. (x, y) or a function return of several values |
- Tuples are slightly smaller and faster to create, and they signal "this does not change".
- A tuple containing a list is still not fully frozen:
t = (1, [2])allowst[1].append(3). - A single-item tuple needs a comma:
(5,).
What they are checking: Whether you know mutability matters (hashing, dict keys), not just the bracket style
Q2FresherMost askedAnswerHideWhat are mutable and immutable types in Python? Give examples.
A mutable object can be changed in place after creation, an immutable one cannot. Changing an immutable value really creates a new object.
- Immutable:
int,float,bool,str,tuple,frozenset,bytes. - Mutable:
list,dict,set,bytearray, and most user-defined class instances.
s = "abc"
print(id(s))
s += "d" # new string object, id changes
print(id(s))
l = [1, 2]
print(id(l))
l.append(3) # same object, id unchanged
print(id(l))
Why it matters: Mutable objects can be changed by anyone who holds them. This causes hard-to-find bugs (for example mutable default arguments). Immutable objects are safe to share and can be dict keys.
What they are checking: Understanding of object identity, and what it means for function arguments and hashing
Q3FresherMost askedAnswerHideWhat is the difference between `is` and `==` in Python?
== compares values (it calls __eq__). is compares identity: do both names point to the same object in memory?
a = [1, 2]
b = [1, 2]
print(a == b) # True, equal values
print(a is b) # False, two different objects
c = a
print(c is a) # True
- Use
==for value comparison almost always. - Use
isfor singletons:x is None,x is True. - Do not use
isfor numbers or strings. CPython keeps small integers (-5 to 256) and some strings in a cache. Soismay seem to work, then fail on larger values.
What they are checking: Whether you avoid the small-integer cache trap and know to use
is None
Q4FresherMost askedAnswerHideWhat are `*args` and `**kwargs`?
They let a function accept a variable number of arguments. *args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dict. The names are convention, the stars are the syntax.
def show(a, *args, **kwargs):
print(a, args, kwargs)
show(1, 2, 3, x=10, y=20) # 1 (2, 3) {'x': 10, 'y': 20}
nums = [1, 2, 3]
opts = {"sep": "-"}
print(*nums, **opts) # unpacking at the call site: 1-2-3
Order in a signature: positional parameters, *args, keyword-only parameters, **kwargs. They are common in decorators and wrappers that must forward any arguments to the wrapped function.
What they are checking: Can you use packing and unpacking, and do you know the parameter order
Q5FresherMost askedAnswerHideWhat is a decorator in Python and how does it work?
A decorator is a callable that takes a function and returns a new function. It usually adds work before or after the original call. @decorator above a function is shorthand for func = decorator(func).
import functools, time
def timed(fn):
@functools.wraps(fn)
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return fn(*args, **kwargs)
finally:
print(f"{fn.__name__} took {time.perf_counter() - start:.4f}s")
return wrapper
@timed
def work(n):
return sum(range(n))
work(1_000_000)
Points to mention: closures keep a reference to fn. functools.wraps preserves the name and docstring. Decorators are applied at definition time. Typical uses are logging, timing, caching, auth checks and retries.
What they are checking: Closures, first-class functions and whether you remember functools.wraps
Q6FresherMost askedAnswerHideWhat are generators in Python? How is `yield` different from `return`?
A generator is a function that contains yield. Calling it returns a generator object (an iterator). It gives values lazily, one at a time. It pauses between values and keeps its local state. return ends the function for good, yield suspends it and can be resumed.
def countdown(n):
while n > 0:
yield n
n -= 1
g = countdown(3)
print(next(g)) # 3
print(list(g)) # [2, 1]
- Memory use stays small, so generators suit large or endless streams, such as reading a huge file line by line.
- A generator is exhausted after one pass. Iterating again gives nothing.
- A generator expression
(x*x for x in range(10))is the compact form.
What they are checking: Lazy evaluation and memory benefits, plus the one-pass trap
Q7FresherMost askedAnswerHideWhat is the difference between shallow copy and deep copy?
A shallow copy creates a new outer container but reuses references to the inner objects. A deep copy recursively copies everything, so the result shares nothing with the original.
import copy
a = [[1, 2], [3, 4]]
s = copy.copy(a) # shallow, same as a[:] or list(a)
d = copy.deepcopy(a)
a[0].append(99)
print(s) # [[1, 2, 99], [3, 4]] inner list is shared
print(d) # [[1, 2], [3, 4]] independent
- Plain assignment
b = ais not a copy. It is a second name for the same object. - Shallow copy is fine for flat structures of immutable items.
- Deep copy is slower and handles cycles through a memo dict; classes can change it with
__deepcopy__.
What they are checking: Whether nested mutable objects are understood, not only the vocabulary
Q8FresherMost askedAnswerHideWhat are dunder (magic) methods? Explain `__str__` vs `__repr__`.
Dunder methods are special methods with double underscores. Python calls them to run built-in behaviour: __init__ (construction), __len__ (len()), __getitem__ (indexing), __eq__ (==), __add__ (+), __enter__/__exit__ (with).
__str__ is the readable, user-facing text used by print() and str(). __repr__ is clear text for developers, used in the REPL, logs and containers. It should ideally let you rebuild the object.
class P:
def __init__(self, x): self.x = x
def __repr__(self): return f"P(x={self.x!r})"
def __str__(self): return f"Point at {self.x}"
p = P(3)
print(p) # Point at 3
print([p]) # [P(x=3)] containers use repr
If only __repr__ is defined, str() falls back to it, so define __repr__ first.
What they are checking: Operator overloading awareness and the repr/str purpose
Q9FresherMost askedFree with sign-inAnswerHideHow does exception handling work in Python? Explain try, except, else and finally.
Code that may fail goes in try. A matching except handles the error. else runs only if no exception happened. finally always runs, for cleanup.
Q10FresherMost askedFree with sign-inAnswerHideWhat is a virtual environment in Python and why do you use one?
A virtual environment is an isolated folder with its own Python and its own installed packages. Each project keeps its own package versions. Other projects and the system Python are not affected.
Q11FresherMost askedFree with sign-inAnswerHideWhat is the output of this code, and why? `def add(x, items=[]): items.append(x); return items` called as `add(1)`, `add(2)`.
It returns [1] and then [1, 2]. The default list is created once, when the function is defined, not on each call. So every call without the argument shares the same list.
Q12FresherMost askedFree with sign-inAnswerHideWhat are the built-in data types in Python?
Python has these core built-in types:
- Numeric:
int(any size),float,complex, plusbool(a subclass of int). - Text:
str(immutable Unicode), and binarybytes,bytearray. - Sequences:
list,tuple,range. - Mappings:
dict. - Sets:
set,frozenset. - None:
NoneType, the single valueNone.
Q13FresherMost askedFree with sign-inAnswerHideWhat does `if __name__ == '__main__':` do?
It runs a block only when the file is run directly (python script.py). It does not run when the file is imported as a module. When Python runs a file as the entry point it sets __name__ to "__main__". When imported, __name__ is the module's name.
Q14FresherMost askedFree with sign-inAnswerHideWhat is the difference between an iterable and an iterator?
An iterable is any object you can loop over: it implements __iter__ which returns an iterator. An iterator is the object that does the traversal: it implements __next__ (and __iter__ returning itself) and raises StopIteration when finished.
Q15FresherMost askedFree with sign-inAnswerHideWhat is the difference between a list comprehension and a generator expression?
A list comprehension builds the whole list in memory at once. A generator expression makes items lazily, one by one. The syntax differs only in brackets.
Q16FresherMost askedFree with sign-inAnswerHideWhat are lambda, map and filter in Python? Show how they relate to comprehensions.
A lambda is a small anonymous function limited to a single expression. map(func, iterable) applies a function to every item, and filter(func, iterable) keeps items where the function returns truthy. Both return lazy iterators in Python 3.
Q17FresherMost askedFree with sign-inAnswerHideWhat are the time complexities of common operations on lists, sets and dicts?
Typical CPython average cases:
Q18FresherMost askedFree with sign-inAnswerHideWhat is the difference between `@staticmethod`, `@classmethod` and an instance method?
- Instance method: takes
self, works on one object's data. @classmethod: takescls, works on the class itself, commonly used as alternative constructors.@staticmethod: takes neither, is just a function namespaced inside the class.
Q19FresherMost askedFree with sign-inAnswerHideAre arguments passed by value or by reference in Python?
Neither exactly. Python passes object references by value, often called "pass by assignment" or "pass by object reference". The parameter becomes a new name bound to the same object.
Q20FresherMost askedFree with sign-inAnswerHideHow do properties work in Python? How do you achieve encapsulation?
Python has no strict private members, so privacy is by convention. @property lets you expose an attribute through getter and setter logic. Callers still use plain attribute syntax.
Q21FresherMost askedFree with sign-inAnswerHideIs Python interpreted or compiled? What is bytecode and what is CPython?
Both, in a sense. CPython (the reference implementation) first compiles source code into bytecode (.pyc files cached in __pycache__). Then the Python Virtual Machine runs that bytecode.
Q22FresherMost askedFree with sign-inAnswerHideWhy does `print(0.1 + 0.2 == 0.3)` print False, and how do you compare floats properly?
Floats use binary (IEEE 754) representation, and values like 0.1 and 0.2 cannot be stored exactly. The sum is 0.30000000000000004, which differs slightly from 0.3.
Q23FresherMost askedFree with sign-inAnswerHideExplain how `sort()` and `sorted()` differ and how the `key` parameter works.
list.sort() sorts the list in place and returns None. sorted() works on any iterable and returns a new list, leaving the original unchanged. Both are stable (equal items keep their original order) and use Timsort, O(n log n).
Q24FresherMost askedFree with sign-inAnswerHideWhat is the difference between list.sort() and sorted() in Python?
list.sort() sorts the list in place and returns None. sorted() returns a new sorted list and works on any iterable, leaving the original unchanged.
Q25FresherMost askedFree with sign-inAnswerHideHow do you sort a dictionary by its values, or by a key inside nested values?
Sort the dictionary's items with a key function and rebuild a dictionary from the result. Since Python 3.7 dictionaries keep insertion order, so the rebuilt dictionary stays sorted.
Python interview questions for experienced 20
Q26ExperiencedMost askedAnswerHideWhat is the GIL and how does it affect multithreading in Python?
The Global Interpreter Lock is a lock in CPython. It lets only one thread run Python bytecode at a time. It protects CPython's internal state, such as reference counts, from race conditions.
Effects:
- CPU-bound Python code gains no speed from threads, since they take turns on one core.
- I/O-bound code still benefits, because a thread releases the GIL while waiting on sockets, files or
time.sleep. - Many C extensions (NumPy, hashing, compression) release the GIL during heavy work.
- Race conditions on your own data are still possible. You still need locks for steps like
counter += 1.
Ways around it: multiprocessing or concurrent.futures.ProcessPoolExecutor for CPU-bound work, asyncio or threads for I/O, native extensions. Python 3.13 added an experimental free-threaded build (PEP 703) where the GIL can be turned off. The default build still has it.
What they are checking: Accurate detail: I/O threads work, CPU threads do not, and the GIL is not a substitute for locks
Q27ExperiencedMost askedAnswerHideExplain threading vs multiprocessing vs asyncio. When would you use each?
All three give concurrency but with different models.
| threading | multiprocessing | asyncio | |
|---|---|---|---|
| Unit | OS threads, one process | Separate processes | Coroutines on one thread |
| GIL limit | Yes | No, each process has its own | Single thread anyway |
| Memory | Shared | Separate (needs pickling or shared memory) | Shared |
| Overhead | Medium | High | Very low |
| Best for | Blocking I/O with libraries that are not async | CPU-bound work | Thousands of concurrent network connections |
- Threading: preemptive switching, so races are possible and locks are needed.
- Multiprocessing: real parallelism across cores, but data moves through IPC and startup costs more.
- Asyncio: cooperative; tasks switch only at
await, so a blocking call (likerequests.get) freezes everything. Use async libraries orasyncio.to_thread.
Rule of thumb: For CPU-bound work use processes. For many network calls use asyncio. For a few blocking calls in old code use a thread pool.
What they are checking: Ability to map a workload to the right concurrency model and know the failure modes
Q28ExperiencedMost askedFree with sign-inAnswerHideExplain async and await in Python. What is an event loop?
async def defines a coroutine. await pauses it until another awaitable finishes. Meanwhile the event loop runs other tasks. The event loop is the scheduler: it runs on a single thread and switches between coroutines at await points.
Q29ExperiencedMost askedFree with sign-inAnswerHideHow does a Python dictionary work internally?
A dict is a hash table. For each key, Python computes hash(key) and uses it to pick a slot in an index array. It handles collisions by probing for another free slot. Then it stores the key and value.
Q30ExperiencedMost askedFree with sign-inAnswerHideHow does memory management and garbage collection work in Python?
CPython manages memory automatically with reference counting plus a cyclic garbage collector.
Q31ExperiencedMost askedFree with sign-inAnswerHideWhat is a context manager and how does the `with` statement work?
A context manager defines setup and cleanup around a block. with calls __enter__ on entry and always calls __exit__ on exit, even after an exception. So resources are released reliably.
Q32ExperiencedMost askedFree with sign-inAnswerHideWhat are type hints in Python? Are they enforced at runtime?
Type hints are annotations that declare the expected types of variables, parameters and return values. They are not enforced by the interpreter at runtime. They exist for static checkers (mypy, pyright), IDEs and documentation.
Q33ExperiencedMost askedFree with sign-inAnswerHideWhat is a dataclass in Python and why use it?
@dataclass (module dataclasses, since 3.7) writes repeated methods for you in classes that mainly hold data: __init__, __repr__, __eq__, and optionally ordering and hashing.
Q34ExperiencedMost askedFree with sign-inAnswerHideWhat does this print? `funcs = [lambda: i for i in range(3)]; print([f() for f in funcs])`
It prints [2, 2, 2], not [0, 1, 2]. Closures capture the variable i, not its value at creation time (late binding). By the time the lambdas are called, the loop has finished and i is 2.
Q35ExperiencedMost askedFree with sign-inAnswerHideExplain Method Resolution Order (MRO) and multiple inheritance in Python.
MRO is the order in which Python searches base classes for an attribute or method. It uses the C3 algorithm. Each class comes before its parents, and the order of the bases you list is kept.
Q36ExperiencedMost askedFree with sign-inAnswerHideHow do you write a decorator that takes arguments?
You need three nested layers: an outer function that takes the decorator's arguments, and a decorator that takes the function. The third layer is a wrapper that runs on each call.
Q37ExperiencedMost askedFree with sign-inAnswerHideWhat is memoization and how does `functools.lru_cache` work?
Memoization saves the result of a function for given arguments. Repeated calls return the saved value instead of recomputing. functools.lru_cache does this with a bounded Least Recently Used cache.
Q38ExperiencedMost askedFree with sign-inAnswerHideHow do you test Python code? Compare unittest and pytest, and explain mocking.
Both run automated tests. unittest ships in the standard library with class-based TestCase and self.assertEqual style. pytest is the de facto standard: plain functions, plain assert statements with rich failure output, fixtures, parametrisation and a large plugin ecosystem.
Q39ExperiencedMost askedFree with sign-inAnswerHideWhat is the difference between `__init__` and `__new__`?
__new__ creates the instance (it is a static method that receives the class and returns a new object). __init__ then sets up that new instance. Python calls __new__ first. It calls __init__ only if __new__ returns an instance of the class.
Q40ExperiencedMost askedFree with sign-inAnswerHideWhat does this print? `a = 256; b = 256; print(a is b); x = 1000; y = 1000; print(x is y)`
In a normal CPython session, typed separately, the first line prints True and the second usually prints False. CPython creates and caches the integers from -5 to 256, so both names point to the same object. Larger integers are created as new objects.
Q41ExperiencedMost askedFree with sign-inAnswerHideWhat are the walrus operator and structural pattern matching, and when are they useful?
The walrus operator := (3.8) assigns a value inside an expression. match/case (3.10) does structural pattern matching.
Q42ExperiencedMost askedFree with sign-inAnswerHideWhat are abstract base classes and duck typing in Python?
Duck typing means the methods an object has decide if it fits, not its declared class. The idea: "if it walks like a duck and quacks like a duck". Abstract base classes (ABCs) set a clear contract. They force subclasses to implement certain methods.
Q43ExperiencedMost askedFree with sign-inAnswerHideHow do you create custom exceptions, and what is exception chaining?
Subclass Exception (not BaseException) and give it a meaningful name, optionally with extra data. Chaining keeps the original cause visible.
Q44ExperiencedMost askedFree with sign-inAnswerHideWhat does this function return, and why?
def f():
try:
return 1
finally:
return 2
It returns 2. The finally block always runs, even when the try block has already hit a return. A return inside finally replaces the earlier return value.
Q45ExperiencedFree with sign-inAnswerHideWhat is `__slots__` and when would you use it?
__slots__ declares a fixed set of attributes for a class. Instances then store them in a compact array instead of a per-instance __dict__. This saves memory and makes attribute access a little faster.
Scenario based Python interview questions 8
Q46ExperiencedMost askedScenarioFree with sign-inAnswerHideA Python function is too slow in production. How do you find and fix the bottleneck?
Measure first, then optimise the part that matters.
Q47ExperiencedMost askedScenarioFree with sign-inAnswerHideYou need to call 100 external APIs and combine the results. How would you do it efficiently?
This is I/O-bound. Run the calls at the same time with asyncio (or a thread pool if the client library is blocking). Also limit how many run at once.
Q48ExperiencedMost askedScenarioFree with sign-inAnswerHideHow would you process a 20 GB log file in Python without running out of memory?
Stream it instead of loading it. File objects are lazy iterators over lines, so memory stays flat.
Q49ExperiencedMost askedScenarioFree with sign-inAnswerHideA long-running Python service keeps growing in memory. How do you investigate a memory leak?
Confirm it is a leak (memory rising steadily and never plateauing), then find what holds references.
- Take snapshots with
tracemallocand compare them to see which lines allocate the growing objects:
Q50ExperiencedMost askedScenarioFree with sign-inAnswerHideYou must process 10 million records that need heavy CPU computation. How do you speed it up in Python?
CPU-bound work is limited by the GIL, so use processes or vectorisation, not threads.
- First check if the work can be vectorised with NumPy or pandas. This moves loops into C and is often 10 to 100 times faster.
- Otherwise split the data into chunks and use
ProcessPoolExecutor:
Q51ExperiencedMost askedScenarioFree with sign-inAnswerHideTwo projects on the same machine need different versions of the same library. How do you manage dependencies so they do not conflict?
Give each project its own virtual environment and pin its dependencies.
Q52ExperiencedMost askedScenarioFree with sign-inAnswerHideHow do you write a retry decorator with exponential backoff for unreliable network calls?
Wrap the call in a loop. Catch only errors worth retrying, wait longer each time, add jitter (a small random delay), and give up after a limit.
Q53ExperiencedMost askedScenarioFree with sign-inAnswerHideSeveral threads update a shared counter and the final total is wrong. Why does this happen and how do you fix it?
A process has its own memory space, a thread shares memory with others in the same process. Threads are lighter, but shared data needs synchronisation.
Safe ways to share:
queue.Queue: thread-safe producer-consumer channel, the preferred approach.threading.Lockaround read-modify-write sequences.