Answer: Decorators are functions that modify the behavior of another function or class without changing its source code. Commonly used for logging, authentication, timing, and caching.
Deep explanation — with and without arguments
Without arguments (2 levels: decorator → wrapper):
def time_it(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f"[LOG] {func.__name__} took {time.time()-start:.4f}s") return result return wrapper@time_itdef fetch_data(): time.sleep(1) return {"status": "success"}
With arguments (3 levels: arg_receiver → decorator → wrapper):
2. @staticmethod vs @classmethod vs instance method
Instance method — takes self. Can access instance variables.
Class method — takes cls. Can access class variables. Use for alternative constructors.
Static method — takes neither. Utility function belonging to the class.
Quick example
class Employee: company = "TechCorp" def __init__(self, name): self.name = name # instance method uses self @classmethod def change_company(cls, name): # class method uses cls cls.company = name @staticmethod def is_workday(day): # static method uses neither return day.weekday() < 5
Changing cls.company updates the attribute for all existing instances — they all share the same class-level variable.
3. What is a generator?
Answer: A generator uses yield to return values one at a time instead of storing them all in memory, making it memory-efficient for large datasets.
4. Generator vs Iterator
Generator — created using yield, automatically implements iterator protocol.
Iterator — any object implementing __iter__() and __next__(). Can be manually implemented.
5. What is the GIL?
Answer: The Global Interpreter Lock allows only one thread to execute Python bytecode at a time in CPython. Makes CPU-bound multithreading ineffective, but I/O-bound multithreading still works fine.
6. How does Python memory management work?
Answer: Python uses reference counting and a cyclic garbage collector. Objects are freed when their reference count drops to zero. The GC handles circular references.
Weakref and circular references
A circular reference (A → B → A) prevents both objects from being freed by reference counting alone. The GC detects and cleans these up periodically.
weakref breaks cycles: a weak reference doesn’t increment the reference count, so Python can destroy the object when no strong references remain.
__new__() creates the object. __init__() initializes it after creation.
8. What is __del__()?
Destructor called before garbage collection. Generally not recommended — use context managers instead.
9. Mutable vs Immutable
Mutable:list, dict, set
Immutable:int, float, tuple, str
Mutable objects can change after creation; immutable objects cannot.
10. Why are tuples hashable?
Tuples are immutable, so their hash value never changes, making them valid dictionary keys.
11. What are context managers?
Objects that manage resources automatically using with, ensuring cleanup even if exceptions occur. Implement __enter__() and __exit__().
12. List vs Tuple
List
Tuple
Mutability
Mutable
Immutable
Speed
Slower
Faster
Memory
More
Less
Use case
Dynamic data
Fixed data, dictionary keys
13. List comprehension vs Generator expression
List comprehension creates the full list in memory: [x*2 for x in range(100)]
Generator expression produces values lazily: (x*2 for x in range(100))
14. Deep Copy vs Shallow Copy
Shallow copy — copies references to nested objects. Modifying nested objects in the copy also modifies the original.
Deep copy — recursively copies all nested objects. Fully independent.
When deep copy is mandatory
Use copy.deepcopy() when you have nested mutable objects (e.g., a list inside a dict) and need a truly independent copy:
import copybase = {"ports": [80, 443]}server_a = base.copy() # shallow — ports list is shared!server_b = copy.deepcopy(base) # deep — ports list is independentserver_b["ports"].append(8080) # base template is safe
15. == vs is
== compares values. is compares object identity (memory address).
Use is for None checks: if result is None: (PEP 8).
16. Lambda functions
Anonymous one-line functions, mainly used with map, filter, and sorted.
sorted(users, key=lambda u: u["age"])
17. map(), filter(), reduce()
map() — applies a function to every element → same-length result.
filter() — keeps elements satisfying a condition → shorter result.
reduce() — aggregates all elements into one value (from functools).
Examples
numbers = [1, 2, 3, 4, 5]list(map(lambda x: x * 2, numbers)) # [2, 4, 6, 8, 10]list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4]from functools import reducereduce(lambda x, y: x * y, numbers) # 120
18. *args and **kwargs
*args — variable positional arguments (tuple).
**kwargs — variable keyword arguments (dict).
19. Modules vs Packages
Module — single Python file (.py). Package — directory with __init__.py containing multiple modules.
OOP
20. Encapsulation
Bundling data and methods together while restricting direct access using private/protected members.
Example
class BankAccount: def __init__(self, balance): self.__balance = balance # private def deposit(self, amount): if amount > 0: self.__balance += amount def get_balance(self): return self.__balance# account.__balance raises AttributeError — access only via methods
21. Inheritance (types)
Allows a class to reuse properties and methods from another class.
Single — one parent. Multiple — two or more parents. Multilevel — chain of inheritance. Hierarchical — multiple children from one parent.
Real-world example (Hierarchical)
class Employee: def get_paid(self): print("Salary deposited.")class Developer(Employee): def write_code(self): ...class Designer(Employee): def create_mockup(self): ...# Both Developer and Designer inherit get_paid() from Employee
22. Polymorphism
Same interface with different implementations. Different classes implement the same method differently.
Payroll example
class FullTime: def calculate(self): return "Fixed: $5,000"class Hourly: def calculate(self): return "40hrs * $25 = $1,000"for person in [FullTime(), Hourly()]: print(person.calculate()) # same call, different result
23. Abstraction
Hides implementation details while exposing only required functionality. Implemented using Abstract Base Classes (ABC).
Composition — “has-a” relationship. Preferred for loose coupling.
Why composition wins
# Inheritance: ElectricCar inherits Car — tight coupling# Composition: Car *has* an engine — flexibleclass Car: def __init__(self, engine): # inject the engine self.engine = engine def start(self): print(self.engine.ignite())car = Car(ElectricMotor()) # swap engine without changing Car
Adding a HybridEngine requires no changes to Car.
25. Method Overloading vs Overriding
Python doesn’t support true method overloading (use default args instead).
Method overriding allows child classes to redefine parent methods.
26. MRO (Method Resolution Order)
Determines the order Python searches parent classes in multiple inheritance. Use ClassName.mro() to inspect.
Diamond problem
class TechLead(Developer, Manager) → MRO searches: TechLead → Developer → Manager → Employee. The first match wins — so Developer.get_role() takes precedence over Manager.get_role().
27. Duck Typing
“If it behaves like a duck, it’s a duck.” Python focuses on object behavior rather than type. If an object has the required method, Python will call it — no inheritance required.
Advanced Python
28. __slots__
Restricts instance attributes to a fixed set, reducing memory usage per instance.
class Point: __slots__ = ['x', 'y']
29. What is a weakref?
A reference to an object that does not increment the reference count. The object can still be garbage collected. Used for caches and observer patterns to avoid memory leaks.
Concurrency
30. Thread vs Process
Thread
Process
Memory
Shared
Separate
Weight
Lightweight
Heavyweight
Best for
I/O-bound
CPU-bound
GIL affected
Yes
No
31. Multithreading vs Multiprocessing
Multithreading — best for I/O-bound work (API calls, DB queries, file I/O). Threads share memory and wait together.
Multiprocessing — best for CPU-bound work (image processing, data crunching). Each process bypasses the GIL.
Real-world rule
Downloading 1,000 files from S3? → Use threads. Your CPU isn’t working — it’s just waiting for network.
Resizing 1,000 images? → Use processes. Your CPU is doing heavy math. Threads won’t help because of the GIL.
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutorwith ThreadPoolExecutor() as ex: # I/O-bound results = list(ex.map(download, urls))with ProcessPoolExecutor() as ex: # CPU-bound results = list(ex.map(resize, images))
32. Why doesn’t multithreading speed up CPU tasks?
Because of the GIL — only one thread executes Python bytecode at a time, regardless of core count.
33. What is asyncio?
Python framework for asynchronous programming using a single thread and an event loop. Tasks cooperatively yield control using await instead of blocking.
asyncio vs threading
Threading: OS switches between threads preemptively.
asyncio: Code explicitly yields at await points — single thread, no OS switching overhead.
Use asyncio when you have many concurrent I/O tasks (websockets, large-scale API calls). It’s lighter than spinning up thousands of threads.
async def get_weather(city): await asyncio.sleep(2) # yields control — other tasks run return f"{city}: Sunny"results = await asyncio.gather( get_weather("NY"), get_weather("London"), get_weather("Tokyo"))# Completes in 2s, not 6s — all three run concurrently
34. async vs await
async def — defines a coroutine (asynchronous function).
await — pauses the coroutine and yields control to the event loop without blocking.
35. Event Loop
Core component of asyncio. Continuously checks for ready tasks and runs them. Manages coroutines, futures, and I/O events.
36. Future vs Coroutine
Coroutine — function defined with async def. Contains the logic.
Future — a low-level placeholder representing a result that isn’t available yet. Modern code uses asyncio.create_task() which wraps coroutines into Tasks (a subclass of Future) automatically.
37. Thread-safe code
Code that avoids race conditions when multiple threads access shared resources concurrently. Achieved using synchronization mechanisms like threading.Lock() to ensure only one thread modifies the resource at a time, or by avoiding shared state altogether.
FastAPI & REST APIs
38. Why FastAPI over Flask?
FastAPI provides async support, automatic OpenAPI docs, Pydantic type validation, and better performance (ASGI vs WSGI).
Key differences
Flask is synchronous (WSGI) — one request per thread at a time.
FastAPI is asynchronous (ASGI via Starlette + Uvicorn) — handles thousands of concurrent requests with a single event loop.
FastAPI also auto-generates /docs (Swagger) and /redoc without any extra setup.
39. What is Pydantic?
Library used by FastAPI for request validation and serialization using Python type hints. Invalid input raises a 422 automatically.
class UserCreate(BaseModel): name: str age: int email: EmailStr
40. Dependency Injection (Depends())
FastAPI’s Depends() allows reusable authentication, database sessions, and shared logic injected at the route level.
async def get_db(): db = SessionLocal() try: yield db finally: db.close()@app.get("/users")async def list_users(db: Session = Depends(get_db)): ...
41. Middleware
Executes before and after every request. Common uses: logging, CORS, authentication, request timing.
Client POSTs username+password to /login as form data
Server returns {"access_token": "...", "token_type": "bearer"}
Client sends Authorization: Bearer <token> on protected routes
Depends(get_current_user) verifies the token on every protected route
44. What is REST?
Architectural style using HTTP methods for CRUD operations on resources. Stateless, resource-based URLs.
45. GET vs POST vs PUT vs PATCH vs DELETE
Method
Operation
Idempotent?
GET
Read
Yes
POST
Create
No
PUT
Full replace
Yes
PATCH
Partial update
Yes
DELETE
Remove
Yes
46. Common HTTP status codes
200 OK · 201 Created · 204 No Content · 400 Bad Request · 401 Unauthorized · 403 Forbidden · 404 Not Found · 422 Unprocessable Entity · 500 Internal Server Error
47. JWT vs OAuth
JWT — a token format for authentication. Self-contained, signed.
OAuth — an authorization framework allowing third-party access delegation. JWTs are often used inside OAuth flows.
48. API Pagination
Splits large datasets using limit/offset (simple, but slow on deep pages) or cursor-based pagination (efficient, scalable).
Unit Test — tests a single function/class in isolation. Fast, no external dependencies.
Integration Test — verifies multiple components working together (e.g., API + DB).
56. What is code coverage?
Measures the percentage of code executed during tests. High coverage improves confidence but doesn’t guarantee bug-free code. Target 80%+ for critical paths.
57. How would you test a FastAPI REST API?
Use pytest with TestClient. Verify status codes, response payloads, auth, validation, and error scenarios.