Tutorials Logic, IN info@tutorialslogic.com
Python

Top 50 Python Interview Questions

Practice Python interview answers with simple explanations first, then examples, project context, and professional notes for OOP, decorators, generators, collections, Django, Flask, and Python 3 features.

01

Explain Python and what are its key features.

Python is a high-level, interpreted, dynamically typed programming language. In simple words, it lets developers write readable code for many kinds of work: automation, web apps, APIs, data analysis, AI, testing, and scripting. In an interview, do not only say "Python is easy." Mention why it is useful in real projects: clean syntax, large standard library, automatic memory management, multiple programming styles, cross-platform support, and a strong ecosystem such as Django, Flask, FastAPI, NumPy, pandas, and PyTorch.

02

What is the difference between Python 2 and Python 3?

  • print - Python 2: print "hello". Python 3: print("hello") (function).
  • Division - Python 2: 5/2 = 2 (integer). Python 3: 5/2 = 2.5 (float). Use // for integer division.
  • Unicode - Python 3 strings are Unicode by default.
  • Python 2 reached end-of-life in 2020. Always use Python 3.
  • Interview note - make it clear that Python 2 is legacy. For new projects, hiring teams expect Python 3 knowledge, including f-strings, type hints, pathlib, async features, and modern package tooling.
03

Compare list, tuple, and set.

  • list - ordered and mutable. Use it when the collection can change, such as cart items, API results, or tasks in a queue.
  • tuple - ordered and immutable. Use it when the group should not change, such as coordinates, fixed settings, or a returned pair of values.
  • set - unique collection with fast membership checks. Use it for removing duplicates or checking if a value already exists.
  • Professional note - choose the structure by behavior, not by habit. Ask: do I need order, duplicates, mutability, or fast membership?
Example
lst = [1, 2, 2, 3]
tpl = (1, 2, 2, 3)
st = {1, 2, 2, 3}  # {1, 2, 3} - duplicates removed
04

Explain the difference between list and dict.

  • list - ordered collection accessed by position. Use a list when order matters and you normally process items one by one.
  • dict - key-value mapping accessed by key. Use a dict when you need to quickly find data by an identifier such as user_id, email, slug, or product_code.
  • Performance note - searching a list by value is usually O(n), while dictionary lookup by key is usually O(1).
  • Common mistake - do not use a list of pairs when a dictionary would make lookups clearer and faster.
Example
lst = ["a", "b", "c"]
print(lst[0])  # "a"

dct = {"name": "Alice", "age": 30}
print(dct["name"])  # "Alice"
05

What is a Python decorator?

A decorator is a function that takes another function and returns a new function with extra behavior. It lets you add behavior without changing the original function body. In real projects, decorators are used for logging, authentication, permissions, caching, retry logic, timing, validation, and route handling in frameworks. A good interview answer should include the idea of "wrapping" plus one practical use case.

Example
def log(func):
  def wrapper(*args, **kwargs):
    print(f"Calling {func.__name__}")
    return func(*args, **kwargs)
  return wrapper

@log
def greet(name):
  return f"Hello, {name}"

greet("Alice")  # prints "Calling greet", returns "Hello, Alice"
06

Compare *args and **kwargs.

  • *args - captures positional arguments as a tuple.
  • **kwargs - captures keyword arguments as a dict.
  • Both allow functions to accept a variable number of arguments.
Example
def func(*args, **kwargs):
  print(args)    # (1, 2, 3)
  print(kwargs)  # {"a": 4, "b": 5}

func(1, 2, 3, a=4, b=5)
07

Explain a Python generator.

A generator yields values one at a time using yield, enabling memory-efficient iteration over large datasets. Generators are lazy - they produce values on demand.

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

for num in fibonacci(10):
  print(num)
08

What is the difference between yield and return?

  • return - exits the function and returns a value. Function state is lost.
  • yield - pauses the function and returns a value. Function state is preserved. Next call resumes from where it left off.
  • yield creates a generator; return creates a regular function.
