🐍 Beginner Python Interview Questions
This lesson covers the fundamental Python concepts every developer must know. Master data types, mutability, functions, comprehensions, OOP basics, modules, exceptions, and the standard library. These questions reflect what interviewers ask at junior and entry-level Python roles.
Questions & Answers
01 What is Python and what are its key characteristics? ►
Core Python is a high-level, interpreted, dynamically-typed, general-purpose programming language created by Guido van Rossum in 1991. It emphasises readability, has a vast standard library (“batteries included”), and supports multiple programming paradigms.
Key characteristics:
- Interpreted โ executed line by line by CPython; no separate compile step needed
- Dynamically typed โ variable types are determined at runtime, not declared
- Strongly typed โ types do not coerce implicitly;
"5" + 5raisesTypeError - Multi-paradigm โ procedural, object-oriented, and functional styles
- Indentation-based โ code blocks defined by whitespace, not braces
- Garbage collected โ automatic memory via reference counting + cyclic GC
- Cross-platform โ runs on Windows, macOS, Linux unchanged
x = 42 # no type declaration x = "now a string" # type changes at runtime (dynamic typing) "5" + 5 # TypeError: can only concatenate str (not int)
02 What are Python’s built-in data types? ►
Data Types
Numeric: int (arbitrary precision), float, complex, bool (subclass of int)
Sequences: str (immutable), list (mutable), tuple (immutable), range (lazy)
Mapping: dict โ insertion-ordered key-value pairs since Python 3.7
Sets: set (mutable, unique), frozenset (immutable)
None: NoneType โ the singleton null value
n = 42; f = 3.14; b = True
s = "hello"; t = (1, 2); lst = [1, 2]
d = {"a": 1}; st = {1, 2, 3}
type(42) # <class 'int'>
isinstance(True, int) # True -- bool IS-A int
03 What is the difference between mutable and immutable objects? ►
Core Immutable objects cannot be changed after creation: int, float, str, tuple, frozenset. Mutable objects can be modified in place: list, dict, set.
lst = [1, 2, 3]
original_id = id(lst)
lst.append(4)
id(lst) == original_id # True -- same object (mutated in place)
# Classic gotcha: mutable default argument
def append_item(item, lst=[]): # BAD: default [] shared across ALL calls
lst.append(item)
return lst
append_item(1) # [1]
append_item(2) # [1, 2] -- NOT [2]!
# Fix: use None as sentinel
def append_item(item, lst=None):
if lst is None: lst = []
lst.append(item)
return lst
04 What are list comprehensions? How do they compare to loops? ►
Syntax List comprehensions provide a concise, Pythonic way to create lists. They are generally faster than equivalent for loops.
# [expression for item in iterable if condition]
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
# Dict and set comprehensions
word_len = {w: len(w) for w in ["hello", "world"]}
unique_ln = {len(w) for w in ["hello", "world", "hi"]}
# Equivalent loop (slower, more verbose)
squares = []
for x in range(10): squares.append(x**2)
# Generator expression -- lazy, O(1) memory regardless of size
total = sum(x**2 for x in range(1_000_000))
05 What is the difference between *args and **kwargs? ►
Functions *args collects extra positional arguments into a tuple. **kwargs collects extra keyword arguments into a dict.
def demo(*args, **kwargs):
print(args) # tuple: (1, 2, 3)
print(kwargs) # dict: {'name': 'Alice', 'age': 30}
demo(1, 2, 3, name="Alice", age=30)
# Order: positional, *args, keyword-only, **kwargs
def full(a, b, *args, sep=", ", **kwargs): ...
# Unpacking with * and **
def add(a, b, c): return a + b + c
nums = [1, 2, 3]
add(*nums) # add(1, 2, 3)
add(**{"a":1,"b":2,"c":3})
06 What are Python decorators? How do you write one? ►
Functions A decorator wraps a function with extra behaviour. It is syntactic sugar for func = decorator(func). Common uses: logging, timing, authentication, caching.
import functools, time
def timer(func):
@functools.wraps(func) # preserve __name__, __doc__
def wrapper(*args, **kwargs):
t0 = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__!r} took {time.perf_counter()-t0:.4f}s")
return result
return wrapper
@timer
def greet(name): return f"Hello, {name}!"
greet("Alice") # 'greet' took 0.0000s
# Decorator with arguments (factory pattern)
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_attempts):
try: return func(*args, **kwargs)
except:
if i == max_attempts-1: raise
return wrapper
return decorator
07 What is the difference between is and == in Python? ►
Core == tests value equality (calls __eq__). is tests identity โ whether both names point to the exact same object in memory.
a = [1, 2, 3]; b = [1, 2, 3]; c = a a == b # True -- equal values a is b # False -- different objects a is c # True -- c is an alias for a # Always use 'is' for None comparisons x = None x is None # correct (only one None object) x == None # works but poor style # CPython caches small integers (-5..256) -- never rely on this x = 256; y = 256; x is y # True (cached) x = 257; y = 257; x is y # False (not cached)
08 What are generators and the yield keyword? ►
Iterators A generator produces values lazily one at a time using yield. It pauses execution and resumes from where it left off. Uses constant memory regardless of sequence size.
def countdown(n):
while n > 0:
yield n # pause, send n, resume on next()
n -= 1
for v in countdown(5): print(v) # 5 4 3 2 1
# Generator expression -- lazy, memory-efficient
gen = (x**2 for x in range(1_000_000))
next(gen) # 0
next(gen) # 1
# yield from -- delegate to a sub-generator
def chain(*iterables):
for it in iterables: yield from it
list(chain([1,2], [3,4])) # [1, 2, 3, 4]
09 What is the difference between list, tuple, set, and dict? ►
Data Types
- list โ ordered, mutable, duplicates allowed:
[1,2,3] - tuple โ ordered, immutable, duplicates allowed:
(1,2,3)โ usable as dict key - set โ unordered, mutable, unique values:
{1,2,3}โ O(1) membership test - dict โ ordered (3.7+), mutable, key-value pairs:
{"a":1}โ O(1) lookup
from collections import Counter, defaultdict
c = Counter("abracadabra") # {'a':5,'b':2,'r':2,'c':1,'d':1}
d = defaultdict(list) # auto-creates [] for missing keys
# Performance comparison
big_list = list(range(1_000_000))
big_set = set(range(1_000_000))
999_999 in big_list # O(n) -- slow
999_999 in big_set # O(1) -- fast
10 What is a lambda function? When should you use one? ►
Functions A lambda is an anonymous one-expression function. Best used as a short key or callback; prefer def for anything more complex.
square = lambda x: x**2
add = lambda x, y: x + y
# Sort by key
students = [{"name":"Bob","grade":85},{"name":"Alice","grade":92}]
students.sort(key=lambda s: s["grade"])
words = ["banana","apple","cherry"]
sorted(words, key=lambda w: len(w)) # sort by length
# map / filter
squares = list(map(lambda x: x**2, range(5)))
evens = list(filter(lambda x: x%2==0, range(10)))
11 What is the GIL (Global Interpreter Lock) in CPython? ►
Concurrency The GIL is a mutex allowing only one thread to execute Python bytecode at a time. It simplifies memory management but limits true CPU parallelism.
- I/O-bound tasks โ GIL released during I/O; threads run effectively concurrently. Use
threading. - CPU-bound tasks โ only one thread runs at a time. Use
multiprocessingor C extensions (NumPy releases the GIL).
import concurrent.futures
# I/O-bound -- threading is effective
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as ex:
results = list(ex.map(download_url, urls))
# CPU-bound -- use separate processes
with concurrent.futures.ProcessPoolExecutor() as ex:
results = list(ex.map(heavy_compute, data_chunks))
# Python 3.13 -- experimental free-threaded build (no GIL)
# python3.13t -- disables GIL, enables true thread parallelism
12 What are Python’s dunder (magic) methods? ►
OOP Dunder methods allow custom objects to integrate with Python built-ins and operators. Python calls them implicitly in certain contexts.
class Vector:
def __init__(self, x, y): self.x, self.y = x, y
def __repr__(self): return f"Vector({self.x}, {self.y})"
def __str__(self): return f"({self.x}, {self.y})"
def __add__(self, other): return Vector(self.x+other.x, self.y+other.y)
def __mul__(self, s): return Vector(self.x*s, self.y*s)
def __eq__(self, other): return self.x==other.x and self.y==other.y
def __hash__(self): return hash((self.x, self.y))
def __len__(self): return 2
def __iter__(self): yield self.x; yield self.y
def __getitem__(self, i): return (self.x, self.y)[i]
def __bool__(self): return self.x!=0 or self.y!=0
def __enter__(self): return self
def __exit__(self, *a): pass
13 What is inheritance and polymorphism in Python? ►
OOP
class Animal:
def __init__(self, name): self.name = name
def speak(self): raise NotImplementedError
class Dog(Animal):
def speak(self): return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self): return f"{self.name} says Meow!"
# Polymorphism -- same interface, different behaviour
for animal in [Dog("Rex"), Cat("Whiskers")]:
print(animal.speak())
# super() -- call parent method
class Retriever(Dog):
def speak(self): return super().speak() + " (tail wagging)"
isinstance(Dog("Rex"), Animal) # True
issubclass(Dog, Animal) # True
14 What is the difference between a module and a package? ►
Modules A module is a single .py file. A package is a directory containing an __init__.py that groups related modules.
import os # module
from os import path # name from module
from os.path import join, exists # specific names
import numpy as np # alias
# Relative imports (inside a package)
from . import utils # same package
from .models import User # sub-module
from ..utils import helper # parent package
# __all__ -- controls what `import *` exports
__all__ = ["public_func"]
# Only run when executed directly (not when imported)
if __name__ == "__main__":
main()
15 What is exception handling in Python? ►
Errors
try:
result = int("abc") # raises ValueError
except ValueError as e:
print(f"Value error: {e}")
except (FileNotFoundError, OSError):
pass
except Exception:
raise # re-raise unknown errors
else:
print("No exception raised") # runs only if try succeeded
finally:
print("Always runs") # cleanup code
# Custom exception
class InsufficientFundsError(ValueError):
def __init__(self, balance, amount):
super().__init__(f"Need {amount}, have {balance}")
# Exception chaining
try: connect_db()
except ConnectionError as e:
raise RuntimeError("DB unavailable") from e
# Suppress specific exceptions silently
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("temp.txt")
16 What are context managers and the with statement? ►
Core Context managers ensure resources are acquired and released cleanly โ even if an exception occurs. They call __enter__ on entry and __exit__ on exit.
with open("file.txt") as f:
data = f.read()
# file closed here even on exception
# @contextmanager -- simplest custom context manager
from contextlib import contextmanager
@contextmanager
def db_transaction(conn):
try:
yield conn # code in `with` block runs here
conn.commit()
except Exception:
conn.rollback()
raise
with db_transaction(connection) as conn:
conn.execute("INSERT ...")
17 What are Python string formatting methods? ►
Strings
name = "Alice"; score = 98.765; count = 1_000_000
# f-strings (Python 3.6+) -- preferred, fastest
f"Hello, {name}!" # Hello, Alice!
f"Score: {score:.2f}" # Score: 98.77
f"Count: {count:,}" # Count: 1,000,000
f"{name!r}" # 'Alice' (repr)
f"{2 ** 10 = }" # 2 ** 10 = 1024 (debug, 3.8+)
# str.format() -- older but common
"Hello, {name}!".format(name=name)
# % formatting -- legacy, still seen in logging
"Hello, %s! Score: %.1f" % (name, score)
# Raw strings -- no backslash interpretation
path = r"C:\Users\Alice\Documents"
18 What is the difference between deepcopy and shallow copy? ►
Core Shallow copy creates a new container but shares references to nested objects. Deep copy recursively copies everything โ fully independent.
import copy original = [[1,2,3],[4,5,6]] shallow = copy.copy(original) # or: original[:] shallow[0].append(99) print(original[0]) # [1,2,3,99] -- inner list shared! deep = copy.deepcopy(original) deep[0].append(99) print(original[0]) # [1,2,3] -- completely independent
19 What are Python’s built-in higher-order functions? ►
Functions
from functools import reduce, partial
nums = [1,2,3,4,5,6,7,8,9,10]
list(map(lambda x: x**2, nums)) # [1,4,9,16,25,...]
list(filter(lambda x: x%2==0, nums)) # [2,4,6,8,10]
reduce(lambda a,x: a+x, nums) # 55
# sorted with key
sorted(["banana","apple"], key=len) # ['apple','banana']
# zip and enumerate
for i, name in enumerate(["Alice","Bob"], start=1):
print(f"{i}. {name}")
# partial -- freeze some arguments
double = partial(lambda f,x: x*f, 2)
double(5) # 10
20 What is the difference between @staticmethod, @classmethod, and instance methods? ►
OOP
class Temperature:
_count = 0
def __init__(self, c):
self.celsius = c
Temperature._count += 1
# Instance method -- receives self (the instance)
def to_fahrenheit(self): return self.celsius * 9/5 + 32
# Class method -- receives cls (the class); common as alternative constructor
@classmethod
def from_fahrenheit(cls, f): return cls((f-32)*5/9)
@classmethod
def get_count(cls): return cls._count
# Static method -- no self or cls; just a namespaced function
@staticmethod
def is_valid(value): return value >= -273.15
t = Temperature.from_fahrenheit(212) # 100.0
Temperature.is_valid(-300) # False
21 What is pip and how do virtual environments work? ►
Tooling
python -m venv .venv # create virtual environment source .venv/bin/activate # activate (Linux/Mac) .venv\Scripts\activate # activate (Windows) deactivate # deactivate pip install requests # install latest pip install requests==2.31.0 # specific version pip install -r requirements.txt # from file pip freeze > requirements.txt # snapshot # Why virtual environments? # Each project gets its own isolated dependencies # Prevents version conflicts between projects # System Python remains clean # Modern alternatives: # uv -- ultra-fast Rust-based package manager # poetry -- dependency management + packaging
22 What is PEP 8 and why does it matter? ►
Style PEP 8 is Python’s official style guide. Consistent style makes code easier to read and maintain across teams.
- Indentation โ 4 spaces (never tabs)
- Naming โ
snake_casefor variables/functions,PascalCasefor classes,UPPER_CASEfor constants - Imports โ stdlib, then third-party, then local; one module per line
- Line length โ 79 chars (88 for Black formatter)
# Enforcement tools:
# ruff -- ultra-fast linter + formatter (recommended, replaces flake8+isort+pyupgrade)
# black -- opinionated auto-formatter
# mypy -- static type checker
# Type hints (encouraged for public APIs)
def greet(name: str, times: int = 1) -> str:
return (name + " ") * times
📝 Knowledge Check
Test your understanding of Python fundamentals.