#1 India's Top IT Training Institute

×

About Uncodemy

Know more about who we are and what we stand for.

About Us

About Us

Discover our mission to equip individuals with essential IT skills and industry expertise.

Read More →
UnCodeMy Gallery

UnCodeMy Gallery

Explore highlights from our IT training sessions and success stories.

Read More →
×

Free Resources

Learn, practice and plan your career — bilkul free.

Tutorials

Tutorials

Step-by-step written tutorials to learn any technology at your own pace.

Read More →
Free Counselling

Free Counselling

Book a free video counselling session and pick the right course for you.

Read More →
Online Compiler

Online Compiler

Write, run and test your code online — no setup required.

Read More →
Python Interview Prep · 2026-27 Edition

Python Interview
Questions and answers with code.

63+ Python interview questions and answers covering core concepts, OOPs, generators, decorators, exception handling, plus a 15-program coding round and 8 pandas and NumPy questions for data analyst roles. Every code snippet is Python 3, complete, and shown with its expected output.

0
Questions
0
Coding programs
0
Pandas / NumPy
0
Day prep plan
Scroll
Written by Mr. Syed Najeeb · Technically reviewed by Mr. Upendra Kumar Tiwari (Senior Data Science Trainer, 15+ yrs) · Last updated 14 October 2026
01
Before you begin

What interviewers actually check in a Python interview

Python interviews are not a memory test on syntax. Interviewers are checking whether you understand how Python works under the hood — memory model, iterators, generators, the GIL — and whether you can write clean, Pythonic code on a whiteboard or shared editor. The six areas below come up again and again.

01

Core language fundamentals

Interpreted vs compiled, dynamic typing, mutable vs immutable, list vs tuple vs set vs dict, is vs ==, pass/continue/break, f-strings and type hints.

02

Functions and functional tools

*args and **kwargs, lambda, map/filter/reduce, list and dict comprehensions, closures, default argument pitfalls, and scope rules (LEGB).

03

OOPs and design

Classes, inheritance, MRO, polymorphism, encapsulation, dunder methods, @staticmethod vs @classmethod, abstract classes, and composition over inheritance.

04

Iterators, generators and decorators

__iter__ and __next__, yield, generator expressions, lazy evaluation, decorators with and without arguments, functools.wraps, and context managers with the with statement.

05

Exceptions, memory and the GIL

try/except/else/finally, custom exceptions, reference counting, cyclic garbage collection, the Global Interpreter Lock, and when to use threads vs processes vs asyncio.

06

Data analysis and the coding round

pandas read_csv, groupby, merge, pivot_table, missing values, apply, datetime handling, and the classic coding problems — reverse a string, palindrome, Fibonacci, two-sum, and more.

How to use this page: pick the level that matches your experience first, work through those questions, then attempt the coding round and the pandas section. Open each question, try to answer it yourself, and only then read the model answer. For every code snippet, type it out and run it — reading Python is not the same as writing it.
Download this page as PDF
02
Beginner · 20 questions

Python interview questions for freshers

These are the questions that come up first in Python fresher interviews. They test whether your fundamentals are solid — answer with a short definition, then one line of code or an example.

Q01 What are the key features of Python? BeginnerFundamentals +
  • Interpreted — code runs line by line through the interpreter, no separate compile step.
  • Dynamically typed — you do not declare types; the type is attached to the object.
  • High-level and readable — indentation defines blocks, syntax is close to English.
  • Object-oriented and functional — supports classes, inheritance, plus first-class functions and closures.
  • Batteries included — a large standard library (os, json, re, collections, itertools).
  • Cross-platform — the same code runs on Windows, macOS, Linux and most embedded systems.
  • Extensible — call C/C++ libraries through ctypes, cffi or Cython for performance.
Q02 What is the difference between a list and a tuple? BeginnerData Types +
Aspect List Tuple
Mutability Mutable — can add, remove, change items Immutable — cannot be changed after creation
Syntax [1, 2, 3] (1, 2, 3)
Performance Slightly slower Faster and uses less memory
Use as dict key No — unhashable Yes — if all elements are hashable
Typical use Collection that changes — a shopping cart Fixed record — coordinates, RGB values
Python
my_list = [1, 2, 3]
my_list.append(4)          # ok

my_tuple = (1, 2, 3)
my_tuple[0] = 9            # TypeError
Q03 What is the difference between a list and a set? BeginnerData Types +

A list is an ordered, indexable collection that allows duplicates. A set is an unordered collection of unique elements with no indexing.

Python
nums = [1, 2, 2, 3]
print(nums)                     # [1, 2, 2, 3]

unique = set(nums)
print(unique)                   # {1, 2, 3}

print(2 in nums)              # True — O(n) scan
print(2 in unique)            # True — O(1) hash lookup

Use a set when you need fast membership checks or uniqueness. Use a list when order and indexing matter.

Q04 What is the difference between a dict and a set? BeginnerData Types +

Both are hash-based and unordered (though dicts preserve insertion order since Python 3.7). A dict stores key-value pairs. A set stores only keys.

Python
d = {"name": "Ravi", "age": 30}
s = {"name", "age"}

print(d["name"])              # Ravi
print("name" in s)              # True

Use a dict when you need to map a key to a value. Use a set when you only need to know whether something exists in a collection.

Q05 What is the difference between is and ==? BeginnerFundamentals +

== compares values. is compares identity — whether two names refer to the same object in memory.

Python
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)                 # True  — same value
print(a is b)                 # False — different objects

c = a
print(a is c)                 # True  — same object

Use is only for None, True, False and sentinel objects — like if x is None. For everything else, use ==.

Q06 What are mutable and immutable types in Python? BeginnerData Types +

Mutable objects can be changed after creation. Immutable objects cannot.

Mutable Immutable
list tuple
dict str
set int, float, bool
bytearray frozenset, bytes
Custom classes (usually) Named tuples

The practical difference: mutable objects can be changed in place, which can cause surprising bugs when two names point to the same object. Immutable objects are safe to share and can be dictionary keys.

Q07 What is a Python dictionary comprehension? BeginnerComprehensions +

A dict comprehension builds a dictionary in a single expression — often clearer and faster than a loop with dict[key] = value.

Python
# squares of numbers 1..5
squares = {n: n*n for n in range(1, 6)}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

# with a condition
evens = {n: n*n for n in range(10) if n % 2 == 0}
print(evens)
# {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
Q08 What is a list comprehension, and how does it differ from a loop? BeginnerComprehensions +

A list comprehension is a compact way to build a list from an iterable, often with a filter. It is usually faster than an equivalent for-loop because the loop is run inside the interpreter.

Python
# loop version
squares = []
for n in range(10):
    squares.append(n * n)

# comprehension version
squares = [n * n for n in range(10)]

# with a filter
evens = [n for n in range(10) if n % 2 == 0]
print(evens)                  # [0, 2, 4, 6, 8]

Use comprehensions when the logic fits on one line. If it needs nested loops or side effects, use a normal loop — readability wins.

Q09 What is a lambda function? BeginnerFunctions +

A lambda is an anonymous function written in one line. It takes any number of arguments but has exactly one expression, and the result of that expression is returned.

Python
# named function
def square(x):
    return x * x

# equivalent lambda
square = lambda x: x * x
print(square(5))              # 25

# common use — sorting with a key
people = [("Ravi", 30), ("Anita", 25)]
people.sort(key=lambda p: p[1])
print(people)
# [('Anita', 25), ('Ravi', 30)]

Use lambdas for short, throwaway functions — as a key argument or inside map and filter. For anything more complex, use a named function.

Q10 What is the difference between append() and extend() on a list? BeginnerLists +

append() adds a single item as one element — even if that item is itself a list. extend() iterates over the argument and adds each element individually.

Python
a = [1, 2]
a.append([3, 4])
print(a)                      # [1, 2, [3, 4]]

b = [1, 2]
b.extend([3, 4])
print(b)                      # [1, 2, 3, 4]
Q11 What is the difference between remove() and pop() on a list? BeginnerLists +

remove(x) removes the first occurrence of the value x and returns None. pop(i) removes and returns the element at index i.

Python
nums = [10, 20, 30, 20]
nums.remove(20)
print(nums)                  # [10, 30, 20]

last = nums.pop()
print(last)                  # 20
print(nums)                  # [10, 30]

