0% found this document useful (0 votes)
1 views8 pages

Python Interview Short Notes

This document is a condensed revision sheet containing 50 essential Python interview questions and notes covering various topics such as GIL, threading, decorators, memory management, and frameworks. It provides key insights into Python's features, performance optimization techniques, and best practices for coding. The notes serve as a quick reference for exam preparation and interview readiness.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views8 pages

Python Interview Short Notes

This document is a condensed revision sheet containing 50 essential Python interview questions and notes covering various topics such as GIL, threading, decorators, memory management, and frameworks. It provides key insights into Python's features, performance optimization techniques, and best practices for coding. The notes serve as a quick reference for exam preparation and interview readiness.
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Python Interview — 50 Questions Short Notes

Condensed revision sheet for exam preparation

1. Python GIL — impact on threading vs multiprocessing vs asyncio


GIL (Global Interpreter Lock) allows only ONE thread to execute Python bytecode at a time, even on multi-core CPUs.
Multithreading: Threads share memory; GIL limits CPU parallelism. Best for I/O-bound (API calls, DB, files).
Multiprocessing: Each process has its own GIL. True parallelism. Best for CPU-bound (ML, image processing).
Asyncio: Single-threaded, cooperative multitasking. Best for massive concurrent I/O (web servers, chat).
KEY: Use Threading for I/O | Multiprocessing for CPU | Asyncio for high-concurrency I/O

2. is vs ==, Mutable vs Immutable


== compares VALUES. is compares IDENTITY (same memory address). Use is only for None checks (if x is None).
Mutable: list, dict, set, bytearray — can be changed after creation.
Immutable: int, float, str, tuple, frozenset — cannot be modified; new object created on change.
Key Trap: def f(items=[]): [Link](1) → default mutable arg shared across calls. Fix: use None default.
KEY: == for value | is for identity/None | Mutable objects passed by reference → side effects!

3. __init__ vs __new__, __str__ vs __repr__, __call__, __slots__


__new__ CREATES the object (returns it). __init__ INITIALIZES it (sets attributes). Python calls __new__ first, then __init__.
__str__: Human-readable: used by print(). __repr__: Developer/debug: used in interactive shell.
__call__: Makes object callable like a function: obj() triggers __call__. Used in ML pipelines, decorators.
__slots__: Replaces __dict__ with fixed attributes → saves memory. Use when creating millions of objects.
KEY: __new__=create | __init__=initialize | __str__=readable | __repr__=debug | __call__=callable | __slots__=memory

4. Generators, Iterators, Iterables + Custom Iterator


Iterable: can be looped (list, str). Iterator: has __iter__() + __next__(), remembers position. Generator: iterator using yield — lazy,
memory efficient.
for loop internally: Calls iter(obj) → returns iterator → repeatedly calls next() → stops at StopIteration.
Generator: def gen(): yield 1; yield 2 → values produced one at a time. Use for large datasets/streams.
Custom Iterator: class Counter: __iter__=return self; __next__=return value or raise StopIteration
KEY: Generator is always an iterator. Iterator is NOT always a generator. yield = lazy evaluation = low memory.

5. Decorators, Context Managers, @contextmanager


Decorator: function that wraps another function to add behavior WITHOUT modifying original code. @logger → greet = logger(greet).
@contextmanager: Use yield instead of __enter__/__exit__. Code before yield = enter. After yield = exit.
Context Manager: Implements __enter__ and __exit__. with statement guarantees cleanup even on exception.
Production use: Decorators: logging, auth, caching, timing. Context managers: DB connections, file handles, locks.
KEY: Decorator = modify behavior | Context Manager = manage resources | @contextmanager = simpler CM via generator

6. List vs Tuple vs Set vs Dict — Time Complexity