09

Explain list comprehension in Python.

List comprehension provides a concise way to create lists. It is more readable and often faster than equivalent for loops.

Example
# Traditional
squares = []
for x in range(10):
  squares.append(x**2)

# List comprehension
squares = [x**2 for x in range(10)]

# With condition
evens = [x for x in range(10) if x % 2 == 0]
10

Compare list comprehension and generator expression.

  • List comprehension [x for x in ...] - creates the entire list in memory immediately.
  • Generator expression (x for x in ...) - creates a generator that yields values lazily. Memory-efficient for large datasets.
Example
lst = [x**2 for x in range(1000000)]  # entire list in memory
gen = (x**2 for x in range(1000000))  # lazy, one at a time
11

What is the difference between == and is in Python?

  • == - compares values (calls __eq__ method).
  • is - compares object identity (memory address). Checks if two variables point to the same object.
Example
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b)  # True (same values)
print(a is b)  # False (different objects)
print(a is c)  # True (same object)
12

Compare shallow copy and deep copy.

  • Shallow copy - copies the object but not nested objects. Nested objects are still referenced.
  • Deep copy - recursively copies the object and all nested objects. Completely independent.
Example
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

shallow[0][0] = 99  # affects original
deep[0][0] = 88     # does not affect original
13

Explain the difference between @staticmethod and @classmethod.

  • @staticmethod - does not receive implicit first argument. Cannot access class or instance state. Just a function grouped in a class namespace.
  • @classmethod - receives the class (cls) as the first argument. Can access class variables and create instances.
Example
class MyClass:
  count = 0
  @staticmethod
  def add(a, b): return a + b
  @classmethod
  def increment(cls): cls.count += 1
14

Compare __str__ and __repr__.

  • __str__ - human-readable string representation. Called by str() and print().
  • __repr__ - unambiguous string representation for developers. Called by repr() and in the REPL. Should ideally be valid Python code to recreate the object.
  • If only one is defined, define __repr__.
Example
class Point:
  def __init__(self, x, y): self.x, self.y = x, y
  def __repr__(self): return f"Point({self.x}, {self.y})"
  def __str__(self): return f"({self.x}, {self.y})"
15

What is the difference between append() and extend() for lists?

  • append(item) - adds a single item to the end of the list. If item is a list, the entire list is added as one element.
  • extend(iterable) - adds all elements from the iterable to the end of the list.
Example
a = [1, 2]
a.append([3, 4])  # [1, 2, [3, 4]]

b = [1, 2]
b.extend([3, 4])  # [1, 2, 3, 4]
16

Compare remove(), pop(), and del for lists.

  • remove(value) - removes the first occurrence of a value. Raises ValueError if not found.
  • pop(index) - removes and returns the element at index (default: last). Raises IndexError if empty.
  • del list[index] - deletes the element at index or a slice. Can also delete the entire list.
Example
lst = [1, 2, 3, 2]
lst.remove(2)  # [1, 3, 2]
lst.pop()      # returns 2, lst is [1, 3]
del lst[0]     # [3]
17

Explain the difference between range() and xrange().

xrange() existed in Python 2 and returned a generator. range() in Python 2 returned a list. In Python 3, range() behaves like Python 2 xrange() (returns a lazy range object), and xrange() was removed.

18

What is the Global Interpreter Lock (GIL)?

The GIL is a mutex that allows only one thread to execute Python bytecode at a time, even on multi-core systems. This simplifies memory management but limits CPU-bound parallelism. Use multiprocessing for CPU-bound tasks; threading still works for I/O-bound tasks.

19

Compare threading and multiprocessing in Python.

  • threading - multiple threads in one process. Shares memory. Limited by GIL for CPU-bound tasks. Good for I/O-bound tasks.
  • multiprocessing - multiple processes, each with its own Python interpreter and memory. No GIL limitation. Good for CPU-bound tasks.