Note: remove raises ValueError if the value is not in the list. pop raises IndexError if the list is empty.

Q12 What are f-strings in Python? BeginnerStrings +

f-strings (formatted string literals) let you embed expressions inside a string using {}. They are the cleanest and fastest way to format strings.

Python
name = "Ravi"
age  = 30

print(f"Hi {name}, you are {age} years old.")
# Hi Ravi, you are 30 years old.

# expressions inside {}
print(f"Next year you will be {age + 1}.")

# formatting — 2 decimal places
pi = 3.14159
print(f"Pi is {pi:.2f}")      # Pi is 3.14
Q13 What is the difference between pass, continue and break? BeginnerControl Flow +

pass — a placeholder that does nothing. Used where syntax requires a block but you have nothing to do yet.

continue — skip the rest of the current loop iteration and move to the next one.

break — exit the loop entirely.

Python
for i in range(10):
    if i == 3:
        continue          # skip 3
    if i == 7:
        break             # stop at 7
    print(i, end=" ")
# 0 1 2 4 5 6
Q14 What is the use of the range() function? BeginnerFunctions +

range() returns an iterable sequence of integers without building a list in memory. Three forms:

  • range(stop) — 0 to stop-1
  • range(start, stop) — start to stop-1
  • range(start, stop, step) — with a custom step
Python
print(list(range(5)))              # [0, 1, 2, 3, 4]
print(list(range(2, 8)))           # [2, 3, 4, 5, 6, 7]
print(list(range(0, 10, 2)))       # [0, 2, 4, 6, 8]
print(list(range(10, 0, -2)))      # [10, 8, 6, 4, 2]

In Python 3, range() is lazy — it produces numbers on demand, so range(10_000_000) does not use gigabytes of memory.

Q15 What is the difference between type() and isinstance()? BeginnerFundamentals +

type(obj) returns the exact class of the object. isinstance(obj, cls) returns True if the object is an instance of that class or any subclass.

Python
class Animal: pass
class Dog(Animal): pass

d = Dog()

print(type(d) == Dog)              # True
print(type(d) == Animal)           # False
print(isinstance(d, Animal))         # True
print(isinstance(d, Dog))            # True

For type checks in application code, prefer isinstance — it respects inheritance. Use type() when you need exact type equality.

Q16 What is PEP 8? BeginnerStyle +

PEP 8 is the official style guide for Python code. It is not enforced by the interpreter, but the community follows it so that code looks consistent.

Key rules:

  • Four spaces per indent level — never mix tabs and spaces.
  • Snake_case for functions and variables, PascalCase for classes, UPPER_CASE for constants.
  • Two blank lines between top-level functions and classes, one blank line between methods.
  • Maximum line length of 79 characters (many teams relax this to 88 or 100).
  • Imports on separate lines, grouped: standard library, third-party, local.
  • Spaces around operators — x = a + b, not x=a+b.
  • Docstrings for every public module, class and function.

Tools like black, flake8 and ruff enforce PEP 8 automatically.

Q17 What are *args and **kwargs? BeginnerFunctions +

*args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dict. They let a function accept any number of arguments.

Python
def greet(greeting, *args, **kwargs):
    print(greeting)
    print("Positional:", args)
    print("Keyword:", kwargs)

greet("Hello", "Ravi", "Anita", age=30, city="Noida")

# Hello
# Positional: ('Ravi', 'Anita')
# Keyword: {'age': 30, 'city': 'Noida'}

Order matters in the function signature: positional, then *args, then keyword-only arguments, then **kwargs.

Q18 What is the difference between a shallow copy and a deep copy? BeginnerMemory +

A shallow copy creates a new container but the elements inside are still shared with the original. A deep copy recursively copies every nested object.

Python
import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)                # shallow
c = copy.deepcopy(a)            # deep

a[0][0] = 99

print(b)                       # [[99, 2], [3, 4]] — inner list shared
print(c)                       # [[1, 2], [3, 4]]  — fully independent

Use copy.copy() for flat containers and copy.deepcopy() when nested objects must be independent.

Q19 What is a docstring, and how is it different from a comment? BeginnerDocumentation +

A docstring is a string literal as the first statement of a module, class or function. It is stored at runtime and accessible as obj.__doc__, so tools and help() can read it.

A comment starts with # and is completely ignored by the interpreter — it exists only for human readers.

Python
def add(a, b):
    """Return the sum of a and b."""
    return a + b

print(add.__doc__)             # Return the sum of a and b.
help(add)                       # shows the docstring

Docstrings are the standard way to document public APIs. Comments are for explaining non-obvious internal logic.

Q20 What is the difference between a module and a package? BeginnerModules +

A module is a single .py file containing Python code. A package is a directory of modules with an __init__.py file, which tells Python that the directory is a package.

Python
# module: math_utils.py
def add(a, b):
    return a + b

# package structure:
# mypackage/
#     __init__.py
#     math_utils.py
#     string_utils.py

# import
from mypackage import math_utils
print(math_utils.add(2, 3))       # 5

Packages group related modules — this is how NumPy, pandas and Django are structured.

03
Intermediate · 20 questions

Intermediate Python interview questions

These questions come up for candidates with 2–4 years of experience. They test whether you understand how Python works under the hood and can write code that is Pythonic.

Q21 What is a generator in Python, and how does yield work? IntermediateGenerators +

A generator is a function that produces values lazily using yield. Each call to yield pauses the function and returns one value; the next call resumes from where it stopped. This means you can iterate over a very large sequence without building it in memory.

Python
def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

for x in count_up_to(5):
    print(x, end=" ")
# 1 2 3 4 5

Generators are used for reading large files, streaming data, and infinite sequences. They are memory-efficient because they produce values one at a time.

Q22 What is the difference between an iterator and an iterable? IntermediateIterators +

An iterable is any object that can be looped over — it implements __iter__ and returns an iterator. A list, dict, string and set are iterables.

An iterator is the object that actually produces values. It implements __iter__ (returns itself) and __next__ (returns the next value or raises StopIteration).

Python
nums = [1, 2, 3]        # iterable
it = iter(nums)            # iterator

print(next(it))              # 1
print(next(it))              # 2
print(next(it))              # 3
# next(it) raises StopIteration

Every iterator is iterable, but not every iterable is an iterator. A list is iterable but not an iterator — you need iter() to get its iterator.

Q23 What is a decorator in Python? IntermediateDecorators +

A decorator is a function that takes another function as input and returns a new function with extra behaviour. It lets you wrap functions without changing their code — for logging, caching, timing, authentication and similar cross-cutting concerns.

Python
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} took {end - start:.4f}s")
        return result
    return wrapper

@timer
def slow():
    time.sleep(0.5)

slow()
# slow took 0.5003s

Always use functools.wraps(func) inside the wrapper to preserve the original function's name and docstring.

Q24 How do you write a decorator that takes arguments? IntermediateDecorators +

You need an extra layer of nesting — one function to accept the decorator arguments, one to accept the function being decorated, and one to wrap it.

Python
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def hi():
    print("Hello")

hi()
# Hello
# Hello
# Hello
Q25 What is a context manager, and how does the with statement work? IntermediateContext Managers +

A context manager is an object that defines setup and cleanup logic that runs automatically around a block of code. The with statement guarantees the cleanup runs even if an exception is raised.

Two ways to create one: a class with __enter__ and __exit__, or a generator decorated with @contextlib.contextmanager.

Python — class-based
class Timer:
    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc, tb):
        import time
        print(f"Elapsed: {time.perf_counter() - self.start:.4f}s")

with Timer():
    sum(range(1_000_000))
# Elapsed: 0.0123s

Note: The most common use is opening files — with open("data.txt") as f: automatically closes the file when the block exits.

Q26 What is the difference between @staticmethod and @classmethod? IntermediateOOPs +
Aspect @staticmethod @classmethod
First argument None cls (the class itself)
Access to class state No Yes — can read class attributes
Access to instance state No No
Typical use Utility function that belongs conceptually to the class Alternative constructor, factory method
Python
class Date:
    def __init__(self, year, month, day):
        self.year, self.month, self.day = year, month, day

    @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("2026-10-14")
print(d.year, Date.is_leap(2024))  # 2026 True
Q27 Explain OOP concepts in Python — encapsulation, inheritance and polymorphism. IntermediateOOPs +

