Python Interview Questions
Top 25 Python Interview Questions
Curated questions covering Python fundamentals, OOP, generators, decorators, async/await, and modern Python features.
What is the difference between mutable and immutable types in Python?
- Mutable — can be changed after creation: list, dict, set, bytearray.
- Immutable — cannot be changed after creation: int, float, str, tuple, frozenset, bytes.
- Immutable objects are hashable and can be used as dictionary keys.
# Mutable
lst = [1, 2, 3]
lst[0] = 99 # OK
# Immutable
t = (1, 2, 3)
# t[0] = 99 # TypeError
What is the difference between list, tuple, set, and dict?
- list — ordered, mutable, allows duplicates. []
- tuple — ordered, immutable, allows duplicates. ()
- set — unordered, mutable, no duplicates. {}
- dict — ordered (Python 3.7+), mutable, key-value pairs, unique keys. {}
lst = [1, 2, 2, 3]
tpl = (1, 2, 2, 3)
st = {1, 2, 2, 3} # {1, 2, 3}
dct = {"a": 1, "b": 2}
What are list comprehensions in Python?
List comprehensions provide a concise way to create lists. They are faster than equivalent for loops and support optional filtering.
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
# Dict comprehension
word_len = {word: len(word) for word in ["hello", "world"]}
# Set comprehension
unique_squares = {x**2 for x in [-2, -1, 0, 1, 2]}
What are generators in Python?
Generators are functions that use yield to produce values lazily, one at a time. They are memory-efficient for large sequences because they do not store all values in memory.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
gen = fibonacci()
print(next(gen)) # 0
print(next(gen)) # 1
print(next(gen)) # 1
# Generator expression
squares = (x**2 for x in range(1000000)) # no memory overhead
What are decorators in Python?
A decorator is a function that wraps another function to extend its behaviour without modifying it. They are applied with the @ syntax.
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.1)
slow_function() # slow_function took 0.1001s
What is the difference between *args and **kwargs?
*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dictionary. Both allow functions to accept a variable number of arguments.
def show(*args, **kwargs):
print(args) # tuple of positional args
print(kwargs) # dict of keyword args
show(1, 2, 3, name="Alice", age=25)
# (1, 2, 3)
# {"name": "Alice", "age": 25}
What are lambda functions in Python?
Lambda functions are anonymous, single-expression functions. They are useful for short callbacks and functional programming patterns.
square = lambda x: x ** 2
print(square(5)) # 25
nums = [3, 1, 4, 1, 5, 9]
nums.sort(key=lambda x: -x) # sort descending
print(nums) # [9, 5, 4, 3, 1, 1]
What are map(), filter(), and reduce() in Python?
- map(func, iterable) — applies func to each element; returns a map object.
- filter(func, iterable) — keeps elements where func returns True.
- reduce(func, iterable) — cumulatively applies func to reduce to a single value (from functools).
from functools import reduce
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x*2, nums)) # [2,4,6,8,10]
evens = list(filter(lambda x: x%2==0, nums)) # [2,4]
total = reduce(lambda a, b: a+b, nums) # 15
How does OOP work in Python?
Python supports OOP with classes, inheritance, polymorphism, and encapsulation. Methods receive self as the first parameter. Python uses name mangling (__attr) for private attributes.
class Animal:
def __init__(self, name: str):
self.name = name
def speak(self) -> str:
return "..."
class Dog(Animal):
def speak(self) -> str:
return f"{self.name} says Woof!"
d = Dog("Rex")
print(d.speak()) # Rex says Woof!
What are dunder (magic) methods in Python?
Dunder methods (double underscore) define how objects behave with built-in operations.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __len__(self):
return 2
v = Vector(1, 2) + Vector(3, 4)
print(v) # Vector(4, 6)
print(len(v)) # 2
What is the @property decorator?
@property turns a method into a read-only attribute. Combined with @setter and @deleter, it provides controlled access to instance attributes.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0: raise ValueError("Radius must be non-negative")
self._radius = value
c = Circle(5)
print(c.radius) # 5
c.radius = 10
What is the difference between @classmethod and @staticmethod?
- @classmethod — receives the class (cls) as the first argument; can access/modify class state; used for alternative constructors.
- @staticmethod — receives no implicit first argument; does not access class or instance state; a utility function scoped to the class.
class Date:
def __init__(self, y, m, d):
self.y, self.m, self.d = y, m, d
@classmethod
def from_string(cls, s):
y, m, d = map(int, s.split("-"))
return cls(y, m, d)
@staticmethod
def is_leap(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
d = Date.from_string("2024-01-15")
print(Date.is_leap(2024)) # True
What are context managers and the with statement?
Context managers handle setup and teardown automatically. The with statement calls __enter__ on entry and __exit__ on exit (even if an exception occurs). Used for file handling, locks, and database connections.
# Built-in context manager
with open("file.txt", "r") as f:
content = f.read()
# file is automatically closed
# Custom context manager
from contextlib import contextmanager
@contextmanager
def timer():
import time
start = time.time()
yield
print(f"Elapsed: {time.time()-start:.3f}s")
with timer():
sum(range(1000000))
What is the GIL (Global Interpreter Lock) in Python?
The GIL is a mutex in CPython that allows only one thread to execute Python bytecode at a time. It simplifies memory management but limits true parallelism for CPU-bound tasks. Use multiprocessing for CPU-bound work; threading is still effective for I/O-bound tasks.
What is the difference between multithreading and multiprocessing in Python?
- threading — multiple threads in one process; share memory; limited by GIL for CPU-bound tasks; good for I/O-bound tasks.
- multiprocessing — multiple processes with separate memory; bypasses GIL; good for CPU-bound tasks; higher overhead.
from multiprocessing import Pool
def square(n): return n * n
with Pool(4) as p:
results = p.map(square, range(10))
print(results) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
What is async/await in Python?
async/await enables asynchronous programming using coroutines. An async function returns a coroutine; await suspends execution until the awaited coroutine completes. asyncio is the standard event loop.
import asyncio
async def fetch(url: str) -> str:
await asyncio.sleep(1) # simulate I/O
return f"Data from {url}"
async def main():
results = await asyncio.gather(
fetch("https://api.example.com/a"),
fetch("https://api.example.com/b"),
)
print(results)
asyncio.run(main())
What are type hints in Python?
Type hints (PEP 484) annotate variables and function signatures with expected types. They are not enforced at runtime but enable static analysis with tools like mypy and improve IDE support.
from typing import Optional, List
def greet(name: str, times: int = 1) -> str:
return (f"Hello, {name}! " * times).strip()
def find_user(user_id: int) -> Optional[dict]:
return None # or a user dict
def process(items: List[int]) -> List[int]:
return [x * 2 for x in items]
What are dataclasses in Python 3.7+?
dataclasses automatically generate __init__, __repr__, and __eq__ methods from class annotations, reducing boilerplate for data-holding classes.
from dataclasses import dataclass, field
@dataclass
class Point:
x: float
y: float
label: str = "origin"
@dataclass(frozen=True) # immutable
class Config:
host: str = "localhost"
port: int = 8080
p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0, label='origin')
What is the walrus operator (:=) in Python 3.8+?
The walrus operator assigns a value to a variable as part of an expression, reducing redundant calls.
import re
# Without walrus
data = get_data()
if data:
process(data)
# With walrus
if data := get_data():
process(data)
# Useful in while loops
while chunk := file.read(8192):
process(chunk)
What are virtual environments in Python?
Virtual environments create isolated Python environments with their own packages, preventing dependency conflicts between projects. Created with python -m venv env and activated with source env/bin/activate (Unix) or env\Scripts\activate (Windows).
What is exception handling in Python?
Python uses try/except/else/finally blocks. Multiple except clauses handle different exception types. raise re-raises or raises new exceptions.
try:
result = 10 / int(input("Enter divisor: "))
except ZeroDivisionError:
print("Cannot divide by zero")
except ValueError as e:
print(f"Invalid input: {e}")
else:
print(f"Result: {result}") # runs if no exception
finally:
print("Done") # always runs
What are modules and packages in Python?
- Module — a single .py file containing Python code (functions, classes, variables).
- Package — a directory containing an __init__.py file and one or more modules.
- Import with import module, from package import module, or from module import function.
What is the difference between deep copy and shallow copy in Python?
A shallow copy creates a new object but references the same nested objects. A deep copy recursively copies all nested objects.
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0][0] = 99
print(shallow[0][0]) # 99 — shared reference
print(deep[0][0]) # 1 — independent copy
What are Python 3.10+ structural pattern matching features?
Python 3.10 introduced match/case statements for structural pattern matching, similar to switch/case but more powerful — supporting sequence patterns, mapping patterns, class patterns, and guards.
def handle_command(command):
match command.split():
case ["quit"]:
return "Quitting"
case ["go", direction]:
return f"Going {direction}"
case ["get", item, *rest]:
return f"Getting {item}"
case _:
return "Unknown command"
What are Python 3.12 improvements?
- Improved error messages — more precise and helpful tracebacks.
- f-string improvements — support for reuse of quotes, multi-line expressions, and backslashes inside f-strings.
- Type parameter syntax (PEP 695) — cleaner generic class/function syntax: class Stack[T]: ...
- Per-interpreter GIL (PEP 684) — each sub-interpreter can have its own GIL, enabling true parallelism.