Python interview questions for 2026
These are short answers to common Python interview questions. The language is simple, but the answers include the details and traps that an interviewer may expect you to know.
1. How can we change the behaviour of a class?
We can add or override methods, inherit from another class, use composition, add a decorator, or change special methods such as __str__, __eq__, and __call__. Python also allows monkey patching at runtime, but it can make code difficult to understand and test.
class Animal:
def speak(self):
return "sound"
class Dog(Animal):
def speak(self):
return "woof" # Override inherited behaviour
2. What is the difference between async code and multithreading?
Async code normally runs many tasks on one thread. A task gives control back to the event loop while it waits for network or disk I/O. It is useful for servers and other programs with many waiting operations.
Multithreading uses several operating-system threads. It is also useful for I/O. Threads share memory, so they need locks when they change shared data. In the normal CPython build, the GIL limits parallel execution of Python code. For CPU-heavy work, processes are often a better choice. Free-threaded CPython builds are also available, but they are a separate build and compatibility must be checked.
3. What does the GIL block?
The Global Interpreter Lock allows only one thread at a time to execute Python bytecode in a normal CPython process. It does not stop all concurrency. A thread usually releases the GIL while it waits for I/O, and some C extensions release it during heavy work. The GIL does not protect your application data from race conditions.
4. How do we define comparison behaviour?
Use special methods: __eq__ for ==, __ne__ for !=, __lt__ for <, __le__ for <=, __gt__ for >, and __ge__ for >=. Return NotImplemented when the other object has an unsupported type.
class Person:
def __init__(self, age):
self.age = age
def __eq__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.age == other.age
def __lt__(self, other):
if not isinstance(other, Person):
return NotImplemented
return self.age < other.age
5. How does multiple inheritance work? What is the diamond problem?
The diamond appears when two parent classes inherit from the same base class, and a child inherits from both parents. Python uses the C3 Method Resolution Order (MRO) to choose where to find a method. You can inspect it with ClassName.mro(). Cooperative classes should call super(); this lets every class in the MRO run once.
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro())
# [D, B, C, A, object]
Python 2 had old-style and new-style classes. Python 3 has only new-style classes, and every class ultimately inherits from object.
6. How can instances of a class be dictionary keys?
A dictionary key must be hashable. Its hash must stay stable while it is in the dictionary, and equal objects must have equal hashes. Implement compatible __eq__ and __hash__ methods, or use an immutable frozen dataclass.
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
locations = {Point(10, 20): "home"}
If a class defines __eq__ but does not define __hash__, Python normally makes its instances unhashable. Do not calculate a hash from mutable fields.
7. Hashing, encryption, and hash-table collisions
Hashing converts data to a fixed-size value and is designed to be one-way. Encryption is reversible when you have the correct key. Passwords should be stored with a slow password-hashing function and a salt, not with ordinary fast hashing and not as reversible encrypted text.
A collision happens when two different keys have the same hash. Python dictionaries handle collisions by checking candidate entries and comparing their keys. Correctness is preserved, but too many collisions reduce performance.
8. Which methods make an object an iterator?
An iterator implements __iter__() and __next__(). __iter__() normally returns self. __next__() returns the next value and raises StopIteration when there are no more values.
class Countdown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current == 0:
raise StopIteration
value = self.current
self.current -= 1
return value
An iterable only needs __iter__(), which may return a separate iterator. A generator function creates an iterator automatically.
9. What is the speed of membership testing in a set?
value in my_set is O(1) on average because a set is a hash table. Python calculates the value's hash and goes directly to a small group of possible positions. The theoretical worst case is O(n) when many values collide. Membership testing in a list is O(n) because items are checked one by one.
10. What problems can serialization cause?
Common problems include unsupported object types, loss of type information, dates and binary data, circular references, large output, schema changes, and compatibility between application versions. Floating-point and character-encoding differences may also matter.
Never unpickle data from an untrusted source: pickle can execute code while loading. JSON is safer for data exchange, but the program still needs to validate all loaded values.
11. What is a callable class? Can a class be a decorator?
An instance is callable when its class defines __call__. A callable instance can be used as a decorator.
from functools import wraps
class Repeat:
def __init__(self, times):
self.times = times
def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(self.times):
result = func(*args, **kwargs)
return result
return wrapper
@Repeat(2)
def hello(name):
print(f"Hello, {name}")
12. What are database indexes? What are their advantages and costs?
An index is an extra data structure that helps a database find rows without scanning the full table. B-tree indexes are common and support equality, range queries, sorting, and prefix lookups. Hash indexes are mainly useful for equality.
Indexes make many reads faster, but they use disk space and memory. Every insert, delete, or update of indexed values must also update the index. Too many indexes slow writes and maintenance. An index stores indexed values plus a row reference; some indexes can also store extra columns and answer a query without reading the table.
13. What do *args and **kwargs mean?
In a function definition, *args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dictionary. The names args and kwargs are conventions; the stars define the behaviour.
def show(first, *args, **kwargs):
print(first) # required positional argument
print(args) # tuple
print(kwargs) # dict
show(1, 2, 3, colour="green")
14. Can one except block catch every exception?
Most application errors inherit from Exception, so except Exception as error catches them. BaseException is above Exception and also includes control signals such as KeyboardInterrupt, SystemExit, and GeneratorExit.
except BaseException can catch almost everything, but application code should rarely do this because it may stop the user from interrupting the program or prevent a clean exit. Catch the narrowest exceptions you can handle. Python calls this statement try/except, not try/catch.
15. What are __new__ and __init__?
__new__ creates and returns an instance. It runs first and receives the class as cls. __init__ then initializes the already created instance and receives it as self. __init__ must return None.
You normally customize __new__ when subclassing immutable types, controlling instance creation, or implementing a singleton. If __new__ returns an object that is not an instance of the requested class, Python does not call that class's __init__.
16. What are decorators? How can @property limit an attribute?
A decorator receives a function or class and returns a replacement. The @decorator syntax applies it when the decorated object is defined. Decorators are often used for logging, authorization, caching, validation, and registration.
@property lets a method be accessed like an attribute. A setter can validate a new value.
class Account:
def __init__(self, balance):
self.balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("balance cannot be negative")
self._balance = value
17. What happens when we try to change a string item?
s1 = "123"
s2 = s1
s2[0] = "6"
print(id(s1) == id(s2))
The assignment raises TypeError: 'str' object does not support item assignment. Strings are immutable. The print line is never reached. Before the error, both names refer to the same string object. To create a changed string, write s2 = "6" + s2[1:].
18. What happens when two names refer to the same list?
li1 = [1, 2, 3]
li2 = li1
li2[0] = 10
print(li1) # [10, 2, 3]
print(id(li1) == id(li2)) # True
Lists are mutable. Both names point to the same object, so a change through li2 is visible through li1.
19. What does this function change?
a1 = 1
a2 = "2"
a3 = ["3"]
a4 = 4
a5 = {5}
a6 = {6: 6}
def f2(a1, a2, a3, a4, a5, a6):
a1 = a1 + 1
a2 = a2 + "2"
a3 = a3.append("30")
a4 = a4 + 40
a5 = a5.add(50)
a6["60"] = 60
return a1, a2, a3, a4, a5, a6
result = f2(a1, a2, a3, a4, a5, a6)
print(a1) # 1
print(a2) # 2
print(a3) # ['3', '30']
print(a4) # 4
print(a5) # {5, 50}, order may differ
print(a6) # {6: 6, '60': 60}
print(result)
# (2, '22', None, 44, None, {6: 6, '60': 60})
Python passes object references by assignment. Integers and strings are immutable, so local reassignment does not change the caller's names. Lists, sets, and dictionaries are mutable, so their contents can change. list.append() and set.add() change an object in place and return None; therefore the local variables a3 and a5 become None after those lines.
Tuple trap: (4) and (40) are integers. A one-item tuple needs a comma: (4,) and (40,).
20. Do equal lists have the same identity?
li1 = [1, 2, 3]
li2 = [1, 2]
li3 = [3]
li4 = li2 + li3
print(li1 == li4) # True: equal values
print(id(li1) == id(li2 + li3)) # False
print(id(li1) == id(li4)) # False
The + operation creates a new list. == compares values, while is compares identity. In real code, prefer li1 is li4 to comparing their id() values.
21. What is the type of a starred unpacking target?
x, *_, y = (1, 2, 3, 4, 5)
print(x) # 1
print(_) # [2, 3, 4]
print(y) # 5
print(type(_)) # <class 'list'>
A starred target always receives a list. The name _ usually means “I do not plan to use this value,” but it is still a normal variable.
22. Write a decorator that prints a function name and all arguments
from functools import wraps
def print_call(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__)
print(args)
print(kwargs)
return func(*args, **kwargs)
return wrapper
@print_call
def f(*args, **kwargs):
print("hello from f function")
f("1", "2", "3", a="a1", b=["b1", "b2", "b3"], c="c1")
The correct attribute is func.__name__. functools.wraps preserves the original function's name and documentation, which helps debugging and introspection.
A final interview tip
Do not only give a definition. Give a small example, name one important limitation, and explain when you would use the feature. A clear two-minute answer is usually stronger than a long list of terms.