Python November 19 ,2025

Table of Contents

Python Interview Preparation  

 How to use this guide?

Interviews test three things: conceptual knowledge, problem solving, and communication. For Python roles you will be evaluated on language fundamentals, standard library knowledge, algorithms/data structures in Python, design patterns and idiomatic usage, and practical system-level concerns (performance, memory, concurrency, testing). Use this guide to revise high-density topics, practice code snippets until you can write and explain them, and rehearse concise explanations of tricky concepts.

Practical: keep a copy of key snippets in a single file; run them in a REPL to verify outputs. Prepare to explain why a solution works and discuss complexity/alternatives.

 Most frequently asked Python interview questions

Below are common questions with short theory and runnable examples or snippets.

Q1: What are Python’s core data types? When to use which?

Theory: Built-in core types include int, float, bool, str, list, tuple, set, dict, bytes. Use list for ordered, mutable collections; tuple for ordered, immutable collections; set for unique elements and membership tests; dict for key→value mapping. Use str for text (immutable). Use bytes for binary data.

Practical:

# list
l = [1, 2, 3]
# tuple
t = (1, 2, 3)
# set
s = {1, 2, 3}
# dict
d = {'a': 1, 'b': 2}

Q2: Difference between list and tuple? Why use tuple?

Theory: Tuples are immutable and can be used as dict keys if they contain hashable items. They are slightly faster and indicate an invariant collection. Use tuple for fixed-size heterogeneous data (like function return values).

Practical:

coords = (10.0, 20.0)   # tuple is good here
# trying to modify will raise
# coords[0] = 5  -> TypeError

Q3: How does Python manage memory? What is reference counting and the garbage collector?

Theory: CPython uses reference counting (each object tracks how many references point to it). When refcount hits zero the object is deallocated. To handle cycles, CPython also has a cyclic garbage collector that periodically detects reference cycles of unreachable objects and frees them.

Practical:

import sys
a = []
b = a
print(sys.getrefcount(a))  # >= 2 (one from 'a', one from getrefcount arg)
del b
print(sys.getrefcount(a))

Q4: Explain mutable vs immutable. Why does list.copy() matter?

Theory: Mutable objects (list, dict, set) can change in-place; immutable objects (tuple, str, int) don’t. Copying avoids unintentional shared-state bugs. Use shallow copy (list.copy() or list[:]) to duplicate top-level structure. Use copy.deepcopy() for nested structures.

Practical:

import copy
a = [1, [2, 3]]
b = a.copy()            # shallow
b[1].append(4)
print(a)                # nested list mutated

c = copy.deepcopy(a)    # deep copy
c[1].append(5)
print(a, c)

Q5: Explain *args and **kwargs.

Theory: *args collects positional arguments into a tuple. **kwargs collects keyword arguments into a dict. They allow flexible argument lists and are used for wrapper functions or APIs.

Practical:

def f(*args, **kwargs):
    print(args, kwargs)

f(1,2, x=10, y=20)
# pass-through
def wrapper(*a, **kw):
    return f(*a, **kw)

Q6: What is list comprehension? Why use it?

Theory: List comprehensions provide a concise, often faster, and more readable way to build lists from iterables with optional filtering. They are syntactic sugar over loops.

Practical:

nums = [1,2,3,4,5]
squares = [n*n for n in nums if n % 2 == 1]

Q7: Generator vs list — when to use?

Theory: Generators produce items lazily, memory efficient for large sequences, and can represent infinite sequences. Lists are materialized in memory. Use generators when you stream large data.

Practical:

def gen():
    for i in range(10**6):
        yield i

g = gen()
next(g)   # produces one item without building entire list

Q8: Explain yield and generator comprehension.

Theory: yield suspends function execution and returns a value to the caller while maintaining state for resumption. Generator comprehensions use parentheses: (x*x for x in range(10)).

Practical:

def fibonacci(n):
    a,b = 0,1
    for _ in range(n):
        yield a
        a,b = b, a+b

for x in fibonacci(5):
    print(x)

Q9: Decorators — what are they and how to preserve metadata?

Theory: Decorators are callables that take and return functions (or callables). They wrap behavior. Use functools.wraps to preserve __name__, __doc__, and metadata.

Practical:

from functools import wraps

def log(func):
    @wraps(func)
    def wrapper(*a, **kw):
        print("calling", func.__name__)
        return func(*a, **kw)
    return wrapper

@log
def add(a,b):
    return a+b

Q10: Explain lambda and when not to use it.

Theory: lambda creates anonymous single-expression functions. Prefer named functions for complex logic or when reusability/readability matters.

Practical:

squared = lambda x: x*x
sorted_list = sorted([3,1,2], key=lambda x: -x)