Example
from multiprocessing import Pool

def square(x): return x * x

with Pool(4) as pool:
  results = pool.map(square, range(10))
20

Explain the difference between lambda and def.

  • lambda - anonymous function, single expression only, returns the expression result implicitly.
  • def - named function, multiple statements, explicit return.
  • Use lambda for short, throwaway functions; def for everything else.
Example
# lambda
square = lambda x: x**2

# def
def square(x):
  return x**2

# lambda in sorted()
students.sort(key=lambda s: s.age)
21

Compare map(), filter(), and reduce().

  • map(func, iterable) - applies func to each element, returns an iterator.
  • filter(func, iterable) - returns elements where func returns True.
  • reduce(func, iterable) - accumulates values into a single result. Requires from functools import reduce.
Example
nums = [1, 2, 3, 4, 5]
list(map(lambda x: x*2, nums))      # [2,4,6,8,10]
list(filter(lambda x: x%2==0, nums)) # [2,4]
from functools import reduce
reduce(lambda a,b: a+b, nums)    # 15
22

What is the difference between __init__ and __new__?

  • __new__(cls) - creates and returns a new instance. Called before __init__. Rarely overridden.
  • __init__(self) - initializes the instance after it is created. Most common place for setup logic.
Example
class Singleton:
  _instance = None
  def __new__(cls):
    if cls._instance is None:
      cls._instance = super().__new__(cls)
    return cls._instance
23

Compare instance variables, class variables, and local variables.

  • Instance variables (self.x) - unique to each instance. Defined in __init__.
  • Class variables - shared across all instances. Defined at class level.
  • Local variables - exist only within a function scope.
Example
class MyClass:
  class_var = 0  # class variable
  def __init__(self, x):
    self.x = x   # instance variable
24

Explain the difference between @property and getters/setters.

@property allows you to define methods that are accessed like attributes, providing controlled access to private variables without explicit getter/setter calls.

Example
class User:
  def __init__(self, name):
    self._name = name
  @property
  def name(self): return self._name
  @name.setter
  def name(self, value):
    if not value: raise ValueError("Name required")
    self._name = value

user.name = "Alice"  # calls setter
25

Compare is None and == None.

is None checks object identity (recommended). == None checks equality (calls __eq__). Always use is None because None is a singleton - there is only one None object in Python.

Example
if value is None:  # correct
  pass
if value == None:  # works but not idiomatic
  pass
26

What is the difference between mutable and immutable types in Python?

  • Immutable - cannot be changed after creation: int, float, str, tuple, frozenset.
  • Mutable - can be modified in place: list, dict, set.
  • Immutable objects are hashable and can be dict keys; mutable objects cannot.
27

Compare pass, continue, and break.

  • pass - does nothing. Placeholder for empty code blocks.
  • continue - skips the rest of the current loop iteration and moves to the next.
  • break - exits the loop entirely.
28

Explain the difference between try/except/else/finally.

  • try - code that might raise an exception.
  • except - handles the exception.
  • else - runs if no exception was raised.
  • finally - always runs, whether or not an exception occurred. Used for cleanup.
Example
try:
  result = risky_operation()
except ValueError as e:
  handle_error(e)
else:
  process_result(result)
finally:
  cleanup()
29

What is the difference between raise and assert?

  • raise - explicitly raises an exception. Used for error handling.
  • assert condition, message - raises AssertionError if condition is False. Used for debugging and testing. Can be disabled with python -O.
Example
if age < 0:
  raise ValueError("Age cannot be negative")

assert age >= 0, "Age must be non-negative"
30

Explain the with statement and context managers.

The with statement ensures proper resource cleanup using context managers (__enter__ and __exit__ methods). Commonly used for file handling, database connections, and locks.

Example
with open("file.txt", "r") as f:
  data = f.read()
# file is automatically closed

# Custom context manager
from contextlib import contextmanager
@contextmanager
def timer():
  start = time.time()
  yield
  print(f"Elapsed: {time.time() - start}s")