Encapsulation — bundling data and methods, and controlling access. Python uses naming conventions: _name for protected, __name for private (name-mangled).

Inheritance — a child class reuses and extends a parent class. Python supports single, multiple and multilevel inheritance.

Polymorphism — the same method name behaves differently for different classes. Python supports it through method overriding and duck typing.

Python
class Animal:
    def __init__(self, name):
        self._name = name            # protected

    def speak(self):
        raise NotImplementedError

class Dog(Animal):
    def speak(self):               # polymorphism
        return f"{self._name} says Woof"

class Cat(Animal):
    def speak(self):
        return f"{self._name} says Meow"

for a in [Dog("Rex"), Cat("Milo")]:
    print(a.speak())
# Rex says Woof
# Milo says Meow
Q28 What is Method Resolution Order (MRO) in Python? IntermediateOOPs +

MRO is the order in which Python searches base classes when looking for a method or attribute. Python uses the C3 linearisation algorithm to compute it. You can inspect it with ClassName.__mro__ or ClassName.mro().

Python
class A:
    def who(self): return "A"

class B(A):
    def who(self): return "B"

class C(A):
    def who(self): return "C"

class D(B, C):
    pass

print([c.__name__ for c in D.mro()])
# ['D', 'B', 'C', 'A', 'object']
print(D().who())               # B

MRO matters most with multiple inheritance. It prevents the "diamond problem" by defining a single, deterministic lookup order.

Q29 What are dunder (magic) methods? Give examples. IntermediateOOPs +

Dunder methods are special methods with double underscores that Python calls automatically — for printing, comparing, adding, indexing and more.

Method Called by
__init__ Constructor
__str__ print(obj)
__repr__ Debugger / repr(obj)
__len__ len(obj)
__eq__ obj1 == obj2
__lt__ obj1 < obj2
__add__ obj1 + obj2
__getitem__ obj[key]
__iter__ for x in obj
__next__ next(obj)
Python
class Money:
    def __init__(self, amount):
        self.amount = amount

    def __str__(self):
        return f"₹{self.amount}"

    def __add__(self, other):
        return Money(self.amount + other.amount)

m1 = Money(100)
m2 = Money(50)

print(m1 + m2)               # ₹150
Q30 How does exception handling work in Python? IntermediateExceptions +

Python uses try, except, else and finally. The else block runs if no exception was raised; finally always runs.

Python
try:
    n = int(input("Enter a number: "))
    result = 10 / n
except ValueError:
    print("Not a valid number")
except ZeroDivisionError:
    print("Cannot divide by zero")
else:
    print(f"Result: {result}")
finally:
    print("Done")

Best practices: catch specific exceptions, not bare except:. Use raise to re-raise. Create custom exception classes by subclassing Exception.

Q31 What is a custom exception, and how do you create one? IntermediateExceptions +

A custom exception is a class you define by subclassing Exception. You create them when built-in exceptions do not describe the error precisely enough.

Python
class InsufficientBalance(Exception):
    def __init__(self, balance, amount):
        self.balance = balance
        self.amount = amount
        super().__init__(
            f"Cannot withdraw ₹{amount}; balance is ₹{balance}")

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientBalance(balance, amount)
    return balance - amount

try:
    withdraw(500, 1000)
except InsufficientBalance as e:
    print(e)
# Cannot withdraw ₹1000; balance is ₹500
Q32 What are modules and packages? How does import work? IntermediateModules +

A module is a .py file. A package is a folder of modules with an __init__.py. The import statement makes a module's objects available in the current namespace.

Python
# import the whole module
import math
print(math.sqrt(16))         # 4.0

# import specific names
from math import sqrt, pi
print(sqrt(25), pi)           # 5.0 3.141592653589793

# import with alias
import pandas as pd

# import from a package
from mypackage import utils

Python's import machinery searches sys.path — the current directory, then standard library paths, then site-packages where third-party libraries are installed.

Q33 What is a virtual environment, and why is it used? IntermediateEnvironments +

A virtual environment is an isolated Python installation with its own site-packages folder. It lets each project use its own versions of libraries without conflicts.

Terminal
# create
python -m venv venv

# activate — Linux / macOS
source venv/bin/activate

# activate — Windows
venv\Scripts\activate

# install packages
pip install pandas

# freeze for reproducibility
pip freeze > requirements.txt

# deactivate
deactivate

Without a virtual environment, installing a library for one project can break another. Every serious Python project uses one.

Q34 What are map, filter and reduce in Python? IntermediateFunctional +

These are functional programming tools. map applies a function to every item in an iterable. filter keeps only the items where a function returns True. reduce combines all items into a single value using a two-argument function.

Python
from functools import reduce

nums = [1, 2, 3, 4, 5]

squares = list(map(lambda x: x * x, nums))
print(squares)                # [1, 4, 9, 16, 25]

evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)                  # [2, 4]

total = reduce(lambda a, b: a + b, nums)
print(total)                  # 15

In modern Python, list comprehensions are usually clearer than map and filter for simple cases. reduce is best reserved for genuine accumulations — sums, products, merging.

Q35 What are closures in Python? IntermediateFunctions +

A closure is a nested function that remembers variables from its enclosing function even after the outer function has returned.

Python
def multiplier(n):
    def mul(x):
        return x * n        # n is captured
    return mul

double = multiplier(2)
triple = multiplier(3)

print(double(10))             # 20
print(triple(10))             # 30

Closures are the basis for decorators. If you need to modify the captured variable, use nonlocal inside the inner function.

Q36 What is the difference between range and xrange in Python? IntermediateCompatibility +

In Python 2, range() built a list in memory and xrange() returned a lazy iterator. In Python 3, xrange() was removed and range() now behaves like the old xrange() — it is lazy and memory-efficient.

Python 3
r = range(1_000_000)
print(type(r))               # <class 'range'>
print(len(r))                # 1000000
print(r[500])                # 500 — O(1) indexing

If you are asked this in an interview, the answer is short: in Python 3, there is no xrange — range is lazy.

Q37 What are type hints, and why are they used? IntermediateTypes +

Type hints annotate the expected types of arguments and return values. They are not enforced at runtime — they are documentation for humans and tools like mypy, Pyright and IDE autocompletion.

Python
from typing import Optional

def greet(name: str, age: int) -> str:
    return f"Hi {name}, age {age}"

def find(items: list[int], target: int) -> Optional[int]:
    for i, x in enumerate(items):
        if x == target:
            return i
    return None

Type hints catch bugs before runtime when checked by a type checker. They also make large codebases easier to read.

Q38 What is the difference between deepcopy and copy in the copy module? IntermediateMemory +

copy.copy(x) creates a shallow copy — a new top-level object, but nested objects are shared with the original.

copy.deepcopy(x) recursively copies every nested object, so the copy is fully independent.

Python
import copy

original = {"user": {"name": "Ravi", "tags": ["a", "b"]}}
shallow  = copy.copy(original)
deep     = copy.deepcopy(original)

original["user"]["tags"].append("c")

print(shallow["user"]["tags"])   # ['a', 'b', 'c']
print(deep["user"]["tags"])      # ['a', 'b']

Use copy for flat structures, deepcopy for nested ones. deepcopy is slower, so use it only when you actually need independence.

Q39 What is the purpose of __init__.py in a package? IntermediateModules +

__init__.py tells Python that a directory should be treated as a package. It can be empty, or it can initialise the package — importing common modules, defining __all__, or setting package-level variables.

Python
# mypackage/__init__.py
from .math_utils import add
from .string_utils import clean

__all__ = ["add", "clean"]

With this, users can write from mypackage import add instead of importing the submodule directly. Namespace packages (folders without __init__.py) exist in Python 3, but most projects still use the file.

Q40 What is the purpose of __name__ == "__main__"? IntermediateModules +

When a Python file is run directly, Python sets its __name__ to "__main__". When the file is imported as a module, __name__ is set to the module's name instead.

The guard if __name__ == "__main__": lets you write code that only runs when the file is executed directly — not when it is imported.

Python — mymodule.py
def greet(name):
    return f"Hello, {name}"

if __name__ == "__main__":
    print(greet("Ravi"))    # runs only when executed directly

Importing mymodule from another file will not print anything, but you can still call mymodule.greet("Ravi").