Set and Dict use hashing → O(1) average for search/insert/delete. List/Tuple require O(n) scan for membership.
List: Ordered, mutable, duplicates allowed. O(1) access by index. O(n) search. Use: ordered collections.
Tuple: Ordered, IMMUTABLE, faster than list. Use: fixed data, function returns, dict keys, coordinates.
Set: Unordered, unique, O(1) lookup. Use: membership checks, duplicate removal, fast intersection/union.
Dict: Key-value, O(1) lookup. Use: caching, JSON data, lookups by key, grouping.
KEY: 1M in list = O(n) | 1M in set = O(1). Need fastest membership? → Set or Dict

7. MRO — Method Resolution Order + super()


MRO defines search order for methods in inheritance. Python uses C3 Linearization algorithm. Check with [Link]().
Diamond Problem: class D(B, C) where B,C both inherit A → MRO: D, B, C, A, object. A visited only once.
super(): Calls NEXT class in MRO, NOT necessarily the direct parent. This enables cooperative multiple inheritance.
Key point: [Link]() → [Link]() (super) → [Link]() (super) → [Link](). Not D → B → A as many expect.
KEY: super() follows MRO, not just parent class. Use super().__init__() instead of Parent.__init__(self).

8. Shallow Copy vs Deep Copy


Shallow copy ([Link]): creates new object but nested objects are SHARED. Deep copy ([Link]): fully independent
clone.
Shallow: a = [[1,2]]; b = copy(a); b[0].append(3) → a also changes! Nested objects shared.
Deep: [Link](a) → completely independent. Changes to b don't affect a.
Slicing: list[:] creates a shallow copy. So does list(original) and [Link]().
KEY: Shallow = new container, shared contents | Deep = everything new | Only use deepcopy when truly needed (slower)

9. List/Dict/Set Comprehensions — Readability & Performance


Comprehensions are concise and faster than loops for simple transformations. emails = [u['email'] for u in users]
Dict comp: {k: v for k, v in items} | Set comp: {x for x in data}
When to AVOID: Multiple nested loops, complex conditions, business logic → use normal loops for readability.
Performance: Large datasets → comprehension loads ALL into memory. Use generator expression (x for x in data) instead.
KEY: Simple = comprehension. Complex logic = loop. Large data = generator expression (parentheses, not brackets).

10. Optimize 10M Row List Memory


Don't load 10M rows into memory at once. Use generators, chunking, __slots__, NumPy arrays.
Generator: yield rows one at a time instead of building a list. (x for x in data) vs [x for x in data]
Pandas chunks: pd.read_csv('[Link]', chunksize=100000) → process chunk by chunk
__slots__: For millions of objects, __slots__ removes __dict__ → less memory per object
NumPy: [Link]([...]) much more memory-efficient than Python list of integers
KEY: First ask: do I need ALL 10M rows in memory? Usually NO. Push filtering/aggregation to database.

11. Descriptors, Properties, @property


Descriptor: object with __get__, __set__, __delete__ that controls attribute access. @property is built ON TOP of descriptors.
@property: Turns a method into an attribute: [Link] instead of emp.get_salary(). Looks like attr, works like method.
Setter/Deleter: @[Link] for validation. @[Link] for cleanup.
Key point: @property IS a descriptor. Used by Django ORM, SQLAlchemy, Pydantic internally.
KEY: @property = most common. Descriptor = lower-level, used by frameworks. Every property is a descriptor.

12. Metaclasses vs Class Decorators


Metaclass: class that creates classes. Python's default metaclass is type. Metaclass runs BEFORE class is created.
Class Decorator: Modifies class AFTER it is created. Simpler. Use for: adding methods, attributes, registration.
Metaclass: Controls class creation BEFORE class exists. Use for: validation, framework APIs (Django ORM).
Rule: Prefer class decorator. Use metaclass only when you need to intercept class creation itself.
KEY: Object ← created by Class ← created by Metaclass. Class decorator = after. Metaclass = before/during.

13. Dataclasses vs Pydantic vs NamedTuple