Q11: What are __init__, __new__, and __repr__?

Theory: __init__ initializes instance after creation. __new__ creates/returns a new instance (rarely overridden). __repr__ returns unambiguous string representation for debugging; implement __str__ for user-facing representation.

Practical:

class C:
    def __new__(cls, *a, **k):
        return super().__new__(cls)
    def __init__(self, x):
        self.x = x
    def __repr__(self):
        return f"C({self.x!r})"

Q12: Context manager and with statement — how to write one?

Theory: Context managers manage setup/cleanup (e.g., file handling). Implement __enter__ and __exit__ or use contextlib.contextmanager.

Practical:

class MyCtx:
    def __enter__(self):
        print("enter")
        return self
    def __exit__(self, exc_type, exc, tb):
        print("exit")

with MyCtx():
    print("inside")

Or:

from contextlib import contextmanager
@contextmanager
def my_ctx():
    print("enter")
    try:
        yield
    finally:
        print("exit")

Q13: Explain exceptions and custom exceptions.

Theory: Use exceptions for error handling. Create custom exceptions by subclassing Exception to signal domain-specific errors. Prefer specific exceptions to avoid catching everything.

Practical:

class MyError(Exception):
    pass

def f(x):
    if x < 0:
        raise MyError("x must be >= 0")

Q14: What is the Global Interpreter Lock (GIL)?

Theory: In CPython, GIL ensures only one native thread executes Python bytecode at a time. It simplifies memory management but limits multi-thread CPU-bound parallelism. I/O-bound tasks still benefit from threads; CPU-bound tasks need multiprocessing or native extensions releasing GIL.

Practical (demo of threads not speeding up CPU-bound task):

import threading, time

def busy(n):
    s = 0
    for i in range(n):
        s += i*i

t1 = threading.Thread(target=busy, args=(10**7,))
t2 = threading.Thread(target=busy, args=(10**7,))
start = time.time()
t1.start(); t2.start(); t1.join(); t2.join()
print("time:", time.time() - start)

(Compare with multiprocessing for real parallelism.)

Q15: Explain map, filter, reduce, and idiomatic Python alternatives.

Theory: map/filter return iterators (in Py3), reduce in functools. List comprehensions/generator expressions are usually clearer.

Practical:

from functools import reduce
nums = [1,2,3,4]
doubled = list(map(lambda x:x*2, nums))
odd = list(filter(lambda x: x%2, nums))
sum_all = reduce(lambda a,b: a+b, nums)
# better:
doubled = [x*2 for x in nums]

Q16: Difference between is and ==?

Theory: is checks identity (same object), == checks equality via __eq__. Use is for None checks (x is None) and singletons.

Practical:

a = [1,2]
b = a
c = [1,2]
print(a is b)  # True
print(a == c)  # True
print(a is c)  # False

Q17: Explain comprehensions for dicts and sets.

Theory: Similar to list comprehensions; compact and efficient.

Practical:

squares = {x: x*x for x in range(5)}
unique = {x%3 for x in range(10)}

Q18: How to profile Python code and measure performance?

Theory: Use timeit for microbenchmarks, cProfile and py-spy or perf for CPU profiling, tracemalloc for memory. Optimize after measuring; premature optimization is bad.

Practical:

import timeit
print(timeit.timeit("sum(range(1000))", number=1000))

Q19: Explain immutability pitfalls (mutable default arguments).

Theory: Default args are evaluated once at function definition. Using mutable defaults leads to state shared across calls.

Practical:

def f(a, lst=[]):
    lst.append(a)
    return lst

print(f(1))  # [1]
print(f(2))  # [1, 2]  <- surprising

# Right way
def f(a, lst=None):
    if lst is None:
        lst = []
    lst.append(a)
    return lst

Q20: What is __slots__?

Theory: __slots__ restricts allowable attributes and prevents creating __dict__, reducing memory usage for many instances. Use for performance-critical, memory-constrained classes.

Practical:

class Point:
    __slots__ = ('x','y')
    def __init__(self,x,y):
        self.x, self.y = x, y

Q21: Iterators vs Iterables — how to implement custom iterator?

Theory: Iterable implements __iter__ returning an iterator. Iterator implements __next__ and __iter__ (returns self). StopIteration signals end.

Practical:

class Count:
    def __init__(self, n):
        self.n = n
    def __iter__(self):
        self.i = 0
        return self
    def __next__(self):
        if self.i < self.n:
            v = self.i
            self.i += 1
            return v
        raise StopIteration

for x in Count(3):
    print(x)

Q22: How to do concurrency — threads, processes, async?