04
Experienced · 15 questions

Python interview questions for experienced professionals

These questions come up for candidates with 5+ years of experience or for anyone interviewing at a product company. They expect you to reason about performance, concurrency and design trade-offs.

Q41 What is the GIL in Python, and why does it matter? ExperiencedConcurrency +

The Global Interpreter Lock (GIL) is a mutex in CPython that allows only one thread to execute Python bytecode at a time. Even on a multi-core CPU, only one thread runs Python code at any moment.

Implications:

  • Threads do not speed up CPU-bound code — but they are fine for I/O-bound work (network, disk) because the GIL is released during I/O.
  • Multiprocessing avoids the GIL by using separate processes, each with its own interpreter.
  • asyncio avoids the GIL issue for I/O concurrency by using a single-threaded event loop and cooperative multitasking.
  • C extensions can release the GIL, which is why NumPy operations are fast despite the GIL.

Note: CPython has an experimental "free-threaded" mode (PEP 703) that removes the GIL, but as of 2026 it is not the default. Most production code still uses the GIL model.

Q42 When would you use multithreading vs multiprocessing? ExperiencedConcurrency +
Scenario Use Why
Reading many files or URLs Threads or asyncio I/O-bound — the GIL is released during I/O
Heavy number crunching Processes CPU-bound — each process has its own GIL
Simple script parallel to a network task Threads Low overhead, simple to write
Thousands of concurrent connections asyncio Single thread, no context switch overhead
Data processing using pandas/NumPy Processes C extensions release the GIL but not always enough

A simple rule: if the task spends most of its time waiting, use threads or asyncio. If it spends most of its time computing, use processes.

Q43 How does memory management and garbage collection work in Python? ExperiencedMemory +

Python uses two mechanisms:

  1. Reference counting — every object keeps a count of how many references point to it. When the count reaches zero, the object is deallocated immediately.
  2. Garbage collection — a cyclic garbage collector runs periodically to detect reference cycles that reference counting cannot break.

You can interact with the GC through the gc module:

Python
import gc, sys

x = [1, 2, 3]
print(sys.getrefcount(x))      # number of references

print(gc.get_threshold())      # (700, 10, 10)
gc.collect()                      # force a collection
gc.disable()                      # turn off automatic GC

Memory leaks in Python usually come from reference cycles, long-lived caches or global state — not from forgetting to free memory.

Q44 What is asyncio, and how does it differ from threading? ExperiencedAsync +

asyncio is a single-threaded event loop for cooperative multitasking. Coroutines yield control at await points, letting the loop run other tasks while one waits for I/O.

Threading uses OS threads with preemptive scheduling. The GIL means only one thread runs Python code at a time, but threads are still useful for I/O because the GIL is released during blocking calls.

Aspect asyncio Threading
Scheduling Cooperative (await) Preemptive (OS)
Threads One Many
Overhead per task Very low Higher — thread stack
Shared state safety Safe — no locks needed Requires locks
Best for Many concurrent network calls Blocking libraries, moderate concurrency
Python — asyncio
import asyncio

async def fetch(url):
    print(f"Fetching {url}")
    await asyncio.sleep(1)
    return f"Done {url}"

async def main():
    results = await asyncio.gather(
        fetch("a"), fetch("b"), fetch("c"))
    print(results)

asyncio.run(main())
# Fetching a
# Fetching b
# Fetching c
# ['Done a', 'Done b', 'Done c'] — runs in ~1 second total
Q45 How would you profile and optimise a slow Python program? ExperiencedPerformance +

Measure first, then optimise. The usual workflow:

  1. Time it — time.perf_counter() around key sections.
  2. Profile — cProfile for function-level hotspots, line_profiler for line-level.
  3. Find the hotspot — often 80% of the time is in 20% of the code. Do not optimise code that is not hot.
  4. Common fixes:
    • Replace loops with vectorised NumPy operations.
    • Use built-in functions — sum, min, any — instead of manual loops.
    • Use a set or dict for membership tests instead of a list.
    • Precompute values outside loops.
    • Use functools.lru_cache for pure functions that repeat.
    • Move data into a proper structure — pandas DataFrame, NumPy array.
  5. Re-measure — confirm the change actually helped.
Python — cProfile
import cProfile

def slow():
    return sum(i * i for i in range(1_000_000))

cProfile.run("slow()")
# shows the number of calls and cumulative time
Q46 What are metaclasses in Python? ExperiencedOOPs +

A metaclass is the class of a class. Just as an object is an instance of a class, a class is an instance of a metaclass. The default metaclass is type.

Metaclasses let you control how classes are created — validating attributes, registering classes, or injecting methods automatically. They are used in frameworks like Django ORM and SQLAlchemy.

Python
class Meta(type):
    def __new__(mcls, name, bases, ns):
        print(f"Creating class {name}")
        return super().__new__(mcls, name, bases, ns)

class MyClass(metaclass=Meta):
    pass

# Creating class MyClass

Rule of thumb: 99% of the time you do not need a metaclass. Use class decorators or __init_subclass__ first — they are simpler and cover most cases.

Q47 What is monkey patching, and when is it acceptable? ExperiencedDesign +

Monkey patching means modifying a class or module at runtime — adding, replacing or removing attributes and methods from outside the original definition.

Python
import math

# replace an existing function
original_sqrt = math.sqrt
math.sqrt = lambda x: original_sqrt(x) * 2

print(math.sqrt(4))              # 4.0 instead of 2.0

# restore later
math.sqrt = original_sqrt

Acceptable uses:

  • Fixing a bug in a third-party library before the fix is released.
  • Mocking or stubbing during testing.
  • Adding compatibility shims for different Python versions.

Avoid it in application code — it makes behaviour hard to trace. Prefer subclassing, composition or wrappers.

Q48 What is the difference between __str__ and __repr__? ExperiencedOOPs +

__str__ is the human-readable representation used by print() and str(). __repr__ is the developer-facing representation used by the debugger and repr() — it should ideally be valid Python that could recreate the object.

Python
class User:
    def __init__(self, name, age):
        self.name, self.age = name, age

    def __str__(self):
        return f"{self.name} ({self.age})"

    def __repr__(self):
        return f"User(name={self.name!r}, age={self.age})"

u = User("Ravi", 30)
print(u)                        # Ravi (30)
print(repr(u))                  # User(name='Ravi', age=30)

Best practice: always implement __repr__. __str__ is optional — if it is missing, Python falls back to __repr__.

Q49 How do you handle large files in Python without running out of memory? ExperiencedPerformance +

Do not read the whole file into memory. Process it line by line or in chunks.

Python — line by line
with open("huge.txt") as f:
    for line in f:            # memory-efficient
        process(line)
Python — in chunks
def read_in_chunks(path, size=8192):
    with open(path, "rb") as f:
        while chunk := f.read(size):
            yield chunk
Python — pandas chunks
import pandas as pd

for chunk in pd.read_csv("big.csv", chunksize=100_000):
    process(chunk)

Other options: memory-mapped files with mmap, generators to stream processed rows, and dtype specification in pandas to reduce memory usage.

Q50 How do you write thread-safe code in Python? ExperiencedConcurrency +

Techniques, from simplest to most complex:

  1. Avoid shared state — the easiest fix. Use function-local variables, pass data explicitly, or use immutable objects.
  2. Use threading.Lock to guard any read-modify-write on shared state.
  3. Use queue.Queue for producer-consumer patterns — it is thread-safe by design.
  4. Use threading.local() for per-thread data that looks global.
  5. Use concurrent.futures — the ThreadPoolExecutor and ProcessPoolExecutor handle the plumbing.
Python — lock
import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    with lock:
        counter += 1

threads = [threading.Thread(target=increment) for _ in range(1000)]
for t in threads: t.start()
for t in threads: t.join()

print(counter)                 # 1000
Q51 How do you design a caching layer in Python? ExperiencedDesign +

Start simple, scale only when needed:

  1. In-process LRU cache — functools.lru_cache for pure functions with repeated inputs.
  2. Dictionary-based cache — for small, explicit caching with custom eviction.
  3. External cache — Redis or Memcached when the cache must be shared across processes or survive restarts.
  4. HTTP cache headers — for web APIs, use Cache-Control and ETag headers.
Python — lru_cache
from functools import lru_cache

