50 Python Interview
Questions & Answers
========================================================
----------------------------------------------------------------
SECTION 1: BASICS
----------------------------------------------------------------
Q1. What is Python? What are its key features?
Ans:
Python is a high-level, interpreted, dynamically-typed, general-purpose programming
language known for its simple and readable syntax.
Key Features:
- Interpreted (no compilation needed)
- Dynamically typed (no need to declare variable types)
- Supports OOP, functional, and procedural programming
- Large standard library
- Automatic memory management via garbage collection
- Platform independent (write once, run anywhere)
----------------------------------------------------------------
Q2. What is the difference between a compiled and an interpreted
language? Where does Python fall?
Ans:
A compiled language (like C, C++) converts source code into machine code before
execution. An interpreted language (like Python) executes code line by line at runtime
via an interpreter.
Python is an interpreted language, which makes it slower in raw performance but faster
to write, test, and debug.
----------------------------------------------------------------
Q3. What are Python's built-in data types?
Ans:
- Numeric : int, float, complex
- Sequence : str, list, tuple, range
- Mapping : dict
- Set : set, frozenset
- Boolean : bool
- Binary : bytes, bytearray, memoryview
- None : NoneType
----------------------------------------------------------------
Q4. What is the difference between list and tuple?
Ans:
A list is mutable — its elements can be changed after creation.
A tuple is immutable — once created, it cannot be modified.
Code:
my_list = [1, 2, 3]
my_list[0] = 99 # Works fine
my_tuple = (1, 2, 3)
my_tuple[0] = 99 # TypeError: 'tuple' object does not support item assignment
Use tuples when data should not change (e.g., coordinates, RGB values). Tuples are
also faster than lists.
----------------------------------------------------------------
Q5. What is the difference between == and is?
Ans:
== checks value equality — whether two objects have the same value.
is checks identity — whether two variables point to the exact same object in memory.
Code:
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True (same value)
print(a is b) # False (different objects in memory)
c=a
print(a is c) # True (same object)
----------------------------------------------------------------
Q6. What is a None type in Python?
Ans:
None is Python's null value. It represents the absence of a value or result. Its type is
NoneType. Functions that don't explicitly return anything return None by default.
Code:
def greet():
print("Hello")
result = greet()
print(result) # None
print(type(result)) # <class 'NoneType'>
----------------------------------------------------------------
Q7. What are mutable and immutable objects in Python?
Ans:
Mutable objects can be changed after creation.
Immutable objects cannot be changed after creation.
Mutable : list, dict, set, bytearray
Immutable : int, float, str, tuple, bool, frozenset
Code:
# String is immutable
s = "hello"
s[0] = "H" # TypeError
# List is mutable
lst = [1, 2, 3]
lst[0] = 99 # Works fine -> [99, 2, 3]
----------------------------------------------------------------
Q8. What is the difference between deep copy and shallow copy?
Ans:
Shallow copy creates a new object but references the same nested objects inside.
Deep copy creates a completely new object and recursively copies all nested objects.
Code:
import copy
original = [[1, 2], [3, 4]]
shallow = [Link](original)
shallow[0][0] = 99
print(original) # [[99, 2], [3, 4]] <- original affected!
original2 = [[1, 2], [3, 4]]
deep = [Link](original2)
deep[0][0] = 99
print(original2) # [[1, 2], [3, 4]] <- original NOT affected
----------------------------------------------------------------
SECTION 2: FUNCTIONS
----------------------------------------------------------------
Q9. What is the difference between *args and **kwargs?
Ans:
*args allows a function to accept any number of positional arguments — received as a
tuple.
**kwargs allows any number of keyword arguments — received as a dictionary.
Code:
def demo(*args, **kwargs):
print(args) # (1, 2, 3)
print(kwargs) # {'name': 'Sameer', 'role': 'Analyst'}
demo(1, 2, 3, name="Sameer", role="Analyst")
----------------------------------------------------------------
Q10. What is a lambda function?
Ans:
A lambda is an anonymous, single-expression function defined with the lambda
keyword. It is used for short, throwaway functions.
Code:
square = lambda x: x ** 2
print(square(5)) # 25
# Commonly used with map, filter, sorted
nums = [3, 1, 4, 1, 5]
print(sorted(nums, key=lambda x: -x)) # [5, 4, 3, 1, 1]
----------------------------------------------------------------
Q11. What are map(), filter(), and reduce() functions?
Ans:
map() - applies a function to every element of an iterable.
filter() - returns elements for which the function returns True.
reduce() - cumulatively applies a function to reduce the iterable to a single value (from
functools).
Code:
from functools import reduce
nums = [1, 2, 3, 4, 5]
print(list(map(lambda x: x**2, nums))) # [1, 4, 9, 16, 25]
print(list(filter(lambda x: x % 2 == 0, nums))) # [2, 4]
print(reduce(lambda x, y: x + y, nums)) # 15
----------------------------------------------------------------
Q12. What is a decorator in Python?
Ans:
A decorator is a function that takes another function as input, adds extra behavior to it,
and returns the modified function. It uses the @ syntax and follows the concept of
higher-order functions.
Code:
def log_call(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
result = func(*args, **kwargs)
print("Done")
return result
return wrapper
@log_call
def add(a, b):
return a + b
add(3, 5)
# Calling add
# Done
----------------------------------------------------------------
Q13. What is a generator in Python? How is it different from a regular
function?
Ans:
A generator is a function that uses yield instead of return. It produces values one at a
time and pauses execution between yields — making it memory-efficient for large
datasets.
Code:
def count_up(n):
for i in range(n):
yield i
gen = count_up(5)
print(next(gen)) # 0
print(next(gen)) # 1
# vs a list which stores everything in memory at once
----------------------------------------------------------------
Q14. What is the difference between return and yield?
Ans:
return terminates a function and sends back a value.
yield pauses the function, saves its state, and produces a value — execution resumes
from that point on the next call.
Code:
def using_return():
return [1, 2, 3] # All values in memory at once
def using_yield():
for i in [1, 2, 3]:
yield i # One value at a time (lazy evaluation)
----------------------------------------------------------------
Q15. What is a closure in Python?
Ans:
A closure is a function that remembers the variables from its enclosing scope even after
that scope has finished executing.
Code:
def outer(msg):
def inner():
print(msg) # 'msg' remembered even after outer() exits
return inner
greet = outer("Hello, Sameer!")
greet() # Hello, Sameer!
----------------------------------------------------------------
SECTION 3: OBJECT-ORIENTED PROGRAMMING (OOP)
----------------------------------------------------------------
Q16. What are the four pillars of OOP in Python?
Ans:
1. Encapsulation - Bundling data and methods inside a class, restricting direct access
using private/protected attributes.
2. Abstraction - Hiding implementation details and showing only essential features.
3. Inheritance - A child class inheriting properties and methods from a parent class.
4. Polymorphism - The same method name behaving differently based on the object.
----------------------------------------------------------------
Q17. What is the difference between __init__ and __new__?
Ans:
__new__ is called first and is responsible for creating and returning a new instance of
the class.
__init__ is called after and is responsible for initializing the newly created instance.
Code:
class MyClass:
def __new__(cls):
print("__new__ called -- object created")
return super().__new__(cls)
def __init__(self):
print("__init__ called -- object initialized")
obj = MyClass()
# __new__ called -- object created
# __init__ called -- object initialized
----------------------------------------------------------------
Q18. What is the difference between class method, static method, and
instance method?
Ans:
Instance method - takes self, works on instance data.
Class method - takes cls, works on class-level data. Decorated with @classmethod.
Static method - takes neither self nor cls, is a plain utility function inside the class.
Decorated with @staticmethod.
Code:
class Employee:
company = "TechCorp"
def __init__(self, name):
[Link] = name
def greet(self): # Instance method
print(f"Hi, I'm {[Link]}")
@classmethod
def get_company(cls): # Class method
return [Link]
@staticmethod
def is_valid_name(name): # Static method
return isinstance(name, str)
----------------------------------------------------------------
Q19. What is Method Resolution Order (MRO) in Python?
Ans:
MRO defines the order in which Python looks for a method in a class hierarchy during
multiple inheritance. Python uses the C3 Linearization algorithm. You can inspect it
using ClassName.__mro__ or mro().
Code:
class A:
def hello(self): print("A")
class B(A):
def hello(self): print("B")
class C(A):
def hello(self): print("C")
class D(B, C):
pass
D().hello() # B -- follows MRO
print(D.__mro__) # D -> B -> C -> A -> object
----------------------------------------------------------------
Q20. What is the difference between __str__ and __repr__?
Ans:
__str__ - human-readable representation, used by print().
__repr__ - official, unambiguous representation for debugging, used in REPL and repr().
Code:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
p = Point(3, 4)
print(str(p)) # Point(3, 4)
print(repr(p)) # Point(x=3, y=4)
----------------------------------------------------------------
SECTION 4: DATA STRUCTURES & COMPREHENSIONS
----------------------------------------------------------------
Q21. How does a Python dict work internally?
Ans:
A dictionary is implemented as a hash table. When you set a key-value pair, Python
computes the hash of the key, finds the appropriate bucket, and stores the pair there.
Key lookups are O(1) on average. Keys must be hashable (immutable types like str, int,
tuple).
----------------------------------------------------------------
Q22. What is a list comprehension? How does it differ from a for loop?
Ans:
A list comprehension is a concise way to create a list in a single line. It is faster than an
equivalent for loop because it is optimized at the C level in CPython.
Code:
# For loop
squares = []
for i in range(10):
[Link](i ** 2)
# List comprehension
squares = [i ** 2 for i in range(10)]
# With condition
evens = [i for i in range(20) if i % 2 == 0]
----------------------------------------------------------------
Q23. What is a dictionary comprehension?
Ans:
Just like list comprehension but creates a dictionary.
Code:
words = ["apple", "banana", "cherry"]
word_lengths = {word: len(word) for word in words}
# {'apple': 5, 'banana': 6, 'cherry': 6}
----------------------------------------------------------------
Q24. What is the difference between remove(), pop(), and del for lists?
Ans:
remove(value) - removes the first occurrence of the specified value.
pop(index) - removes and returns the item at the given index (default: last item).
del - removes an item by index or deletes the variable entirely.
Code:
lst = [10, 20, 30, 40]
[Link](20) # [10, 30, 40]
[Link]() # Returns 40 -> [10, 30]
del lst[0] # [30]
----------------------------------------------------------------
Q25. What is the difference between set and frozenset?
Ans:
set - mutable, you can add or remove elements.
frozenset - immutable and hashable, can be used as a dict key or as an element of
another set.
Code:
s = {1, 2, 3}
[Link](4) # Works
fs = frozenset([1, 2, 3])
[Link](4) # AttributeError -- immutable
d = {fs: "frozen"} # Valid as dict key
----------------------------------------------------------------
SECTION 5: ERROR HANDLING
----------------------------------------------------------------
Q26. What is the difference between Exception and BaseException?
Ans:
BaseException is the base class for ALL built-in exceptions, including system-exit events like
KeyboardInterrupt and SystemExit.
Exception is a subclass of BaseException and the base for all regular, non-fatal exceptions.
Always catch Exception, not BaseException, unless you specifically need to handle system exit
events.
----------------------------------------------------------------
Q27. What is the purpose of else and finally in try-except?
Ans:
else - runs only if NO exception was raised in the try block.
finally - always runs, regardless of whether an exception occurred. Used for cleanup (closing
files, DB connections, etc.).
Code:
try:
result = 10 / 2
except ZeroDivisionError:
print("Error!")
else:
print(f"Result: {result}") # Runs because no exception
finally:
print("This always runs") # Always executes
----------------------------------------------------------------
Q28. How do you create a custom exception?
Ans:
Inherit from the Exception class and optionally override __init__ to add custom behavior.
Code:
class InvalidAgeError(Exception):
def __init__(self, age):
[Link] = age
super().__init__(f"Age {age} is not valid. Must be 0-120.")
def validate_age(age):
if age < 0 or age > 120:
raise InvalidAgeError(age)
try:
validate_age(-5)
except InvalidAgeError as e:
print(e) # Age -5 is not valid. Must be 0-120.
----------------------------------------------------------------
SECTION 6: FILE HANDLING
----------------------------------------------------------------
Q29. How do you read and write files in Python?
Ans:
Use the built-in open() function with mode flags:
'r' - read
'w' - write (overwrites)
'a' - append
'rb'/'wb' - binary modes
Always use the with statement so the file is closed automatically.
Code:
# Write
with open("[Link]", "w") as f:
[Link]("Hello, Python!\n")
# Read line by line
with open("[Link]", "r") as f:
for line in f:
print([Link]())
# Read entire file
with open("[Link]", "r") as f:
content = [Link]()
----------------------------------------------------------------
Q30. What is the difference between read(), readline(), and readlines()?
Ans:
read() - reads the entire file as a single string.
readline() - reads one line at a time.
readlines() - reads all lines and returns a list of strings.
Code:
with open("[Link]") as f:
print([Link]()) # Entire file as string
with open("[Link]") as f:
print([Link]()) # First line only
with open("[Link]") as f:
print([Link]()) # ['line1\n', 'line2\n', ...]
----------------------------------------------------------------
SECTION 7: MODULES & PACKAGES
----------------------------------------------------------------
Q31. What is the difference between a module and a package?
Ans:
Module - a single .py file containing Python code (functions, classes, variables).
Package - a directory containing multiple modules along with an __init__.py file.
Structure:
mypackage/
|-- __init__.py
|-- [Link]
|-- [Link]
Code:
from mypackage import utils
from [Link] import User
----------------------------------------------------------------
Q32. What does if __name__ == "__main__": mean?
Ans:
__name__ is a special variable. When a file is run directly, __name__ equals "__main__". When
it is imported as a module, __name__ equals the module's file name.
This block ensures certain code runs only when the script is executed directly, not when
imported.
Code:
def add(a, b):
return a + b
if __name__ == "__main__":
print(add(3, 5)) # Only runs when file is executed directly
----------------------------------------------------------------
SECTION 8: ITERATORS & ITERABLES
----------------------------------------------------------------
Q33. What is the difference between an iterable and an iterator?
Ans:
Iterable - any object that can be looped over (has __iter__ method). Example: list, tuple, string.
Iterator - an object that has both __iter__ and __next__ methods. It remembers its current
position.
Code:
lst = [1, 2, 3] # Iterable
it = iter(lst) # Convert to iterator
print(next(it)) #1
print(next(it)) #2
print(next(it)) #3
print(next(it)) # StopIteration
----------------------------------------------------------------
Q34. How do you create a custom iterator?
Ans:
Implement __iter__ and __next__ methods in a class.
Code:
class Counter:
def __init__(self, limit):
[Link] = limit
[Link] = 0
def __iter__(self):
return self
def __next__(self):
if [Link] >= [Link]:
raise StopIteration
[Link] += 1
return [Link]
for num in Counter(3):
print(num) # 1, 2, 3
----------------------------------------------------------------
SECTION 9: MEMORY & PERFORMANCE
----------------------------------------------------------------
Q35. How does Python manage memory?
Ans:
Python uses a private heap space to store all objects and data structures. The memory
manager handles allocation. The garbage collector uses reference counting as its primary
mechanism — when an object's reference count drops to zero, it is immediately deallocated.
Python also uses a cyclic garbage collector to handle circular references.
----------------------------------------------------------------
Q36. What are Python's __slots__?
Ans:
By default, Python stores instance attributes in a __dict__ per object, which uses extra memory.
Defining __slots__ tells Python to use a fixed-size array instead of a dict, saving significant
memory when creating many instances.
Code:
class WithoutSlots:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# WithSlots uses significantly less memory per instance
----------------------------------------------------------------
Q37. What is the GIL (Global Interpreter Lock) in Python?
Ans:
The GIL is a mutex that allows only one thread to execute Python bytecode at a time, even on
multi-core systems. This means multi-threading in Python does not achieve true CPU
parallelism for CPU-bound tasks. However, I/O-bound tasks still benefit from threading because
the GIL is released during I/O operations. For true CPU parallelism, use multiprocessing
instead.
----------------------------------------------------------------
SECTION 10: ADVANCED CONCEPTS
----------------------------------------------------------------
Q38. What is a context manager? How do you create one?
Ans:
A context manager manages resources using the with statement. It guarantees cleanup even if
an error occurs. You can create one using a class with __enter__ and __exit__, or using the
@contextmanager decorator.
Code:
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name, 'w')
try:
yield f
finally:
[Link]()
with open_file("[Link]") as f:
[Link]("Hello!")
----------------------------------------------------------------
Q39. What is monkey patching?
Ans:
Monkey patching means dynamically modifying or replacing methods or attributes of a class or
module at runtime — typically used for testing or quick fixes.
Code:
class Dog:
def bark(self):
return "Woof!"
def silent_bark(self):
return "..."
[Link] = silent_bark # Monkey patching at runtime
d = Dog()
print([Link]()) # ...
----------------------------------------------------------------
Q40. What is the difference between @staticmethod and a module-level
function?
Ans:
Functionally they are similar, but a static method is logically grouped inside a class — making
code more organized when the function is conceptually related to the class but doesn't need
access to self or cls. A module-level function exists outside any class.
----------------------------------------------------------------
Q41. What are dunder (magic) methods?
Ans:
Dunder methods (double underscore methods like __add__, __len__, __str__) are special
methods Python calls automatically in specific situations. They allow you to define how objects
behave with built-in operations.
Code:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other): # Enables v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __len__(self): # Enables len(v)
return int((self.x**2 + self.y**2) ** 0.5)
def __str__(self): # Enables print(v)
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
----------------------------------------------------------------
Q42. What is functools.lru_cache and why is it useful?
Ans:
lru_cache is a decorator that caches the results of a function so repeated calls with the same
arguments return the cached result instantly instead of recomputing. LRU stands for Least
Recently Used.
Code:
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # Instant -- without cache this would take ages
----------------------------------------------------------------
Q43. What is the difference between threading and multiprocessing in
Python?
Ans:
threading - creates multiple threads within the same process and same memory space. Due
to the GIL, threads don't achieve true CPU parallelism. Best for I/O-bound tasks (network calls,
file reads).
multiprocessing - creates separate processes each with their own memory and GIL, achieving
true CPU parallelism. Best for CPU-bound tasks (data processing, image manipulation).
----------------------------------------------------------------
Q44. What are enumerate() and zip() and when do you use them?
Ans:
enumerate() - adds a counter to an iterable.
zip() - combines multiple iterables element-by-element into tuples.
Code:
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits, start=1):
print(index, fruit)
# 1 apple
# 2 banana
# 3 cherry
names = ["Sameer", "Riya"]
scores = [95, 89]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# Sameer: 95
# Riya: 89
----------------------------------------------------------------
Q45. What is the * (unpacking) operator used for?
Ans:
The * operator unpacks iterables and ** unpacks dictionaries. Commonly used in function calls
and to merge collections.
Code:
a = [1, 2, 3]
b = [4, 5, 6]
merged = [*a, *b] # [1, 2, 3, 4, 5, 6]
d1 = {"a": 1}
d2 = {"b": 2}
merged_dict = {**d1, **d2} # {'a': 1, 'b': 2}
def add(x, y, z):
return x + y + z
nums = [1, 2, 3]
print(add(*nums)) #6
----------------------------------------------------------------
SECTION 11: PYTHON INTERNALS
----------------------------------------------------------------
Q46. What is the difference between is and == for small integers?
Ans:
Python caches small integers (typically -5 to 256) as singleton objects. So is may return True for
small integers even without explicit assignment to the same variable, but this is an
implementation detail and should never be relied upon.
Code:
a = 100
b = 100
print(a is b) # True (cached singleton)
x = 1000
y = 1000
print(x is y) # False (different objects)
Always use == to compare values and is only to check identity (e.g., if x is None).
----------------------------------------------------------------
Q47. What is __init__.py and why is it needed?
Ans:
__init__.py is a file that marks a directory as a Python package. It can be empty or contain
initialization code for the package. Without it (before Python 3.3), the directory would not be
treated as a package. In Python 3.3+, namespace packages can exist without it, but __init__.py
remains the standard and preferred approach.
----------------------------------------------------------------
Q48. What is the difference between sorted() and .sort()?
Ans:
.sort() - a list method that sorts in-place and returns None.
sorted() - a built-in function that works on any iterable and returns a new sorted list without
modifying the original.
Code:
lst = [3, 1, 4, 1, 5]
new = sorted(lst) # Returns new list
print(lst) # [3, 1, 4, 1, 5] -- unchanged
[Link]() # Modifies in-place
print(lst) # [1, 1, 3, 4, 5]
# Both support key and reverse parameters
sorted(lst, key=lambda x: -x)
----------------------------------------------------------------
Q49. What is __call__ in Python?
Ans:
Defining __call__ in a class makes its instances callable just like a regular function. Useful for
building function-like objects that maintain state.
Code:
class Multiplier:
def __init__(self, factor):
[Link] = factor
def __call__(self, value):
return value * [Link]
double = Multiplier(2)
print(double(5)) # 10
print(double(9)) # 18
----------------------------------------------------------------
Q50. What is the difference between raise, raise Exception, and raise from?
Ans:
raise alone - re-raises the currently active exception (used inside except blocks).
raise Exception(...) - raises a new exception.
raise X from Y - chains exceptions, explicitly linking the new one to the original cause —
preserving full context in tracebacks.
Code:
try:
int("abc")
except ValueError as e:
raise RuntimeError("Conversion failed") from e
# Traceback shows:
# ValueError: invalid literal for int()... (original cause)
# The above exception was the direct cause of the following:
# RuntimeError: Conversion failed
================================================================
END OF DOCUMENT
50 Python Interview Q&A | Academy Datalab |
================================================================
Follow instagram: @academy_datalab
Whatsapp No.: 9520369037 (write “pandas” in mssg)
For ebook →Data Cleaning in Pandas: Complete
Guide (49 Rs./–)
📊 How to Clean Data in Pandas – Practical Guide for Data Analysts
Master one of the most important skills in Data Analytics: Data Cleaning.
This practical ebook teaches you how to clean messy real-world datasets using
Python Pandas with step-by-step explanations, ready-to-use code snippets, and
real-world examples. Learn how to handle missing values, remove duplicates, fix
data types, clean text columns, detect outliers, perform feature engineering, and
export analysis-ready datasets.
🎯 What You'll Learn:
• Handling Missing Values
• Removing Duplicate Records
• Fixing Data Types
• Cleaning Text Columns
• Standardizing Column Names
• Detecting & Handling Outliers
• Feature Engineering
• Exporting Clean Data
🎁 Bonus Included:
• Data Cleaning Checklist
• Interview Questions & Answers
• Practice Dataset
• Real-World Pandas Code Examples
👨💻 Who Is This For?
• Aspiring Data Analysts
• Students Learning Python
• Data Science Beginners
• Freshers Preparing for Interviews
• Anyone Working with Excel or CSV Data
🛠 Tools Covered:
• Python
• Pandas
• NumPy
• Jupyter Notebook
📄 Format: PDF
📚 Length: 31 Pages
📈 Level: Beginner to Intermediate
Clean Data → Better Analysis → Better Career Opportunities 🚀
Created by Academy Datalab
Email: [Link]@[Link]