All reduce boilerplate for data classes. Key differences: validation, mutability, performance.
NamedTuple: Immutable, fastest, lowest memory. No validation. Use: fixed lightweight records, coordinates.
Dataclass: @dataclass auto-generates __init__, __repr__, __eq__. No runtime type enforcement. Use: internal models, DTOs.
Pydantic: Runtime validation, type coercion, serialization. Slowest. Use: API request/response, external data, FastAPI.
KEY: NamedTuple=fast+immutable | Dataclass=less boilerplate | Pydantic=validation+APIs. Why not always Pydantic?
Performance cost.
14. @staticmethod vs @classmethod vs Instance Methods
Instance method: takes self, accesses instance data. Classmethod: takes cls, works with class. Staticmethod: takes nothing, utility
function.
classmethod use: Alternative constructors: Employee.from_string('Ayesha,24'). Returns cls(...).
staticmethod use: Employee.is_adult(24) → doesn't need self or cls. Pure utility related to the class.
Memory trick: Instance: [Link]() | Class: [Link]() | Static: [Link]() but no self/cls
KEY: Instance→object behavior | Class→factory methods/class-wide logic | Static→utility function grouped in class

15. ABC vs Protocol — Duck Typing vs Static Typing


Duck Typing: Python checks behavior, not type. ABC: explicit inheritance contract enforced at runtime. Protocol: structural typing —
no inheritance needed.
ABC: class Dog(Animal): must inherit. TypeError if @abstractmethod not implemented. Use: strict contracts, plugin systems.
Protocol: class Dog: just needs matching methods. Type checker verifies. Use: loose coupling, modern Python.
Static Typing: Type hints + mypy verify types BEFORE execution. Python itself still dynamically typed.
KEY: Duck Typing=runtime | ABC=inheritance+runtime enforcement | Protocol=structure-only, no inheritance needed

16. __enter__ and __exit__ — Custom DB Context Manager


__enter__ called when entering with block (setup). __exit__ called when leaving — even if exception occurs (cleanup).
Parameters: __exit__(self, exc_type, exc_val, exc_tb). If exception: exc_type has error class.
Pattern: with DatabaseConnection() as conn: → opens in __enter__, closes in __exit__ automatically
vs try-finally: Context manager encapsulates resource logic, making code cleaner and reusable across the codebase.
KEY: Use context managers for: files, DB connections, locks, transactions. Guarantees cleanup on exception.

17. Monkey Patching — Risks and Alternatives


Monkey patching: modifying class/module/object behavior at runtime without changing source. [Link] = new_greet
Risks: Hidden behavior (confusing), hard debugging (source vs runtime mismatch), global side effects, library update breaks.
Safe alternatives: Dependency Injection (pass object in), Inheritance (subclass), Decorators, [Link] for tests.
Test use: with patch('[Link]'): → temporary, auto-restored. Preferred approach for mocking.
KEY: Monkey patching useful for testing/mocking but dangerous in production. Prefer DI/inheritance/decorators.

18. Memory Model — Reference Counting + Garbage Collector


CPython uses Reference Counting (primary) + Cyclic GC (secondary). Ref count = 0 → memory freed immediately.
Circular refs: a.b = b; b.a = a → del a, del b → both still have ref count > 0. GC detects and cleans cycles.
Generations: GC has 3 generations. Young objects checked frequently (die fast). Old objects checked rarely.
gc module: [Link]() force-triggers GC. [Link]() turns it off. Usually not needed.
KEY: Reference counting handles most cleanup. GC handles circular references. GC does NOT run after every allocation.

19. functools.lru_cache, partial, wraps


lru_cache: memoizes function results. partial: pre-fills arguments. wraps: preserves function metadata in decorators.
lru_cache: @lru_cache(maxsize=None) → fib(35) fast. Args must be hashable. Cache hit = no re-execution.
partial: double = partial(multiply, 2) → double(10) calls multiply(2, 10). Creates specialized functions.
wraps: @wraps(func) in decorator → preserves __name__, __doc__, annotations. Without it: add.__name__ = 'wrapper'.
KEY: lru_cache=cache results | partial=pre-fill args | wraps=preserve metadata. Use @wraps in EVERY custom decorator.