Theory: Threads: good for I/O-bound. Multiprocessing: use separate processes for CPU-bound. asyncio: cooperative concurrency using async/await for high-concurrency I/O without threads. Choose based on workload and libraries’ async compatibility.

Practical (async example):

import asyncio, aiohttp

async def fetch(url):
    async with aiohttp.ClientSession() as s:
        async with s.get(url) as r:
            return await r.text()

async def main():
    html = await fetch('https://example.com')
    print(len(html))

asyncio.run(main())

Q23: Explain Python’s sort() vs sorted() and key usage.

Theory: list.sort() sorts in place and returns None. sorted() returns a new sorted list and accepts any iterable. Use key for customizing order; use reverse=True for descending.

Practical:

data = [{'name':'b','age':30},{'name':'a','age':25}]
sorted_by_age = sorted(data, key=lambda x: x['age'])
data.sort(key=lambda x: x['name'])

Q24: Explain pickling and its security implications.

Theory: pickle serializes Python objects, but unpickling untrusted data is unsafe (code execution risk). Use JSON for simple types; use pickle only for trusted environments.

Practical:

import pickle
d = {'a':1}
s = pickle.dumps(d)
obj = pickle.loads(s)

Q25: Explain namedtuple and dataclass.

Theory: namedtuple provides lightweight immutable tuples with named fields. dataclass (Python 3.7+) simplifies writing classes with boilerplate (init, repr, eq). Dataclasses can be frozen (immutable) and support default factories.

Practical:

from collections import namedtuple
P = namedtuple('P', ['x','y'])
p = P(1,2)

from dataclasses import dataclass
@dataclass
class Person:
    name: str
    age: int = 0

Q26: How to handle dependencies and virtual environments?

Theory: Use venv (or virtualenv) to isolate dependencies. Keep requirements.txt (pip freeze) or pyproject.toml/poetry.lock for reproducible builds.

Practical:

python -m venv venv
source venv/bin/activate
pip install requests
pip freeze > requirements.txt

Q27: Explain asyncio event loop basics

Theory: asyncio runs tasks on an event loop; coroutines (async def) yield control using await. IO-bound code with async-aware libraries can scale to thousands of concurrent tasks.

Practical:

import asyncio
async def say(n):
    await asyncio.sleep(1)
    print(n)

async def main():
    await asyncio.gather(*(say(i) for i in range(5)))

asyncio.run(main())

Tricky concepts and deep explanations 

Below are commonly probed tricky areas. Understand them well and practice explaining with examples.

A. Mutable default arguments (revisited)

Theory: See earlier — defaults evaluated once. Always use None + factory.

Practical (quick demo shown earlier).

B. Late binding closures in loops

Theory: A classic gotcha: closures capture variables by reference (not value) leading to surprising results when creating functions in loops.

Practical:

funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])   # [2,2,2]  - all use final i

# Fix: bind current value
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])   # [0,1,2]

C. Method resolution order (MRO) and multiple inheritance

Theory: Python uses C3 linearization for MRO. Explain with diamond hierarchy examples. super() follows MRO.

Practical:

class A: 
    def m(self): print('A')
class B(A): 
    def m(self): print('B'); super().m()
class C(A): 
    def m(self): print('C'); super().m()
class D(B,C): 
    def m(self): print('D'); super().m()

D().m()
# Output order follows MRO: D B C A

D. Descriptor protocol (__get__, __set__)

Theory: Descriptors control attribute access; used to implement properties, methods, and attributes on classes. Understand data vs non-data descriptors.

Practical:

class Reveal:
    def __get__(self, obj, objtype=None):
        return "revealed"

class Obj:
    x = Reveal()

o = Obj()
print(o.x)  # "revealed"

E. Metaclasses (theory)

Theory: Metaclasses create classes; used rarely but useful for frameworks. type is the default metaclass. Use only when you need to modify class creation.

Practical (basic):

class Meta(type):
    def __new__(mcls, name, bases, dct):
        dct['added'] = 42
        return super().__new__(mcls, name, bases, dct)

class C(metaclass=Meta):
    pass

print(C.added)  # 42

F. __slots__ vs __dict__

Theory: __slots__ reduces memory; prevents dynamic attribute creation unless __dict__ included.

Practical: Shown earlier.

G. Thread-safety and CPython internals

Theory: GIL simplifies single-interpreter thread-safety but not for shared mutable data—use locks or thread-safe queues.

Practical:

from threading import Thread, Lock
counter = 0
lock = Lock()
def inc():
    global counter
    for _ in range(100000):
        with lock:
            counter += 1
threads = [Thread(target=inc) for _ in range(4)]
for t in threads: t.start()
for t in threads: t.join()
print(counter)

H. Coroutines vs Generators — subtle difference