@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(100))              # 354224848179261915075

print(fib.cache_info())       # CacheInfo(hits=98, misses=101, ...)

Watch out for memory growth with unbounded caches, staleness with cached data that changes, and thread safety if the cache is shared between threads.

Q52 What are the differences between shallow copy and assignment? ExperiencedMemory +

Assignment (a = b) just binds another name to the same object. Both names point to the same data. Shallow copy creates a new top-level container, but the elements inside are still shared.

Python
import copy

a = [[1, 2], [3, 4]]
b = a                      # assignment — same object
c = copy.copy(a)              # shallow copy

a.append([5, 6])

print(b)                       # [[1, 2], [3, 4], [5, 6]] — same object
print(c)                       # [[1, 2], [3, 4]] — independent top level
print(a is b, a is c)       # True False

Use assignment when you want to share, shallow copy when you want a new top-level container, and deepcopy when you want full independence.

Q53 What is the difference between list.sort() and sorted()? ExperiencedLists +

list.sort() sorts the list in-place and returns None. It only works on lists.

sorted(iterable) returns a new sorted list and leaves the original unchanged. It works on any iterable — lists, tuples, sets, dicts.

Python
nums = [3, 1, 4, 2]

nums.sort()
print(nums)                  # [1, 2, 3, 4]

nums = [3, 1, 4, 2]
sorted_nums = sorted(nums)
print(nums)                  # [3, 1, 4, 2] — unchanged
print(sorted_nums)            # [1, 2, 3, 4]

# both accept key and reverse
words = ["banana", "apple", "cherry"]
print(sorted(words, key=len, reverse=True))
# ['banana', 'cherry', 'apple']
Q54 What is the difference between multiprocessing and subprocess? ExperiencedConcurrency +

multiprocessing spawns Python processes that run Python code, with built-in IPC — Queue, Pipe, Manager. Designed for parallel Python computation.

subprocess runs external programs (shell commands, other executables) and captures their output. It is for running non-Python commands, not parallel Python code.

Python — subprocess
import subprocess

result = subprocess.run(["ls", "-la"],
                        capture_output=True, text=True)
print(result.stdout)
Python — multiprocessing
from multiprocessing import Pool

def square(n):
    return n * n

with Pool(4) as pool:
    print(pool.map(square, range(10)))
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Q55 How do you write a good test in Python? ExperiencedTesting +

Tests should be isolated, fast, and test one behaviour each. Use pytest — it is the standard in the Python ecosystem.

Principles:

  • Arrange-Act-Assert — set up, execute, verify. Keep each test focused.
  • One assertion per concept — do not bundle unrelated checks.
  • Use fixtures for shared setup — database connections, test data, mock objects.
  • Test edge cases — empty inputs, boundaries, invalid types.
  • Test the public API — not private methods. Refactoring should not break tests.
  • Mock external dependencies — APIs, databases, file systems — so tests run fast and deterministically.
Python — pytest
def add(a, b):
    return a + b

def test_add_positive():
    assert add(2, 3) == 5

def test_add_negative():
    assert add(-1, -1) == -2

# run: pytest test_file.py
05
Scenario · 8 questions

Scenario-based Python interview questions

These are the questions an interviewer asks when they want to see how you think under a realistic problem. Answer with a step-by-step approach.

S01 A Python script works on your machine but fails on the server. How do you debug it? ScenarioDebugging +

Step 1 — Read the full traceback. It shows the exact file, line and error type. Do not guess.

Step 2 — Check the Python version — python --version on both machines. Syntax and behaviour change between 3.9 and 3.13.

Step 3 — Check installed packages — pip freeze on both. Version mismatches are the most common cause of "works on my machine".

Step 4 — Check environment variables and config files. A missing API key or database URL will fail silently or with a generic error.

Step 5 — Check file paths. Absolute paths and Windows-style backslashes break on Linux.

Step 6 — Check file permissions and file existence — a script may not have access to read a log directory on the server.

Step 7 — Reproduce locally with the same Python version and packages, ideally in a container.

S02 A function returns the wrong result for some inputs. How do you debug it? ScenarioDebugging +

Step 1 — Write down the input that produces the wrong output. Be exact — the value, not a description.

Step 2 — Add print() statements or use pdb to step through the function and inspect intermediate values.

Step 3 — Check the boundaries. Are the wrong results only at the edges — zero, negative, empty, maximum?

Step 4 — Check for off-by-one errors. Loops with range(n) vs range(1, n+1).

Step 5 — Check for type issues — an integer passed where a float was expected, a string that looks like a number.

Step 6 — Write a small test that covers the failing input. Fix the bug. The test stays, so the bug cannot come back.

S03 You are asked to improve the performance of a slow data processing script. What do you do? ScenarioPerformance +

Step 1 — Measure. Time the current script with time.perf_counter(). Profile with cProfile to find the hotspot.

Step 2 — Fix the algorithm first. A better algorithm beats a faster implementation. O(n²) to O(n) is a much bigger win than micro-optimisation.

Step 3 — Replace loops with pandas or NumPy vectorised operations. This is usually the single biggest win for data work.

Step 4 — Use the right data structure — set or dict for membership, deque for queues, heapq for priority.

Step 5 — Avoid repeated work — cache with functools.lru_cache, precompute outside loops, batch database queries.

Step 6 — If it is still slow, parallelise. Processes for CPU-bound, threads or asyncio for I/O-bound.

Step 7 — Re-measure. Confirm the change actually helped.

S04 How would you process a 10 GB CSV file without loading it into memory? ScenarioPerformance +

Step 1 — Read the file in chunks with pandas.read_csv(..., chunksize=100_000) and process each chunk.

Step 2 — If you only need aggregated results, accumulate them across chunks — sum, count, min, max — instead of storing all rows.

Step 3 — For non-pandas processing, use csv.reader with for row in reader: — it streams lazily.

Step 4 — If you must do multiple passes, write intermediate results to disk (Parquet, SQLite) rather than holding them in memory.

Step 5 — Consider a streaming framework — PySpark, Dask, Polars lazy mode — for genuinely large data.

Step 6 — Use dtype specification in pandas to reduce per-row memory — int32 instead of int64, category for repeated strings.

S05 A script crashes with a MemoryError after running for hours. How do you fix it? ScenarioMemory +

Step 1 — Reproduce the leak on a smaller scale. A leak that takes hours on production can often be triggered in minutes with a controlled loop.

Step 2 — Use tracemalloc or memory_profiler to see where memory is growing.

Step 3 — Check for reference cycles. Objects that refer to each other cannot be freed by reference counting alone; the cyclic GC handles them but only periodically.

Step 4 — Check for growing collections — a list or dict that is appended to on every loop iteration and never cleared.

Step 5 — Check for cached data — an unbounded lru_cache or a global dict used as a cache.

Step 6 — Check whether file handles, database connections or threads are being opened and never closed. Use with blocks.

Step 7 — If the leak is in a C library, upgrade or replace the library. Python's GC cannot clean up memory allocated outside its control.

S06 You need to call an API 10,000 times. How do you do it efficiently? ScenarioAsync +

Step 1 — Check the API's rate limits and terms. Do not hammer a service — you will get blocked.

Step 2 — Use asyncio with aiohttp or httpx for concurrent requests. Sequential requests at 200 ms each would take over 30 minutes; concurrency brings it down to seconds.

Step 3 — Use a semaphore to cap concurrency. 100 concurrent requests is usually enough; more can cause timeouts and rate limit errors.

Step 4 — Add retries with exponential backoff for transient errors — 429, 500, 502, 503.

Step 5 — Cache responses where possible. If the same request appears twice, do not call the API twice.

Step 6 — Log failures with the request details so you can re-run only the failed calls.

Python — asyncio with semaphore
import asyncio, aiohttp

async def fetch(session, url, sem):
    async with sem:
        async with session.get(url) as resp:
            return await resp.json()

async def main(urls):
    sem = asyncio.Semaphore(100)
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, u, sem) for u in urls]
        return await asyncio.gather(*tasks)

# asyncio.run(main(url_list))
S07 You inherit a legacy Python codebase with no tests. How do you approach adding tests? ScenarioTesting +

Step 1 — Do not try to test everything at once. Pick the most critical code path — the one that would cause the most damage if it broke.