20. Circular Import — How to Fix


Circular import: A imports B, B imports A. Python can't finish loading either. Error: 'cannot import name from partially initialized
module'.
Best fix: Move shared code to a THIRD module ([Link]). Both A and B import from common.
Quick fix: Move import INSIDE the function: def foo(): from module import thing. Lazy import at call time.
Design fix: Dependency Inversion — both depend on an abstract interface, not each other.
KEY: Circular imports = design smell. One-direction dependency flow. [Link] > function import > dependency inversion.

21. Threading vs Multiprocessing vs Asyncio


Choose based on work type: CPU-bound → Multiprocessing. I/O-bound → Threading or Asyncio. Massive concurrent I/O → Asyncio.
Threading: Shared memory, lightweight. GIL limits CPU parallelism. Good: API calls, file ops, DB queries.
Multiprocessing: Separate processes, own GIL. True parallel execution. Good: ML, image/video processing.
Asyncio: Single thread, event loop, cooperative. Very low memory. Good: web servers, thousands of connections.
KEY: Threading→I/O | Multiprocessing→CPU | Asyncio→high-concurrency I/O. Asyncio = concurrency NOT parallelism.

22. async/await, Event Loop, Coroutines, Tasks, [Link]()


async def creates a coroutine. await pauses it and yields control. Event loop schedules and runs coroutines.
Coroutine: async def greet(): ... → calling greet() returns coroutine object, doesn't execute. Need await or create_task.
Task: asyncio.create_task(coro()) → schedules coroutine to run concurrently. Without task: sequential.
gather(): await [Link](task1(), task2(), task3()) → all run concurrently → total time = max, not sum.
KEY: await = pause current coroutine | Task = schedule coroutine | gather() = concurrent coroutines. NOT parallel.

23. ThreadPoolExecutor vs ProcessPoolExecutor vs asyncio.to_thread()


Executors run work in parallel. Choose by work type and GIL constraints.
ThreadPoolExecutor: Pool of threads. GIL affected. Best: I/O-bound (API calls, file ops). From [Link].
ProcessPoolExecutor: Pool of processes. Bypasses GIL. Best: CPU-bound (ML, data processing).
asyncio.to_thread(): Runs blocking function in a thread without blocking event loop. Use in FastAPI/async code.
KEY: ThreadPool→I/O | ProcessPool→CPU | to_thread()→blocking code in async apps. to_thread creates a thread, not process.

24. Race Conditions — Lock, RLock, Semaphore, Queue


Race condition: multiple threads modify shared state simultaneously → unpredictable results. counter += 1 is NOT atomic.
Lock: [Link]() → only 1 thread enters critical section. with lock: counter += 1
RLock: Reentrant lock → same thread can acquire multiple times. Use when nested locking needed.
Semaphore: [Link](N) → maximum N threads simultaneously. Use: connection pools, rate limiting.
Queue: Thread-safe communication between threads. No manual locks needed. Preferred for producer-consumer.
KEY: Lock=1 thread | RLock=nested locking | Semaphore=N threads | Queue=thread-safe data exchange. GIL ≠ no race conditions!

25. 100 I/O-Bound API Calls — Speed Without Rate Limit Issues
API calls are I/O-bound → asyncio is best. Don't fire all 100 at once → use Semaphore to limit concurrency.
Solution: asyncio + [Link] + [Link](10) → 10 concurrent max
Retry: Exponential backoff: await [Link](2**attempt). Respect Retry-After headers.
Production: asyncio + async HTTP client + Semaphore + retries + timeouts + connection pooling + caching
KEY: 100 I/O calls → [Link]() with Semaphore. NOT multiprocessing. Add retries + timeout always.

