Part 1: Python Basics (Q1βQ10)
Q1. What is Python and what are its key features?
Python is a high-level, interpreted, dynamically-typed programming language known for its clear syntax and readability. Key features include: dynamic typing, automatic memory management via garbage collection, a vast standard library, support for multiple programming paradigms (procedural, OOP, functional), and a huge ecosystem of third-party packages via pip.
Q2. What is the difference between a compiled and an interpreted language? Where does Python fit?
Compiled languages (C, C++) translate source code directly to machine code before execution. Interpreted languages execute source code line by line via an interpreter. Python is primarily interpreted β the CPython interpreter compiles Python source to bytecode (.pyc), then the Python Virtual Machine (PVM) executes that bytecode. This makes Python slower than compiled languages but far more portable and developer-friendly.
Q3. What is the difference between is and ==?
== compares values; is compares identity (whether two variables point to the exact same object in memory).
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True β same value
print(a is b) # False β different objects in memory
print(a is c) # True β c points to the same object as a
# Small integer caching β CPython caches -5 to 256
x = 256
y = 256
print(x is y) # True (cached)
x = 257
y = 257
print(x is y) # False (not cached β implementation detail)
Q4. What is a mutable vs immutable type? Give examples.
Mutable objects can be changed after creation: list, dict, set, bytearray. Immutable objects cannot be changed: int, float, str, tuple, frozenset, bytes. This distinction matters for function arguments, dict keys (must be hashable/immutable), and performance optimisation.
Q5. What are Python's built-in data types?
| Category | Types |
|---|---|
| Numeric | int, float, complex |
| Sequence | str, list, tuple, range, bytes |
| Mapping | dict |
| Set | set, frozenset |
| Boolean | bool |
| None | NoneType |
Q6. What is the difference between deepcopy and copy?
import copy
original = [[1, 2], [3, 4]]
# Shallow copy β copies the outer object, inner objects are shared
shallow = copy.copy(original)
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] β AFFECTED!
original = [[1, 2], [3, 4]]
# Deep copy β recursively copies all nested objects
deep = copy.deepcopy(original)
deep[0].append(99)
print(original) # [[1, 2], [3, 4]] β NOT affected
Q7. What is the GIL (Global Interpreter Lock)?
The GIL is a mutex in CPython that allows only one thread to execute Python bytecode at a time, even on multi-core machines. It simplifies memory management (reference counting) but limits CPU-bound multi-threading. Workarounds: use multiprocessing (separate processes, no GIL), or use async I/O (asyncio) for I/O-bound tasks. The GIL is being progressively weakened in Python 3.12+ and made optional in 3.13.
Q8. What are Python's namespace and scope? Explain LEGB.
LEGB is the order Python searches for variable names: Local β Enclosing β Global β Built-in. Python first checks the innermost local scope, then enclosing function scopes (for closures), then the module global scope, then Python's built-ins.
Q9. What is the difference between *args and **kwargs?
def demo(*args, **kwargs):
print(args) # tuple of positional args
print(kwargs) # dict of keyword args
demo(1, 2, 3, name="Alice", age=30)
# (1, 2, 3)
# {'name': 'Alice', 'age': 30}
# Unpacking when calling functions
nums = [1, 2, 3]
info = {"name": "Bob"}
demo(*nums, **info) # unpacks list and dict
Q10. What is PEP 8?
PEP 8 is Python's official style guide. Key rules: use 4 spaces for indentation (not tabs), lines β€ 79 characters, snake_case for functions/variables, PascalCase for classes, UPPER_CASE for constants, two blank lines between top-level functions/classes, one blank line between methods. Use flake8 or ruff to automatically check compliance.
Part 2: Data Structures (Q11βQ20)
Q11. What is the difference between a list and a tuple?
Lists are mutable, tuples are immutable. Tuples are slightly faster to create and iterate; they can be used as dictionary keys. Use lists when you need to modify the collection; use tuples for fixed data like coordinates or records. Tuples also signal intent: "this data should not change."
Q12. How does a Python dictionary work internally?
Python dictionaries are implemented as hash tables. Each key is hashed to a slot in the table. Lookup, insertion, and deletion are O(1) average-case. Since Python 3.7, dictionaries maintain insertion order. Keys must be hashable (immutable). Hash collisions are resolved via open addressing.
Q13. What is the difference between a set and a frozenset?
A set is mutable β you can add/remove elements. A frozenset is immutable and hashable, so it can be used as a dictionary key or placed inside another set. Both guarantee uniqueness and provide O(1) membership testing.
Q14. What are list comprehensions? How do they compare to map/filter?
nums = [1, 2, 3, 4, 5]
# List comprehension (preferred β more Pythonic)
squares = [x**2 for x in nums if x % 2 == 0]
print(squares) # [4, 16]
# Equivalent with map + filter
squares = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, nums)))
print(squares) # [4, 16]
# Comprehensions are generally faster and more readable
# Also available: dict/set comprehensions and generator expressions
d = {x: x**2 for x in range(5)} # {0:0, 1:1, 2:4, 3:9, 4:16}
Q15. How do you remove duplicates from a list while preserving order?
data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
# Fast (Python 3.7+ dicts maintain insertion order)
unique = list(dict.fromkeys(data))
print(unique) # [3, 1, 4, 5, 9, 2, 6]
# Alternative using a seen set
def remove_dupes(lst):
seen = set()
return [x for x in lst if not (x in seen or seen.add(x))]
Q16. What is the time complexity of common list and dict operations?
| Operation | List | Dict/Set |
|---|---|---|
| Access by index/key | O(1) | O(1) avg |
| Search (in) | O(n) | O(1) avg |
| Append / insert at end | O(1) amortised | O(1) avg |
| Insert at position i | O(n) | N/A |
| Delete by index/key | O(n) | O(1) avg |
| Sort | O(n log n) | N/A |
Q17. How do defaultdict and Counter work?
from collections import defaultdict, Counter
# defaultdict β supplies a default value for missing keys
word_count = defaultdict(int)
for word in ["apple", "banana", "apple", "cherry", "banana", "apple"]:
word_count[word] += 1
print(dict(word_count)) # {'apple': 3, 'banana': 2, 'cherry': 1}
# Counter β specialised dict for counting
counts = Counter(["apple", "banana", "apple", "cherry", "banana", "apple"])
print(counts.most_common(2)) # [('apple', 3), ('banana', 2)]
print(counts["apple"]) # 3
print(counts["missing"]) # 0 (no KeyError!)
Q18. How do you sort a list of dictionaries by a key?
from operator import itemgetter
people = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Carol", "age": 35},
]
# Sort by age ascending
by_age = sorted(people, key=lambda p: p["age"])
print([p["name"] for p in by_age]) # ['Bob', 'Alice', 'Carol']
# Sort by age descending
by_age_desc = sorted(people, key=itemgetter("age"), reverse=True)
# Sort by multiple keys: primary age, secondary name
by_multi = sorted(people, key=lambda p: (p["age"], p["name"]))
Q19. What is slicing? Explain the syntax a[start:stop:step].
s = "Hello, World!"
print(s[0:5]) # Hello
print(s[7:]) # World!
print(s[:5]) # Hello
print(s[-6:]) # orld! β last 6 characters
print(s[::2]) # Hlo ol! β every other char
print(s[::-1]) # !dlroW ,olleH β reverse
lst = list(range(10))
print(lst[2:8:2]) # [2, 4, 6]
Q20. What is the difference between append(), extend(), and insert()?
lst = [1, 2, 3]
lst.append([4, 5]) # Adds ONE element (the list itself)
print(lst) # [1, 2, 3, [4, 5]]
lst = [1, 2, 3]
lst.extend([4, 5]) # Adds EACH element of the iterable
print(lst) # [1, 2, 3, 4, 5]
lst.insert(1, 99) # Inserts 99 at index 1
print(lst) # [1, 99, 2, 3, 4, 5]
Part 3: Object-Oriented Programming (Q21βQ30)
Q21. What are the four pillars of OOP?
Encapsulation β bundling data and methods; hiding internal state. Abstraction β exposing only relevant interface. Inheritance β a class deriving properties from a parent. Polymorphism β different classes sharing a common interface, behaving differently.
Q22. What is the difference between __init__ and __new__?
__new__ creates and returns the instance (object allocation). __init__ initialises the already-created instance. In practice, you almost never override __new__ unless implementing singletons or immutable types (subclassing int, str).
Q23. What are class methods vs static methods vs instance methods?
class MyClass:
count = 0
def instance_method(self):
# Has access to self (instance) and MyClass (via self.__class__)
return f"instance, count={MyClass.count}"
@classmethod
def class_method(cls):
# Has access to cls (the class itself, not the instance)
# Used as alternative constructors
cls.count += 1
return cls()
@staticmethod
def static_method(x, y):
# No access to self or cls β just a regular function namespaced to the class
return x + y
obj = MyClass()
MyClass.class_method() # Increments count
MyClass.static_method(3, 4) # Returns 7
Q24. What is multiple inheritance? What is the MRO?
Python supports multiple inheritance β a class can inherit from more than one parent. The Method Resolution Order (MRO) determines which parent's method is called. Python uses the C3 linearisation algorithm. Use ClassName.__mro__ or help(ClassName) to inspect it.
class A:
def hello(self): return "A"
class B(A):
def hello(self): return "B"
class C(A):
def hello(self): return "C"
class D(B, C): # Diamond inheritance
pass
d = D()
print(d.hello()) # "B" β MRO: D β B β C β A
print(D.__mro__) # (D, B, C, A, object)
Q25. What are dunder (magic) methods? Give five examples.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self): # unambiguous string representation
return f"Vector({self.x}, {self.y})"
def __str__(self): # human-readable string
return f"({self.x}, {self.y})"
def __add__(self, other): # v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __len__(self): # len(v)
return 2
def __eq__(self, other): # v1 == v2
return self.x == other.x and self.y == other.y
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # (4, 6)
print(len(v1)) # 2
Q26. What is a property? Why use it instead of a getter method?
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2
c = Circle(5)
print(c.radius) # 5 β looks like attribute access, not method call
c.radius = 10 # triggers the setter
print(c.area) # 314.159...
Q27. What is the difference between __getattr__ and __getattribute__?
__getattribute__ is called on every attribute access. __getattr__ is only called when the attribute is not found through normal means. Override __getattr__ to provide dynamic attributes (like Mock objects do). Avoid overriding __getattribute__ β infinite recursion is easy to trigger.
Q28. What are abstract base classes?
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
def perimeter(self):
return 2 * (self.w + self.h)
# Shape() # TypeError: Can't instantiate abstract class
r = Rectangle(3, 4)
print(r.area()) # 12
Q29. What is a descriptor?
A descriptor is any object that defines __get__, __set__, or __delete__. Descriptors power Python's property, classmethod, staticmethod, and many ORM fields. They are the mechanism behind attribute lookup in Python's data model.
Q30. What is dataclass and when would you use it?
from dataclasses import dataclass, field
@dataclass(order=True)
class Employee:
name: str
department: str
salary: float = 50_000.0
skills: list = field(default_factory=list)
def give_raise(self, pct):
self.salary *= (1 + pct / 100)
e = Employee("Alice", "Engineering", 90_000)
e.skills.append("Python")
print(e) # Employee(name='Alice', department='Engineering', salary=90000.0, skills=['Python'])
print(e == Employee("Alice", "Engineering", 90_000.0, ["Python"])) # True
Part 4: Functions & Closures (Q31βQ38)
Q31. What is a closure?
def make_multiplier(n):
def multiplier(x):
return x * n # 'n' is closed over from the enclosing scope
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# Inspect closed variables
print(double.__closure__[0].cell_contents) # 2
Q32. What is a decorator and how do you write one?
import functools, time
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function(n):
return sum(range(n))
slow_function(10_000_000)
# slow_function took 0.3142s
Q33. What is the difference between a lambda and a regular function?
A lambda is an anonymous function restricted to a single expression. It cannot contain statements (loops, assignments, return). Use lambdas for short, throwaway functions β especially as arguments to sorted(), map(), filter(). For anything more complex, use a named def function.
Q34. What is functools.lru_cache and when is it useful?
from functools import lru_cache
@lru_cache(maxsize=None) # cache all results
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(50)) # Instant β without cache, this would take exponential time
print(fibonacci.cache_info()) # CacheInfo(hits=48, misses=51, maxsize=None, currsize=51)
Q35. What is a generator function and how is it different from a regular function?
A generator function contains yield instead of (or in addition to) return. Calling it returns a generator object β no code runs until you call next(). The function then runs until the next yield, which pauses execution and returns the value. This enables lazy, memory-efficient iteration. See the Generators lesson for a full deep-dive.
Q36. What is the difference between map(), filter(), and reduce()?
from functools import reduce
nums = [1, 2, 3, 4, 5]
# map β apply function to every element
squares = list(map(lambda x: x**2, nums)) # [1, 4, 9, 16, 25]
# filter β keep elements where function returns True
evens = list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
# reduce β accumulate to a single value
product = reduce(lambda acc, x: acc * x, nums) # 120 (1*2*3*4*5)
Q37. What are Python type hints and how do you use them?
from typing import Optional, List, Dict, Tuple, Union
def process_data(
items: List[int],
config: Optional[Dict[str, str]] = None,
) -> Tuple[int, float]:
total = sum(items)
avg = total / len(items)
return total, avg
# Python 3.10+ union syntax
def parse(value: int | str) -> str:
return str(value)
# Type hints are NOT enforced at runtime β use mypy or pyright to check them
Q38. What are * and / in function signatures?
def func(pos_only, /, normal, *, kw_only):
print(pos_only, normal, kw_only)
# pos_only must be positional (before /)
# normal can be positional or keyword
# kw_only must be keyword (after *)
func(1, 2, kw_only=3) # OK
func(1, normal=2, kw_only=3) # OK
# func(pos_only=1, ...) # TypeError
Part 5: Exceptions (Q39βQ43)
Q39. What is the try/except/else/finally structure?
def safe_divide(a, b):
try:
result = a / b
except ZeroDivisionError as e:
print(f"Cannot divide by zero: {e}")
return None
except TypeError as e:
print(f"Invalid types: {e}")
return None
else:
# Runs only if NO exception occurred
print("Division successful")
return result
finally:
# ALWAYS runs β good for cleanup (closing files, releasing locks)
print("Division attempted")
Q40. How do you create a custom exception?
class AppError(Exception):
"""Base class for application exceptions."""
pass
class ValidationError(AppError):
def __init__(self, field, message):
self.field = field
super().__init__(f"Validation failed for '{field}': {message}")
try:
raise ValidationError("email", "Invalid format")
except ValidationError as e:
print(e) # Validation failed for 'email': Invalid format
print(e.field) # email
Q41. What is the difference between raise and raise ... from ...`?
raise ExceptionB from ExceptionA chains exceptions, preserving the original traceback as the cause. Use this in except blocks to wrap low-level errors in higher-level ones without losing context. raise ... from None suppresses the chained traceback entirely.
Q42. What are context managers and how does with work?
from contextlib import contextmanager
@contextmanager
def managed_resource(name):
print(f"Acquiring {name}")
try:
yield name # body of the with-block runs here
except Exception as e:
print(f"Error: {e}")
raise
finally:
print(f"Releasing {name}")
with managed_resource("database") as res:
print(f"Using {res}")
# Acquiring database
# Using database
# Releasing database
Q43. What exceptions should you NOT catch with bare except:?
Bare except: (or except Exception:) catches everything, including SystemExit, KeyboardInterrupt, and MemoryError. Always catch specific exceptions. If you must catch broadly, use except Exception (which misses SystemExit and KeyboardInterrupt), then log and re-raise.
Part 6: Advanced Python (Q44βQ53)
Q44. What is metaclass?
A metaclass is the "class of a class." Just as instances are created by classes, classes are created by metaclasses. The default metaclass is type. Custom metaclasses intercept class creation to add/modify class behaviour β used by ORMs (SQLAlchemy, Django Models) and testing frameworks (ABCMeta).
Q45. What is __slots__ and when should you use it?
class WithDict:
def __init__(self, x, y):
self.x, self.y = x, y # uses __dict__ β flexible but memory-heavy
class WithSlots:
__slots__ = ("x", "y") # no __dict__ β fixed attributes
def __init__(self, x, y):
self.x, self.y = x, y
import sys
print(sys.getsizeof(WithDict(1, 2)).__sizeof__()) # ~200+ bytes with __dict__
print(sys.getsizeof(WithSlots(1, 2))) # ~64 bytes
# Use __slots__ when creating millions of instances (e.g., graph nodes)
Q46. What is the difference between asyncio and threading?
Threading uses OS threads β true parallelism, but limited by the GIL for CPU-bound work; good for I/O-bound tasks with blocking calls. asyncio uses cooperative multitasking on a single thread β no thread overhead, no GIL issue, but requires async/await throughout the call chain. Choose asyncio for high-concurrency I/O (web servers, API clients); choose threading when integrating with blocking C libraries.
Q47. What is a context variable and how is it used in async code?
import asyncio
from contextvars import ContextVar
request_id: ContextVar[str] = ContextVar("request_id", default="none")
async def handle_request(rid):
token = request_id.set(rid)
await process()
request_id.reset(token)
async def process():
print(f"Processing request {request_id.get()}")
asyncio.run(handle_request("req-123"))
Q48. What is the difference between __str__ and __repr__?
__repr__ should return an unambiguous string β ideally valid Python that recreates the object. __str__ should return a human-readable string. When only one is defined, Python falls back: str() uses __repr__ if __str__ is absent. Containers (list, dict) call __repr__ on their elements.
Q49. What are Python's memory management mechanisms?
Python uses reference counting as the primary mechanism β each object tracks how many references point to it; when the count reaches 0, the object is freed immediately. A supplementary cyclic garbage collector (the gc module) handles reference cycles (e.g., A β B β A). Python also uses a private heap managed by the memory allocator pymalloc for small objects.
Q50. What is the walrus operator (:=)?
# Without walrus β compute twice
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
if len(data) > 5:
print(f"Long list: {len(data)} items")
# With walrus (:=) β compute once, assign and test
if (n := len(data)) > 5:
print(f"Long list: {n} items")
# Useful in while loops reading streams
import io
stream = io.BytesIO(b"Hello World from stream")
while chunk := stream.read(5):
print(chunk)
Q51. What is __init__.py and what is a namespace package?
__init__.py marks a directory as a Python package. It can be empty or contain package-level initialisation. A namespace package (PEP 420, Python 3.3+) is a package that spans multiple directories without an __init__.py. It is useful for large organisations splitting a namespace across repos.
Q52. What does if __name__ == "__main__": do?
When Python runs a file directly, it sets __name__ to "__main__". When a file is imported as a module, __name__ is the module name. This guard lets you write code that only runs when the script is executed directly, not when it is imported β preventing side effects and allowing dual-use modules.
Q53. What is structural pattern matching (match/case)?
def handle_command(command):
match command.split():
case ["quit"]:
return "Quitting"
case ["go", direction] if direction in ("north", "south"):
return f"Going {direction}"
case ["pick", item, *rest]:
return f"Picking up {item}"
case _:
return f"Unknown: {command}"
print(handle_command("go north")) # Going north
print(handle_command("pick sword")) # Picking up sword
print(handle_command("fly")) # Unknown: fly
Part 7: System Design (Q54βQ58)
Q54. How would you design a rate limiter in Python?
import time
from collections import deque
class RateLimiter:
"""Sliding window rate limiter: max `limit` calls per `window` seconds."""
def __init__(self, limit: int, window: float):
self.limit = limit
self.window = window
self.calls: deque = deque()
def is_allowed(self) -> bool:
now = time.monotonic()
# Remove calls outside the window
while self.calls and self.calls[0] <= now - self.window:
self.calls.popleft()
if len(self.calls) < self.limit:
self.calls.append(now)
return True
return False
limiter = RateLimiter(limit=3, window=1.0) # 3 calls per second
for i in range(5):
print(f"Call {i+1}: {'OK' if limiter.is_allowed() else 'BLOCKED'}")
Q55. How would you implement a thread-safe singleton?
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None: # double-checked locking
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
print(a is b) # True
Q56. How do you profile a Python application?
Standard tools: cProfile (built-in, per-function call counts and timing), timeit (micro-benchmarks), tracemalloc (memory usage). Third-party: py-spy (sampling profiler, zero overhead, attaches to running process), memory_profiler (line-by-line memory), line_profiler (line-by-line CPU time). Always profile before optimising β intuition about bottlenecks is usually wrong.
Q57. How would you implement a simple in-memory cache with TTL?
import time
class TTLCache:
def __init__(self, ttl: float):
self.ttl = ttl
self._store: dict = {} # key β (value, expiry_time)
def set(self, key, value):
self._store[key] = (value, time.monotonic() + self.ttl)
def get(self, key, default=None):
entry = self._store.get(key)
if entry is None:
return default
value, expiry = entry
if time.monotonic() > expiry:
del self._store[key]
return default
return value
cache = TTLCache(ttl=5.0)
cache.set("user:1", {"name": "Alice"})
print(cache.get("user:1")) # {'name': 'Alice'}
time.sleep(6)
print(cache.get("user:1")) # None (expired)
Q58. How would you design a task queue in Python?
import threading
import queue
import time
def worker(task_queue: queue.Queue, worker_id: int):
while True:
task = task_queue.get()
if task is None: # Poison pill β signal to stop
break
func, args, kwargs = task
print(f"Worker {worker_id}: running {func.__name__}")
func(*args, **kwargs)
task_queue.task_done()
class TaskQueue:
def __init__(self, num_workers=4):
self.queue = queue.Queue()
self.workers = [
threading.Thread(target=worker, args=(self.queue, i), daemon=True)
for i in range(num_workers)
]
for w in self.workers:
w.start()
def submit(self, func, *args, **kwargs):
self.queue.put((func, args, kwargs))
def shutdown(self):
for _ in self.workers:
self.queue.put(None) # send poison pills
for w in self.workers:
w.join()
Preparation Tips
Focus on understanding why things work, not just what they do. Interviewers probe depth β be ready to go one or two levels deeper on any answer.
| Day | Focus | Resources |
|---|---|---|
| 1β2 | Python fundamentals, data types, LEGB | This page, Q1βQ10 |
| 3β4 | Data structures, time complexity | Q11βQ20, LeetCode Easy |
| 5β6 | OOP: classes, inheritance, dunder methods | Q21βQ30, build a mini project |
| 7β8 | Functional Python: closures, decorators, generators | Q31βQ38 |
| 9 | Error handling, context managers | Q39βQ43 |
| 10β11 | Advanced topics: GIL, async, metaclasses | Q44βQ53 |
| 12 | Mock interviews, system design | Q54βQ58, Pramp, Interviewing.io |
ποΈ Practice Plan
- For each question, write the answer from memory first, then verify.
- Run every code example in a Python REPL β modify it and observe the effect.
- After each section, explain the concepts aloud as if teaching someone else (the Feynman technique).
- Do at least 2 LeetCode problems per day using Python β focus on Medium difficulty after day 5.
π Quick-Reference Cheat Sheet
iscompares identity;==compares value.- LEGB: Local β Enclosing β Global β Built-in.
- Mutable: list, dict, set. Immutable: int, str, tuple, frozenset.
- GIL: one thread executes bytecode at a time in CPython.
- MRO: C3 linearisation β use
Class.__mro__to inspect. __repr__is for devs;__str__is for users.@classmethodreceivescls;@staticmethodreceives neither.- List comprehension is generally faster and more readable than
map/filter. - Generator expression uses
( ); lazy, memory-efficient. - Always catch specific exceptions β never use bare
except:.
Related Topics
Frequently Asked Questions
For a junior role: 1β2 weeks of focused study covering basics through functions. For a senior role: 3β4 weeks covering all sections plus system design. Daily practice of 2β3 hours is more effective than marathon weekend sessions.
No β understand them. Interviewers probe deeper with follow-up questions, so memorised answers without understanding fall apart quickly. Write the code yourself, run it, break it, and understand why it behaves as it does.
Python 3.10+ is a safe assumption for modern companies. If unsure, ask the interviewer which version they use in production. Avoid Python 2 syntax β it is end-of-life and signals outdated knowledge.