Step 2 — Add characterisation tests. Run the current code with known inputs, record the outputs, and write tests that assert those outputs. This locks in current behaviour, bugs and all.

Step 3 — Refactor only after you have tests. The tests give you confidence to change things.

Step 4 — Add tests as you fix bugs. Every bug becomes a regression test.

Step 5 — Add tests for new code as you write it. Do not add new untested code on top of old untested code.

Step 6 — Run tests in CI. A test suite nobody runs is worse than no test suite — it gives false confidence.

Step 7 — Track coverage. Aim for meaningful coverage of critical paths, not 100% for its own sake.

S08 You need to build a data pipeline that runs daily. How do you structure it? ScenarioDesign +

Step 1 — Break the pipeline into stages: extract, transform, load (ETL). Each stage should be a separate function or module.

Step 2 — Make each stage idempotent. Running the pipeline twice should produce the same result, not duplicate rows.

Step 3 — Use a scheduler — cron for simple jobs, Apache Airflow, Prefect or Dagster for anything with dependencies and retries.

Step 4 — Log everything. When it fails at 3 AM, the logs are the only thing you have.

Step 5 — Add alerting. Do not wait for someone to notice the dashboard is empty — send an email or Slack message on failure.

Step 6 — Store data in a format that supports what you will do with it. Parquet for analytics, SQLite for small structured data, a real database for transactional workloads.

Step 7 — Add data validation. Check row counts, null rates and value ranges against the previous run. Catch silent failures.

Step 8 — Version your code and configuration. Use Git for code, environment variables or a config service for secrets.

06
Coding round · 15 programs

Python coding round — 15 programs with code and output

These are the fifteen programs that come up most often in Python coding rounds. Write each one by hand at least once before your interview — reading Python is not the same as writing it.

C01 Reverse a string BeginnerStrings +
Python
def reverse_string(s):
    return s[::-1]

print(reverse_string("hello"))       # olleh
print(reverse_string("Python 3"))     # 3 nohtyP

# without slicing
def reverse_loop(s):
    result = ""
    for ch in s:
        result = ch + result
    return result

print(reverse_loop("hello"))          # olleh

Slicing with [::-1] is the idiomatic answer. Mention the loop version to show you understand what slicing does internally.

C02 Check if a string is a palindrome BeginnerStrings +
Python
def is_palindrome(s):
    s = s.lower().replace(" ", "")
    return s == s[::-1]

print(is_palindrome("racecar"))          # True
print(is_palindrome("A man a plan a canal Panama"))  # True
print(is_palindrome("hello"))            # False

The key step is normalising the input — lowercase and remove spaces — before comparing.

C03 Fibonacci series — iterative and generator BeginnerGenerators +
Python — iterative
def fib_iter(n):
    a, b = 0, 1
    result = []
    for _ in range(n):
        result.append(a)
        a, b = b, a + b
    return result

print(fib_iter(10))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Python — generator
def fib_gen(n):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