26. Flask vs FastAPI vs Django


Flask: lightweight microframework. FastAPI: modern async API framework. Django: full-stack batteries-included.
Flask: Simple routing, flexible. Choose for: small APIs, microservices, prototypes, internal tools.
FastAPI: Auto validation (Pydantic), auto docs (/docs), async. Choose for: REST APIs, ML APIs, high-concurrency.
Django: ORM, admin panel, auth, migrations built-in. Choose for: large business apps, CMS, e-commerce.
KEY: Flask=small/flexible | FastAPI=modern APIs | Django=full-stack. Choose by requirements, not popularity.

27. WSGI vs ASGI — Uvicorn vs Gunicorn


WSGI = synchronous interface (Flask, Django). ASGI = asynchronous interface (FastAPI, Starlette).
Gunicorn: WSGI server + process manager. Used with Flask/Django. Can run ASGI with Uvicorn workers.
Uvicorn: ASGI server. Used directly with FastAPI. Fast, async-native.
Production: Gunicorn + UvicornWorker + FastAPI = best of both worlds. Process management + async execution.
KEY: WSGI=sync | ASGI=async+WebSocket. Production FastAPI: gunicorn -k [Link] main:app

28. Pydantic Validation + FastAPI Dependency Injection


Pydantic validates, coerces types, serializes. FastAPI uses Pydantic for all request/response models automatically.
Validation: class User(BaseModel): name: str; age: int → age='24' auto-converts to int. 'abc' → ValidationError.
field_validator: @field_validator('age') → custom validation logic. Raise ValueError to reject.
DI with Depends(): db = Depends(get_db) → FastAPI calls get_db() and injects result. Used for DB sessions, auth, config.
KEY: Pydantic=validate+convert+serialize | Depends()=inject dependencies per request. DI makes code testable and clean.

29. SQLAlchemy Core vs ORM + N+1 Problem


Core = SQL-centric, more control. ORM = object-centric, less boilerplate but can hide inefficiencies.
N+1 Problem: 1 query for 100 users + 100 queries for their orders = 101 queries. Caused by lazy loading.
Fix: joinedload(): .options(joinedload([Link])) → LEFT JOIN → 1 query total. Good for small relationships.
Fix: selectinload(): .options(selectinload([Link])) → WHERE IN → 2 queries total. Better for large relationships.
KEY: N+1 = most common ORM performance bug. Fix with eager loading. Use EXPLAIN ANALYZE to detect slow queries.

30. Pandas Vectorization, apply vs map, groupby Performance


Vectorized operations use optimized C/NumPy code → much faster than Python loops. Avoid iterrows().
Performance rank: Vectorized > map() > apply() > iterrows(). Always prefer vectorized: df['bonus'] = df['salary'] * 0.1
map(): Series-only value mapping/replacement. df['gender'].map({'M': 'Male', 'F': 'Female'})
groupby tips: Use built-in agg (sum, mean, count). Select columns before groupby. Convert strings to category dtype.
KEY: Never loop over rows in Pandas. Use vectorized ops. If you must apply(), check if vectorization is possible first.

31. NumPy Arrays vs Python Lists + Broadcasting + Views vs Copies


NumPy arrays: contiguous memory, homogeneous, C-optimized. Lists: pointers to objects, flexible but slow for math.
Broadcasting: arr + 10 → adds 10 to each element without loop. NumPy expands shape automatically.
View: arr[:5] → shares memory. Modifying view modifies original. Fast, no extra memory.
Copy: [Link]() → independent new array. Changes don't affect original. Use when isolation needed.
KEY: NumPy speed = contiguous memory + C operations. Slicing creates VIEW (not copy). Use .copy() for independence.

32. Serialization — Pickle vs JSON vs MsgPack vs Protobuf


