Tutorials Logic, IN info@tutorialslogic.com
Python

Top 50 Python Interview Questions

Curated questions covering OOP, decorators, generators, list comprehensions, data structures, Django, Flask, and Python 3 features.

01

What is Python and what are its key features?

Python is a high-level, interpreted, dynamically-typed language. Key features: simple syntax, extensive standard library, dynamic typing, automatic memory management (garbage collection), supports multiple paradigms (OOP, functional, procedural), and a vast ecosystem (Django, Flask, NumPy, pandas).

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.
03

What is the difference between list, tuple, and set?

  • list - mutable, ordered, allows duplicates. Syntax: [1, 2, 3].
  • tuple - immutable, ordered, allows duplicates. Syntax: (1, 2, 3). Faster than list.
  • set - mutable, unordered, no duplicates. Syntax: {1, 2, 3}. O(1) membership test.
Example
lst = [1, 2, 2, 3]
tpl = (1, 2, 2, 3)
st = {1, 2, 2, 3}  # {1, 2, 3} - duplicates removed
04

What is the difference between list and dict?

  • list - ordered collection indexed by integers. O(n) lookup by value.
  • dict - unordered (Python 3.7+ maintains insertion order) key-value pairs. O(1) lookup by key.
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 wraps a function to extend its behavior without modifying it. Used for logging, authentication, caching, and timing.

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

What is the difference between *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

What is 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

What is 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

What is the difference between 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

What is the difference between 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

What is 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

What is the difference between __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

What is the difference between 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

What is 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

What is the difference between 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

What is 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

What is the difference between 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

What is the difference between 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

What is 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

What is the difference between 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

What is the difference between 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

What is 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

What is 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

What is the difference between __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

What is the difference between 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

What is 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

What is the difference between 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

What is the difference between 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

What is 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

What is the difference between 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

What is the difference between 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

What is 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

What is the difference between *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

What is the difference between 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

What is 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

What is the difference between 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

What is the difference between 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

What is 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))
Next Step

Use Python interview prep to move into practice and application.

After reviewing interview answers, strengthen the same topic with tutorials, coding practice, or a stronger resume story.

Ready to Level Up Your Skills?

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