Theory: Generators produce data (yield), coroutines consume using send(); async/await are built on top of coroutines but are different building blocks.

Practical: Advanced topic — show yield vs await usage if asked.

 Best practices interviewers expect 

A. Code style & readability

Theory: Follow PEP8 naming, spacing, line length, docstrings. Prefer explicit code over clever one-liners.

Practical:

# Bad
def f(x): return x+1

# Better
def increment(x):
    """Return x + 1"""
    return x + 1

B. Type hints for clarity

Theory: Use typing to document intent and enable static checks.

Practical:

from typing import List, Dict

def average(values: List[float]) -> float:
    return sum(values) / len(values)

C. Write tests

Theory: Unit tests and small testable functions show professionalism.

Practical:

def add(a,b): return a+b
def test_add():
    assert add(2,3) == 5

D. Logging vs printing

Theory: Use logging for production-level traceability with levels and handlers.

Practical:

import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
logger.info("starting")

E. Avoid premature optimization

Theory: Profile, measure, then optimize. Keep code correct and clear first.

F. Use standard library

Theory: Prefer built-in modules (itertools, functools, collections, pathlib) over reinventing solutions.

Practical:

from collections import Counter
Counter(['a','b','a'])

G. Secure coding practices

Theory: Avoid exec/eval on untrusted input, sanitize inputs, secure secrets, and validate user data. For web work use established frameworks and libraries.

Common mistakes (theory + how to avoid)

Mistake 1: Mutable default argument — fix with None.

(Explained earlier.)

Mistake 2: Shadowing builtins (e.g., using list, str, id as variable names)

Avoidance: Choose variable names that don't shadow builtins. If you accidentally shadow, reassigning can break standard functions.

Practical:

list = [1,2]   # avoid — now built-in list is shadowed
del list

Mistake 3: Confusing equality vs identity (== vs is) — use is None pattern

Avoidance: Use is only for singletons.

Mistake 4: Catching broad exceptions (except Exception: or except:)

Avoidance: Catch specific exceptions and re-raise or log with context.

Practical:

try:
    x = int(s)
except ValueError:
    handle_bad_input()

Mistake 5: Inefficient loops instead of comprehensions or vectorized ops

Avoidance: For numeric data prefer NumPy/Pandas vectorized ops.

Mistake 6: Not closing files — use with

Avoid with:

with open('f') as f:
    data = f.read()

Mistake 7: Using pickle for untrusted sources (security risk)

Avoidance: Use JSON or safe serializers.

Mistake 8: Overuse of global variables — leads to hard-to-debug state

Avoidance: Pass arguments, use classes, or modules encapsulation.

Interview strategy & closing tips 

  1. Explain your thinking: Narrate tradeoffs, complexity, and assumptions. Interviewers value reasoning more than a single correct line.
  2. State time & space complexity: Always give Big-O.
  3. Start with examples: Manual dry run with a small example to validate approach.
  4. Write simple correct code first: Then optimize or generalize.
  5. Handle edge cases: Null/empty inputs, extremes, invalid inputs.
  6. Test your code aloud: Show sample runs or walk through the code path.
  7. Know standard libs and idioms: itertools, collections, functools, heapq.
  8. Practice common patterns: sliding window, two pointers, hashing, sorting-based, recursion with memoization.
  9. Prepare quick cheat-sheets: For list/dict methods, slicing, comprehension, and iterator protocols.
  10. Know how to use REPL and write readable variable names during whiteboard or shared-editor interviews.

Quick reference: bite-sized snippets to memorize 

  • Swap variables:
a, b = b, a
  • Reverse list in place:
lst.reverse()
  • Slicing:
sub = lst[1:5:2]  # start:stop:step
  • Dictionary comprehension:
{k:v for k,v in pairs}
  • Default dict:
from collections import defaultdict
d = defaultdict(list)
d['a'].append(1)
  • Counter:
from collections import Counter
Counter(items).most_common(3)
  • Groupby (from itertools):
from itertools import groupby
groups = {k: list(v) for k,v in groupby(sorted(data), key=lambda x: x.key)}
  • LRU cache:
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n): ...

Final checklist before interview (practical)

  • Know basic algorithms: sorting, binary search, BFS/DFS, two-sum, sliding window.
  • Know Python specifics: list vs tuple, comprehension, generator, decorator, context manager, iterators.
  • Practice 20–30 coding problems on a whiteboard/editor and explain them.
  • Prepare 6–8 good questions for the interviewer about team, codebase, tooling.
  • Sleep well and rehearse clear explanations.

Next Blog- Advanced Topics

 

Sanjiv
0

You must logged in to post comments.

Get In Touch

Kurki bazar Uttar Pradesh

+91-8808946970

techiefreak87@gmail.com