31

Compare __getattr__ and __getattribute__.

  • __getattribute__(self, name) - called for every attribute access. Can cause infinite recursion if not careful.
  • __getattr__(self, name) - called only when the attribute is not found via normal lookup. Safer fallback mechanism.
32

What is the difference between pickle and JSON?

  • pickle - Python-specific binary serialization. Can serialize almost any Python object including functions. Not secure - do not unpickle untrusted data.
  • JSON - text-based, language-agnostic. Only supports basic types (dict, list, str, int, float, bool, None). Secure and portable.
Example
import pickle, json
data = {"name": "Alice", "age": 30}
pickle.dumps(data)  # binary
json.dumps(data)    # text
33

Compare enumerate() and range().

  • range(n) - generates a sequence of numbers from 0 to n-1.
  • enumerate(iterable, start=0) - returns (index, value) pairs from an iterable.
Example
for i in range(len(items)):
  print(i, items[i])

# Better with enumerate
for i, item in enumerate(items):
  print(i, item)
34

Explain the difference between zip() and itertools.zip_longest().

  • zip(*iterables) - stops when the shortest iterable is exhausted.
  • itertools.zip_longest(*iterables, fillvalue=None) - continues until the longest iterable is exhausted, filling missing values.
Example
a = [1, 2, 3]
b = ["a", "b"]
list(zip(a, b))  # [(1,"a"), (2,"b")]

from itertools import zip_longest
list(zip_longest(a, b, fillvalue="-"))  # [(1,"a"), (2,"b"), (3,"-")]
35

Compare any() and all().

  • any(iterable) - returns True if at least one element is truthy.
  • all(iterable) - returns True only if all elements are truthy.
Example
print(any([False, 0, 1]))  # True
print(all([True, 1, "a"]))  # True
print(all([True, 0, "a"]))  # False
36

What is the difference between sort() and sorted()?

  • list.sort() - sorts the list in place. Returns None. Modifies the original list.
  • sorted(iterable) - returns a new sorted list. Does not modify the original.
Example
a = [3, 1, 2]
a.sort()       # a is now [1, 2, 3]

b = [3, 1, 2]
c = sorted(b)  # b unchanged, c is [1, 2, 3]
37

Compare join() and split().

  • str.split(sep) - splits a string into a list of substrings.
  • sep.join(iterable) - joins elements of an iterable into a single string with sep as separator.
Example
s = "a,b,c"
parts = s.split(",")  # ["a", "b", "c"]
joined = ",".join(parts)  # "a,b,c"
38

Explain the difference between __call__ and regular methods.

__call__ makes an instance callable like a function. Regular methods are called with dot notation.

Example
class Multiplier:
  def __init__(self, factor): self.factor = factor
  def __call__(self, x): return x * self.factor

double = Multiplier(2)
print(double(5))  # 10 - instance called like a function
39

Compare staticmethod, classmethod, and instance method.

  • Instance method - receives self. Can access instance and class variables.
  • @classmethod - receives cls. Can access class variables, cannot access instance variables. Used for factory methods.
  • @staticmethod - receives neither self nor cls. Cannot access instance or class variables. Just a utility function.
40

What is the difference between __init__.py and regular Python files?

__init__.py marks a directory as a Python package, allowing imports from it. It can be empty or contain package initialization code. Python 3.3+ supports namespace packages without __init__.py, but it is still recommended for explicit package definition.

41

Compare import module and from module import name.

  • import module - imports the entire module. Access members with module.name.
  • from module import name - imports specific names directly into the current namespace.
  • from module import * - imports all public names (avoid - pollutes namespace).
Example
import math
math.sqrt(16)

from math import sqrt
sqrt(16)
42

Explain the difference between __name__ == "__main__" and module-level code.

Code under if __name__ == "__main__": only runs when the script is executed directly, not when imported as a module. Module-level code (outside the if) runs on both direct execution and import.