Serialization converts objects to storable/transmittable format. Choice depends on performance, readability, language support.
JSON: Human readable, language-independent, REST API standard. Larger size, slower than binary.
Pickle: Python-only, supports any Python object. NEVER unpickle untrusted data (can execute arbitrary code).
MsgPack: Binary JSON — smaller and faster. Cross-language. Good for caching, microservices.
Protobuf: Schema-based binary. Fastest, smallest. Used in gRPC, distributed systems. Strong typing.
KEY: JSON=APIs | Pickle=Python internal (UNSAFE external) | MsgPack=compact binary | Protobuf=fastest/gRPC

33. Logging, Config, Secrets in Python Services


Use logging module (not print). Load config from env vars. NEVER hardcode secrets in source code.
Log levels: DEBUG < INFO < WARNING < ERROR < CRITICAL. Use [Link]() in except → includes stack trace.
Config: [Link]('DB_URL') or Pydantic BaseSettings. Different values per environment (dev/staging/prod).
Secrets: Never in code. Use env vars for small deployments. AWS Secrets Manager / HashiCorp Vault for production.
KEY: print()=dev only. logging=production. Secrets in env vars or secret manager. Never in Git or hardcoded.

34. pytest — Fixtures, Parametrize, Mocking, Coverage


pytest is the standard Python testing framework. Fixtures provide reusable setup. Parametrize runs one test with many inputs.
Fixture: @[Link] def db(): return Database() → inject into tests. Scope: function/module/session.
Parametrize: @[Link]('a,b,result', [(1,2,3),(2,3,5)]) → 2 test cases from 1 function.
Mock: @patch('[Link]') mock_get.return_value = ... → no real network calls in tests.
Coverage: pytest --cov → % of lines executed. 80-90% realistic target. 100% coverage ≠ bug-free code.
KEY: Fixture=setup | Parametrize=multiple inputs | Mock=isolate dependencies | Coverage=find untested code

35. Optimize Slow Pandas Merge on 50M Rows


Large merges are slow due to memory and computation. Fix: reduce data, fix dtypes, use proper tools.
Step 1: [Link](memory_usage='deep') → check memory. Reduce int64→int32 where possible. Strings → category dtype.
Step 2: Keep only needed columns before merge. Ensure merge key types match. Check for duplicate keys.
Step 3: Use inner join if outer not needed. Consider Parquet (columnar, compressed) over CSV.
Step 4: Dask/Polars for larger-than-RAM. Push joins to database (SQL) instead of loading everything.
KEY: Fastest Pandas merge = the merge you avoid by doing it in the database. Check dtypes first.

36. Python Profiling — cProfile, line_profiler, memory_profiler


Never optimize by guessing. Profile first. cProfile=function timing. line_profiler=line timing. memory_profiler=memory.
cProfile: [Link]('process()') → shows calls, tottime (self only), cumtime (self + children).
line_profiler: @profile + kernprof -l [Link] → shows time per line. Use AFTER cProfile identifies slow function.
memory_profiler: @profile + python -m memory_profiler → shows memory per line. Use for OOM, leaks.
KEY: Flow: cProfile → find slow function → line_profiler → find slow line. memory_profiler for memory issues.

37. Common Python Performance Pitfalls


Most performance issues come from bad algorithms, DB queries, or I/O — not Python syntax.
String concat: ''.join(parts) not result += part in loop. Each += creates new string object.
List for lookup: Use set or dict for membership checks. list: O(n), set: O(1).
iterrows(): Never use iterrows() on large DataFrames. Use vectorized ops.
Blocking async: [Link]() in async = blocks event loop. Use await [Link]() or asyncio.to_thread().
KEY: Optimize: algorithm > DB > I/O > data structures > micro-opts. Profile before optimizing. Don't guess.

38. pip, poetry, uv, [Link], [Link]