print(list(fib_gen(10)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

The generator version is memory-efficient — it does not build the whole list in memory. For n = 1_000_000, the list version would fail; the generator version works fine.

C04 Check if a number is prime BeginnerMath +
Python
def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

print(is_prime(7))              # True
print(is_prime(10))             # False
print(is_prime(97))             # True
print(is_prime(1))              # False

The key optimisation is only checking divisors up to sqrt(n). If n has a factor larger than sqrt(n), it also has one smaller.

C05 Factorial of a number BeginnerRecursion +
Python — iterative and recursive
def factorial_iter(n):
    result = 1
    for i in range(2, n + 1):
        result *= i
    return result

def factorial_rec(n):
    if n <= 1:
        return 1
    return n * factorial_rec(n - 1)

print(factorial_iter(5))         # 120
print(factorial_rec(5))          # 120

# built-in option
import math
print(math.factorial(5))           # 120

Note: Python has no tail-call optimisation. For large n, the recursive version hits RecursionError. Use the iterative version or math.factorial.

C06 Check if two strings are anagrams BeginnerStrings +
Python — using sorted
def is_anagram(s1, s2):
    s1 = s1.lower().replace(" ", "")
    s2 = s2.lower().replace(" ", "")
    return sorted(s1) == sorted(s2)

print(is_anagram("listen", "silent"))   # True
print(is_anagram("hello", "world"))    # False
Python — using Counter
from collections import Counter

def is_anagram2(s1, s2):
    return Counter(s1.lower()) == Counter(s2.lower())

print(is_anagram2("listen", "silent"))  # True

The Counter version is O(n) — better than sorting, which is O(n log n). Both are accepted in interviews.

C07 Count character frequency in a string BeginnerCounter +
Python — using Counter
from collections import Counter

s = "programming"
counts = Counter(s)
print(counts)
# Counter({'r': 2, 'g': 2, 'm': 2, 'p': 1, 'o': 1, 'a': 1, 'i': 1, 'n': 1})

# most common 2
print(counts.most_common(2))
# [('r', 2), ('g', 2)]
Python — without Counter
s = "programming"
counts = {}
for ch in s:
    counts[ch] = counts.get(ch, 0) + 1

print(counts)
# {'p': 1, 'r': 2, 'o': 1, 'g': 2, 'a': 1, 'm': 2, 'i': 1, 'n': 1}

Counter is the clean answer. The dict.get(ch, 0) idiom is what you write when you do not want the import.

C08 Find the second largest number in a list BeginnerLists +
Python — using set and sorted
def second_largest(nums):
    unique = sorted(set(nums), reverse=True)
    if len(unique) < 2:
        return None
    return unique[1]

print(second_largest([10, 5, 20, 20, 8]))   # 10
print(second_largest([5, 5]))                      # None
Python — one pass, no sorting
def second_largest_fast(nums):
    first = second = float("-inf")
    for n in nums:
        if n > first:
            first, second = n, first
        elif second < n < first:
            second = n
    return second if second != float("-inf") else None

print(second_largest_fast([10, 5, 20, 20, 8]))  # 10

The set + sorted version handles duplicates cleanly. The one-pass version is O(n) and does not sort. Mention both.

C09 Remove duplicates from a list while keeping order BeginnerLists +
Python — using dict.fromkeys
def dedupe(nums):
    return list(dict.fromkeys(nums))

print(dedupe([1, 2, 3, 2, 1, 4]))
# [1, 2, 3, 4]
Python — using a seen set
def dedupe2(nums):
    seen = set()
    result = []
    for n in nums:
        if n not in seen:
            seen.add(n)
            result.append(n)
    return result

print(dedupe2([1, 2, 3, 2, 1, 4]))
# [1, 2, 3, 4]

Both preserve insertion order. Using set(nums) directly would lose the original order in older Python versions, so it is not the right answer here.

C10 Flatten a nested list IntermediateRecursion +
Python — recursion
def flatten(nested):
    result = []
    for item in nested:
        if isinstance(item, list):
            result.extend(flatten(item))
        else:
            result.append(item)
    return result

nested = [1, [2, 3], [4, [5, 6]], 7]
print(flatten(nested))
# [1, 2, 3, 4, 5, 6, 7]
Python — generator
def flatten_gen(nested):
    for item in nested:
        if isinstance(item, list):
            yield from flatten_gen(item)
        else:
            yield item

print(list(flatten_gen(nested)))
# [1, 2, 3, 4, 5, 6, 7]

The generator version uses yield from — cleaner and memory-efficient for deeply nested structures.

C11 Merge two dictionaries BeginnerDicts +
Python — Python 3.9+
d1 = {"a": 1, "b": 2}
d2 = {"b": 99, "c": 3}

merged = d1 | d2
print(merged)
# {'a': 1, 'b': 99, 'c': 3}  — d2 wins for common keys
Python — unpacking
merged = {**d1, **d2}
print(merged)
# {'a': 1, 'b': 99, 'c': 3}
Python — in-place update
d1.update(d2)
print(d1)
# {'a': 1, 'b': 99, 'c': 3}

The | operator (Python 3.9+) is the cleanest. {**d1, **d2} works in all Python 3 versions. update modifies in place.

C12 Word count from text BeginnerStrings +
Python
from collections import Counter

text = "the quick brown fox jumps over the lazy dog the fox"

words = text.lower().split()
counts = Counter(words)

print(counts)
# Counter({'the': 3, 'fox': 2, 'quick': 1, 'brown': 1, ...})

print(counts.most_common(3))
# [('the', 3), ('fox', 2), ('quick', 1)]
Python — from a file
with open("book.txt", encoding="utf-8") as f:
    words = f.read().lower().split()

counts = Counter(words)
print(counts.most_common(10))

For real text, add punctuation stripping with re.findall(r"\w+", text.lower()).

C13 Find the missing number in a list of 1 to n IntermediateMath +
Python — using sum formula
def missing_number(nums, n):
    expected = n * (n + 1) // 2
    actual = sum(nums)
    return expected - actual

print(missing_number([1, 2, 4, 5, 6], 6))   # 3
print(missing_number([1, 2, 3, 4, 5], 6))   # 6
Python — using XOR
def missing_xor(nums, n):
    xor = 0
    for i in range(1, n + 1):
        xor ^= i
    for x in nums:
        xor ^= x
    return xor

print(missing_xor([1, 2, 4, 5, 6], 6))       # 3

The sum formula is O(n) and simplest. The XOR version also O(n) but avoids potential overflow in languages with fixed-width integers — in Python it is a nice alternative to mention.

C14 Two-sum — find two numbers that add to a target IntermediateHash Map +
Python — O(n) with a hash map
def two_sum(nums, target):
    seen = {}
    for i, n in enumerate(nums):
        complement = target - n
        if complement in seen:
            return [seen[complement], i]
        seen[n] = i
    return []

print(two_sum([2, 7, 11, 15], 9))      # [0, 1]
print(two_sum([3, 2, 4], 6))               # [1, 2]
print(two_sum([3, 3], 6))                  # [0, 1]

The hash map turns an O(n²) brute-force solution into O(n). This is the classic "use a dictionary" interview question.

Python — brute force O(n²)
def two_sum_brute(nums, target):
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []
C15 Write a simple class with __str__ and __repr__ IntermediateOOPs +
Python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit must be positive")
        self.balance += amount
        return self.balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds")
        self.balance -= amount
        return self.balance

    def __str__(self):
        return f"{self.owner}'s account: ₹{self.balance}"

    def __repr__(self):
        return f"BankAccount(owner={self.owner!r}, balance={self.balance})"

acc = BankAccount("Ravi", 5000)
acc.deposit(2000)
acc.withdraw(500)

print(acc)                       # Ravi's account: ₹6500
print(repr(acc))                 # BankAccount(owner='Ravi', balance=6500)

Notice the pattern: __str__ is for users, __repr__ is for developers. Both are worth showing.

07
Data analysis · 8 questions

Python for data analysis — pandas and NumPy interview questions

These questions come up in Python interviews for data analyst and data science roles. Every answer includes runnable pandas or NumPy code and its output.

D01 How do you read a CSV file into a pandas DataFrame? Intermediatepandas +
Python
import pandas as pd

df = pd.read_csv("sales.csv")

print(df.head())
print(df.shape)                # (rows, cols)
print(df.info())               # dtypes, non-null counts
print(df.describe())            # summary stats

Useful parameters:

  • usecols=["date", "amount"] — load only the columns you need.
  • dtype={"id": "int32"} — reduce memory.
  • parse_dates=["order_date"] — parse date columns on load.
  • chunksize=100_000 — read large files in chunks.
  • na_values=["", "NA", "N/A"] — treat these as missing.
D02 How do you use groupby in pandas? Intermediatepandas +

groupby splits the data into groups by one or more keys, applies an aggregation, and combines the results. This is the pandas equivalent of SQL's GROUP BY.

Python
import pandas as pd

df = pd.DataFrame({
    "city":  ["Delhi", "Noida", "Delhi", "Noida", "Delhi"],
    "sales": [100, 200, 150, 250, 300],
})

result = df.groupby("city")["sales"].sum()
print(result)
Output
city
Delhi    550
Noida    450
Name: sales, dtype: int64
Python — multiple aggregations
result = df.groupby("city")["sales"].agg(["sum", "mean", "count"])
print(result)
Output
        sum        mean  count
city
Delhi   550  183.333333      3
Noida   450  225.000000      2
D03 How do you merge two DataFrames in pandas? Intermediatepandas +

merge() combines two DataFrames on a common key, like a SQL JOIN. The how parameter controls the join type.

how= Equivalent SQL Keeps
"inner" (default) INNER JOIN Keys in both
"left" LEFT JOIN All keys from left
"right" RIGHT JOIN All keys from right
"outer" FULL OUTER JOIN All keys from both
Python
import pandas as pd

orders = pd.DataFrame({
    "order_id": [1, 2, 3],
    "customer_id": [10, 11, 12],
    "amount": [500, 1000, 750],
})

customers = pd.DataFrame({
    "customer_id": [10, 11, 13],
    "name": ["Ravi", "Anita", "Sam"],
})

merged = pd.merge(orders, customers, on="customer_id", how="inner")
print(merged)
Output
   order_id  customer_id  amount   name
0         1           10     500   Ravi
1         2           11    1000  Anita

Note: Use on= when the key column has the same name in both DataFrames. Use left_on= and right_on= when the names differ.

D04 How do you create a pivot table in pandas? Intermediatepandas +

pivot_table() reshapes data into a grid — one key down the rows, another across the columns, and an aggregate value in each cell.

Python
import pandas as pd

df = pd.DataFrame({
    "region":  ["North", "North", "South", "South", "North"],
    "product": ["A", "B", "A", "B", "A"],
    "sales":   [100, 150, 200, 250, 300],
})

pivot = df.pivot_table(
    values="sales",
    index="region",
    columns="product",
    aggfunc="sum",
    fill_value=0,
)
print(pivot)
Output
product    A    B
region
North    400  150
South    200  250

fill_value=0 replaces missing combinations with zero instead of NaN. This is what you usually want for sales reports.

D05 How do you handle missing values in pandas? Intermediatepandas +
Python — detect
import pandas as pd
import numpy as np

df = pd.DataFrame({
    "name":   ["Ravi", "Anita", None, "Sam"],
    "salary": [50000, None, 70000, 60000],
})

print(df.isnull().sum())
# name      1
# salary    1
Python — handle
# drop rows with any missing value
df_dropped = df.dropna()

# fill with a fixed value
df_filled = df.fillna(0)

# fill salary with the mean
df["salary"] = df["salary"].fillna(df["salary"].mean())

# forward-fill (useful for time series)
df_filled = df.ffill()

The right strategy depends on why the data is missing. For numeric columns, mean or median imputation is common. For categorical columns, a "Unknown" category works. For time series, forward-fill or interpolation is common. Dropping should be a last resort — you lose information.

D06 How does apply() work in pandas, and when should you use it? Intermediatepandas +

apply() runs a Python function on every element, row or column of a DataFrame. It is flexible but slower than vectorised operations.

Python — on a column
import pandas as pd

df = pd.DataFrame({"name": ["ravi", "anita", "sam"]})

df["name_upper"] = df["name"].apply(lambda x: x.upper())
print(df)
Output
    name name_upper
0   ravi       RAVI
1  anita      ANITA
2    sam        SAM
Python — on rows (axis=1)
df = pd.DataFrame({"price": [100, 200], "qty": [2, 3]})

df["total"] = df.apply(lambda row: row["price"] * row["qty"], axis=1)
print(df)
Output
   price  qty  total
0    100    2    200
1    200    3    600

For simple arithmetic like this, the vectorised version — df["price"] * df["qty"] — is much faster. Use apply only when the logic cannot be expressed vectorised.

D07 How do you work with dates and times in pandas? Intermediatepandas +
Python
import pandas as pd

df = pd.DataFrame({
    "order_date": ["2026-01-15", "2026-02-20", "2026-03-25"],
    "amount": [100, 200, 150],
})

# convert to datetime
df["order_date"] = pd.to_datetime(df["order_date"])

# extract parts
df["year"]  = df["order_date"].dt.year
df["month"] = df["order_date"].dt.month
df["day"]   = df["order_date"].dt.day

print(df)
Output
  order_date  amount  year  month  day
0 2026-01-15     100  2026      1   15
1 2026-02-20     200  2026      2   20
2 2026-03-25     150  2026      3   25
Python — monthly aggregation
monthly = df.groupby(df["order_date"].dt.to_period("M"))["amount"].sum()
print(monthly)
Output
order_date
2026-01    100
2026-02    200
2026-03    150
Freq: M, Name: amount, dtype: int64

Use .dt to access datetime properties, and pd.to_period() or pd.Grouper() for time-based grouping.

D08 Why is vectorisation faster than loops in pandas and NumPy? IntermediateNumPy +

NumPy stores data in contiguous C arrays and runs operations on them in compiled C code. A Python loop runs one iteration at a time in the interpreter, which is much slower — often 50–100 times slower for simple arithmetic.

Python — vectorised vs loop
import numpy as np
import time

arr = np.arange(1_000_000)

# Python loop
start = time.perf_counter()
result_loop = [x * 2 for x in arr]
loop_time = time.perf_counter() - start

# vectorised
start = time.perf_counter()
result_vec = arr * 2
vec_time = time.perf_counter() - start

print(f"Loop:       {loop_time:.4f}s")
print(f"Vectorised: {vec_time:.4f}s")
Output (example timing)
Loop:       0.0800s
Vectorised: 0.0020s

The rule: use vectorised operations whenever possible — df["a"] + df["b"] instead of a row-by-row loop. Use apply as a fallback, and a Python loop only as a last resort.

08
Quick revision

Quick-revision cheat table

Fifteen one-line definitions and syntax reminders to skim on the morning of your interview.

One-line reminders for common Python interview topics
Topic One-line reminder
Interpreted vs compiled Python compiles source to bytecode, then interprets that bytecode in the CPython VM.
Mutable vs immutable Mutable — list, dict, set. Immutable — tuple, str, int, frozenset.
is vs == is compares identity, == compares value. Use is only for None/True/False.
*args and **kwargs *args collects positional args into a tuple, **kwargs collects keyword args into a dict.
Lambda Anonymous one-line function — lambda x: x * 2. No statements, only an expression.
Comprehension Compact way to build a list/dict/set — [n*n for n in range(5) if n % 2 == 0].
Generator Lazy function using yield. Produces values on demand — memory-efficient.
Iterator vs iterable Iterable has __iter__. Iterator has __iter__ and __next__.
Decorator A function that wraps another function. Use functools.wraps to preserve metadata.
Context manager Object with __enter__ and __exit__, used with with. Auto cleanup.
OOPs Encapsulation, inheritance, polymorphism, abstraction. Python uses _ and __ for access.
MRO Method Resolution Order — the sequence Python searches for a method. Uses C3 linearisation.
Dunder methods Special methods called by Python syntax — __init__, __str__, __eq__, __add__.
GIL Global Interpreter Lock — one thread runs Python bytecode at a time. Blocks CPU parallelism with threads.
Shallow vs deep copy Shallow shares nested objects. Deep recursively copies everything.
Multithreading vs multiprocessing Threads for I/O-bound, processes for CPU-bound (bypasses the GIL).
asyncio Single-threaded event loop with coroutines — good for many concurrent I/O operations.
Virtual environment Isolated Python installation per project — python -m venv venv.
Type hints Annotations not enforced at runtime — checked by mypy or Pyright.
pandas groupby df.groupby("city")["sales"].sum() — like SQL GROUP BY.
09
Preparation plan

How to prepare for a Python interview in 7 days

If your interview is next week, follow this plan. Each day has a single focus and a practice task — do not just read, write code.

Seven-day Python interview preparation plan
Day Focus Practice task
Day 1 Core fundamentals Answer Q1–Q6 out loud. Write a script that shows mutable vs immutable, is vs ==, and shallow vs deep copy. Run it.
Day 2 Functions and comprehensions Answer Q9, Q14, Q17, Q34. Write a function using *args and **kwargs, plus three comprehensions (list, dict, set).
Day 3 OOPs and dunder methods Answer Q27–Q29, Q48. Write a class with __init__, __str__, __repr__, __eq__ and __add__.
Day 4 Generators, decorators, context managers Answer Q21–Q25. Write a generator for Fibonacci, a timing decorator, and a context manager using __enter__ and __exit__.
Day 5 Exceptions and memory Answer Q30–Q33, Q41, Q43. Write a custom exception and a try/except/else/finally block. Explain the GIL out loud.
Day 6 Coding round Write all 15 programs from section 06 by hand. Run each one. Rewrite the two you struggled with.
Day 7 pandas and mock interview Answer the 8 pandas questions with code. Run each snippet. Then answer one scenario question out loud per level.
10
What to avoid

Common mistakes candidates make

01

Using mutable default arguments

def f(items=[]): is a classic bug — the same list is reused across calls. Use def f(items=None): and create the list inside the function.

02

Confusing is and ==

is compares identity, == compares value. Using is for numbers or strings works by accident for small values and fails silently for large ones.

03

Bare except clauses

except: catches everything — including KeyboardInterrupt and SystemExit. Catch specific exceptions, or at minimum except Exception:.

04

Writing loops where vectorisation works

In pandas and NumPy, an explicit loop is 50–100 times slower than the vectorised version. Learn the vectorised form first.

05

Ignoring the GIL in concurrency questions

Saying "use threads for parallelism" in a Python interview is a red flag. Know that threads help with I/O, and processes are needed for CPU-bound work.

06

Reading code instead of writing it

You will not remember how to write a decorator, a generator or a two-sum by reading them three times. Write each one by hand, then again without looking.

11
Training

Practise these topics with Uncodemy

Python, pandas, NumPy, SQL and the full data analysis workflow are taught inside Uncodemy's Data Analytics and Data Science programs. Python fundamentals are also taught as a standalone module inside both tracks.

Uncodemy programs covering Python and data analysis topics
Program What it covers Duration / fee
Data Analytics Python (20 h), SQL, Excel, Power BI, pandas, NumPy, statistics. This is the direct track for the Python and pandas topics on this page. ₹17,500 + GST · 6–7 months
Data Science Python, statistics, machine learning, deep learning, NLP, 7 projects. Includes the Python for Data Science module. ₹17,500 + GST · 8–9 months
Business Analyst SQL, Excel, BRD/FRD, process modelling, Agile/Scrum, JIRA, stakeholder management. Includes Python for analysis. ₹17,500 + GST · 6–7 months

Placement assistance (as published by Uncodemy): resume building, LinkedIn optimisation, portfolio review, interview preparation, mock interviews and opportunity sharing through a network of 850+ companies. Uncodemy does not guarantee employment, as hiring decisions rest with employers. See placement support.

Also useful: AI career guidance for picking a path, and the 2-class free trial to see how a live batch runs before you commit.

12
FAQs

Python interview preparation FAQs

F1 How do I prepare for a Python interview? +
Yes, with a structured plan. Start with the beginner questions, then work through the coding round — all 15 programs — and the pandas section if you are targeting data roles. Write code by hand, not just read it. Follow the 7-day plan in section 09.
F2 Is Python enough to get a job? +
Python alone is rarely enough. Most roles expect a second skill — SQL for data roles, Django or Flask for backend, pandas and NumPy for analysis, or Selenium for testing. Python is the foundation, not the whole profile.
F3 Which Python topics are asked for data analyst roles? +
For data analyst roles, focus on pandas — read_csv, groupby, merge, pivot_table, missing values, apply, datetime handling — plus NumPy vectorisation and basic SQL. OOPs and decorators come up less often but are still asked at product companies.
F4 How many coding questions are asked in a Python interview? +
Typically two to four coding problems in a 45–60 minute round. Common ones — reverse a string, palindrome, Fibonacci, two-sum, and list manipulation. You are judged on approach and clean code, not just the correct answer.
F5 Should freshers learn pandas before an interview? +
Yes, if you are targeting data analyst or data science roles. Basic pandas — read_csv, groupby, merge, fillna, apply — appears in almost every entry-level data interview. For general Python or backend roles, core Python and SQL matter more.
Want to practise with a trainer?

Join a live Python batch — 2 free classes first

Reading questions is the easy part. Writing Python under time pressure is what gets you hired. Uncodemy runs live Data Analytics, Data Science and Python batches at Noida and Delhi, and online. Attend two classes before you decide.

Page contents 63+ questions · 15 programs
Levels covered Fresher · Intermediate · Experienced
Reviewed by Mr. Upendra Kumar Tiwari
Last updated 14 October 2026