Example
def main():
  print("Running main")

if __name__ == "__main__":
  main()  # only runs when script is executed directly
43

Compare *args unpacking and **kwargs unpacking.

  • *iterable - unpacks an iterable into positional arguments.
  • **dict - unpacks a dict into keyword arguments.
Example
def func(a, b, c): print(a, b, c)

args = [1, 2, 3]
func(*args)  # unpacks to func(1, 2, 3)

kwargs = {"a": 1, "b": 2, "c": 3}
func(**kwargs)  # unpacks to func(a=1, b=2, c=3)
44

What is the difference between type() and isinstance()?

  • type(obj) - returns the exact type of obj. Does not consider inheritance.
  • isinstance(obj, class) - checks if obj is an instance of class or any subclass. Respects inheritance.
  • Always prefer isinstance() for type checking.
Example
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(type(d) == Dog)        # True
print(type(d) == Animal)     # False
print(isinstance(d, Animal)) # True
45

Compare dict.get() and dict[key].

  • dict[key] - raises KeyError if key does not exist.
  • dict.get(key, default) - returns default (None if not specified) if key does not exist. Safer.
Example
d = {"name": "Alice"}
print(d["age"])       # KeyError
print(d.get("age"))   # None
print(d.get("age", 0)) # 0
46

Explain the difference between itertools.chain() and list concatenation.

itertools.chain(*iterables) creates a lazy iterator that chains multiple iterables without creating a new list in memory. list1 + list2 creates a new list immediately. Use chain() for memory efficiency with large iterables.

Example
from itertools import chain
for item in chain([1,2], [3,4], [5,6]):
  print(item)  # 1,2,3,4,5,6 - no intermediate list created
47

Compare collections.defaultdict and regular dict.

defaultdict automatically creates a default value for missing keys using a factory function. Regular dict raises KeyError for missing keys.

Example
from collections import defaultdict

# Regular dict
d = {}
d["key"] += 1  # KeyError

# defaultdict
d = defaultdict(int)  # int() returns 0
d["key"] += 1  # works, d["key"] is now 1
48

What is the difference between collections.Counter and dict?

Counter is a dict subclass for counting hashable objects. It provides convenient methods like most_common(), elements(), and arithmetic operations on counts.

Example
from collections import Counter
c = Counter(["a", "b", "a", "c", "a"])
print(c)  # Counter({"a": 3, "b": 1, "c": 1})
print(c.most_common(2))  # [("a", 3), ("b", 1)]
49

Compare collections.namedtuple and dataclasses.

  • namedtuple - immutable, lightweight, tuple-like. Access fields by name or index.
  • dataclass (Python 3.7+) - mutable by default, more features (default values, type hints, methods). Generates __init__, __repr__, __eq__ automatically.
Example
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)

from dataclasses import dataclass
@dataclass
class Point:
  x: int
  y: int = 0  # default value
50

Explain the difference between f-strings, format(), and % formatting.

  • % formatting - oldest, C-style. "Hello %s" % name.
  • str.format() - more powerful. "Hello {}".format(name).
  • f-strings (Python 3.6+) - fastest, most readable. f"Hello {name}". Supports expressions.
Example
name = "Alice"
age = 30
print(f"{name} is {age} years old")  # f-string (preferred)
print("{} is {} years old".format(name, age))
print("%s is %d years old" % (name, age))
Role-Based Preparation

Prepare across the full job, not one topic at a time.

Use curated frontend, Java, Python, or cloud and DevOps packs with 200 questions, model answers, code examples, and a seven-day plan.

200 curated Q&As

Combine four related topic areas into one focused role-preparation path.

Examples and trade-offs

Practise explaining code, design decisions, limitations, and failure modes.

Seven-day plan

Move from baseline review to a timed mock interview and final checklist.

Next Step
Use Python interview prep to move into practice and application.

After reviewing interview answers, reinforce the topic with a relevant guide, hands-on practice, or a stronger resume story.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.