pip=default installer. [Link]=dependency list. [Link]=modern standard. poetry/uv=full managers.
[Link]: pip freeze > [Link] → pin versions. pip install -r [Link] to install.
[Link]: Centralizes project metadata + deps + build config + tool config. Modern Python standard.
poetry/uv: Poetry: dependency manager + lock file + virtualenv + packaging. uv: Rust-based, very fast.
KEY: Modern projects: [Link] + uv or poetry. Legacy: pip + [Link] still common.

39. venv, virtualenv, Conda, Pipenv


Virtual environments isolate dependencies per project, preventing version conflicts between projects.
venv: Built into Python: python -m venv myenv. Activate then use pip. Lightweight, no install needed.
Conda: Package manager + env manager. Handles non-Python dependencies (C libs, CUDA). Best for data science/ML.
Pipenv: pip + virtualenv combined. Uses Pipfile + [Link]. Largely replaced by poetry/uv today.
KEY: Backend project → venv or uv. Data science / ML → Conda. Legacy → virtualenv. All serve same core purpose.

40. Dockerizing Python Apps — Layers, .dockerignore, Multi-Stage


Docker packages app + dependencies + runtime into portable containers. Each Dockerfile instruction = a layer (cached).
Layer caching: COPY [Link] first → RUN pip install → COPY source. Dependencies cached if req unchanged.
.dockerignore: Exclude: venv/, __pycache__/, .git/, .env, *.log → smaller image, faster build, safer.
Multi-stage: FROM python:3.12 AS builder ... FROM python:3.12-slim → only copy build output → smaller final image.
KEY: Always: slim base image + .dockerignore + COPY requirements before source + multi-stage for production.

41. mypy and Type Hints — TypedDict, Protocol, Generic


Type hints annotate expected types. Python doesn't enforce them at runtime. mypy performs static analysis before execution.
TypedDict: class User(TypedDict): name: str; age: int → type-safe dict with known keys. mypy validates usage.
Protocol: from typing import Protocol → structural typing: class just needs matching methods, no inheritance.
Generic: class Box(Generic[T]) → Box[int] or Box[str]. Type-safe reusable containers.
KEY: Type hints = documentation + IDE support. mypy = catch bugs before runtime. Protocol > ABC for flexible APIs.

42. CI/CD — ruff, black, isort, bandit, safety, pytest


CI/CD automates: lint → format check → security scan → tests → build → deploy on every code push.
ruff: Fast linter (written in Rust). Detects unused imports, code smells, style violations.
black + isort: black: auto-formats code. isort: auto-sorts imports. Both enforce consistent style.
bandit + safety: bandit: scans code for security issues (hardcoded passwords, unsafe functions). safety: checks deps for CVEs.
KEY: Pipeline: Push → ruff → black → bandit → safety → pytest → coverage → docker build → deploy.

43. Microservice Patterns — Circuit Breaker, Retry, Idempotency


Distributed systems expect failures. Retry handles transient failures. Circuit breaker prevents cascade failures.
Retry + Backoff: Try 3x with delays: 1s, 2s, 4s. Use exponential backoff. Respect Retry-After headers.
Circuit Breaker: States: Closed (normal) → Open (stop calls after N failures) → Half-Open (test recovery). pybreaker library.
Idempotency: Same request executed N times = same result. Use unique txn IDs. Critical for payments/orders.
KEY: Retry=transient failures | Circuit Breaker=cascade prevention | Idempotency=duplicate safety. All three together.

44. Celery + Redis/RabbitMQ — Background Tasks + Retries


Celery = distributed task queue. Decouples long tasks from API response. Producer → Broker → Worker.
Basic: @[Link] def send_email(email): ... → send_email.delay(email) → queues task, returns immediately.
Retry: @[Link](autoretry_for=(Exception,), retry_backoff=True, max_retries=5) → auto-retries on failure.
Idempotency: Tasks may execute multiple times (crash/retry). Use transaction IDs to detect and skip duplicates.
KEY: Celery = background processing. Redis=simple broker. RabbitMQ=reliable/complex. Always: retries + idempotency.

45. Debug Python API with 5s p95 Latency


p95 = 95% of requests faster than this value. 5s p95 = 5% of requests take >5s. Never guess — profile first.
Step 1: Add tracing/metrics. Break request into: DB time + external API time + app time + network.
Step 2: Check DB: slow queries (EXPLAIN ANALYZE), missing indexes, N+1 queries, connection pool exhaustion.
Step 3: Check external APIs: timeouts, async opportunities ([Link]). Check caching opportunities.
Step 4: Profile CPU (cProfile), memory (memory_profiler). Check async blocking ([Link] in async code).
KEY: Measure first. Usually: DB > external APIs > CPU > memory. Fixing N+1 often gives 10x improvement instantly.

46. SOLID Principles with Python Examples


Five design principles for maintainable, flexible, testable code.
S: SRP: One class = one responsibility. Split UserRepo, EmailService, ReportService from User class.
O: OCP: Add new payment type by creating new class, not editing existing if-else chain.
L: LSP: Child should replace parent without breaking behavior. Penguin should NOT extend FlyingBird.
I: ISP: Robot shouldn't implement eat() just because it extends Worker. Split into Workable + Eatable.
D: DIP: UserService takes db: Database (abstract), not MySQL directly. Inject the dependency.
KEY: SOLID ≠ more classes. SOLID = less coupling. DIP most commonly asked. Use dependency injection.

47. Design Patterns — Singleton, Factory, Strategy, Observer, Repository


Reusable solutions to recurring design problems. Use when they solve a real problem, not by default.
Singleton: One instance only. class uses _instance = None in __new__. Use: logger, config. Drawback: global state.
Factory: [Link]('postgres') → returns right DB. Hides creation logic.
Strategy: PaymentStrategy ABC with StripePayment, PayPalPayment subclasses. Swap algorithm without if-else chain.
Observer: [Link]() → all [Link](). Event systems, notifications, WebSockets.
Repository: UserRepository handles SQLAlchemy. UserService handles business logic. Separates DB from logic.
KEY: Strategy = most common in interviews. Repository = clean architecture. Singleton = use carefully (global state risk).

48. Exception Handling — Custom Exceptions, Chaining, finally vs else


Catch specific exceptions. Never use bare except: pass. Use finally for cleanup.
else: Runs only if NO exception occurred. Use for code that shouldn't run on error.
finally: Always runs. Use for cleanup: close file, release DB connection, release lock.
Custom exceptions: class PaymentFailedError(Exception): pass → domain-specific, readable, catchable by type.
Chaining: raise RuntimeError('msg') from original_error → preserves root cause in traceback.
KEY: try→except specific→else (success)→finally (always). Chain exceptions to preserve root cause.

49. Unit vs Integration vs Contract Tests


Test pyramid: many unit tests → fewer integration → very few contract tests. Fast feedback loop.
Unit Test: Tests single function/class in isolation. Uses mocks for DB/APIs. Fast, simple, reliable.
Integration Test: Tests components together (API + real DB). Slower but catches schema mismatches, config errors.
Contract Test: Verifies service-to-service API compatibility. Consumer defines expected response. Provider verifies.
KEY: Unit=business logic | Integration=component interaction | Contract=service agreements. Mock external deps in units.

50. Design a Rate Limiter — Data Structures + Concurrency


Rate limiter: controls requests per user/IP per time window. Returns HTTP 429 when limit exceeded.
Sliding Window: Store request timestamps in deque per user. Remove expired. Count remaining. O(1) ops.
Token Bucket: Bucket refills N tokens/second. Each request consumes 1 token. Allows bursts. Common in production.
Concurrency: Single server: [Link]() around counter. Distributed: Redis INCR (atomic) for shared state.
Dict + Deque: users = {'user1': deque()} → O(1) user lookup + O(1) deque operations.
KEY: Single server → Lock + deque. Multi-server → Redis atomic ops. Semaphore controls concurrency. 429 on exceed.

You might also like