Summary
Python is a high-level, interpreted programming language widely used for backend development due to its simplicity, extensive libraries, and strong community support. This cheatsheet covers essential Python concepts for backend development including language fundamentals, data structures, object-oriented programming, async programming, web frameworks (Flask, FastAPI, Django), database integration, caching strategies, authentication, testing, performance optimization, and common design patterns. Key features include dynamic typing, garbage collection, extensive standard library, and frameworks like Django and Flask for rapid web development.
Python Fundamentals
Variables & Data Types
# Immutable: int, float, str, tuple, frozenset
# Mutable: list, dict, set, bytearray
# Type hints (Python 3.5+)
name: str = "Alice"
age: int = 30
scores: list[int] = [90, 85, 88]
String Operations
# F-strings (Python 3.6+)
name = "Alice"
greeting = f"Hello, {name}!"
# Common methods
s = " Hello World "
s.strip() # "Hello World"
s.lower() # " hello world "
s.split() # ["Hello", "World"]
" ".join(['a','b']) # "a b"
List Comprehensions
# Basic
squares = [x**2 for x in range(10)]
# With condition
evens = [x for x in range(10) if x % 2 == 0]
# Nested
matrix = [[i*j for j in range(3)] for i in range(3)]
Dictionary Operations
# Dictionary comprehension
d = {x: x**2 for x in range(5)}
# Safe access
d.get('key', default_value)
# Merge dictionaries (Python 3.9+)
merged = dict1 | dict2
# setdefault
d.setdefault('key', []).append(value)
Data Structures
Lists
# O(1): append, pop (from end)
# O(n): insert, remove, pop(0)
lst = [1, 2, 3]
lst.append(4) # [1, 2, 3, 4]
lst.extend([5, 6]) # [1, 2, 3, 4, 5, 6]
lst.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6]
Sets
# O(1) average: add, remove, in
set1 = {1, 2, 3}
set2 = {3, 4, 5}
set1 & set2 # Intersection: {3}
set1 | set2 # Union: {1, 2, 3, 4, 5}
set1 - set2 # Difference: {1, 2}
Deque (Double-ended queue)
from collections import deque
dq = deque([1, 2, 3])
dq.appendleft(0) # O(1)
dq.popleft() # O(1)
Heaps
import heapq
# Min heap by default
heap = [3, 1, 4, 1, 5]
heapq.heapify(heap) # O(n)
heapq.heappush(heap, 2) # O(log n)
min_val = heapq.heappop(heap) # O(log n)
# Max heap trick
max_heap = [-x for x in values]
heapq.heapify(max_heap)
Counter
from collections import Counter
c = Counter(['a', 'b', 'a', 'c', 'b', 'a'])
c.most_common(2) # [('a', 3), ('b', 2)]
Object-Oriented Programming
Classes & Inheritance
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!"
# Multiple inheritance
class A: pass
class B: pass
class C(A, B): pass # MRO: C -> A -> B -> object
Special Methods
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point({self.x}, {self.y})"
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
Properties
class Temperature:
def __init__(self, celsius=0):
self._celsius = celsius
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
self._celsius = (value - 32) * 5/9
Abstract Base Classes
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
Functions & Decorators
Function Arguments
def func(pos, /, pos_or_kw, *, kw_only, **kwargs):
# pos: positional-only
# pos_or_kw: positional or keyword
# kw_only: keyword-only
pass
# Unpacking
def func(*args, **kwargs):
pass
func(*[1, 2, 3], **{'a': 4, 'b': 5})
Decorators
# Basic decorator
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.time()-start:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
# Decorator with arguments
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if i == max_attempts - 1:
raise
return wrapper
return decorator
@retry(max_attempts=5)
def unstable_api_call():
pass
Closures
def outer(x):
def inner(y):
return x + y # x is captured from outer scope
return inner
add_five = outer(5)
result = add_five(3) # 8
Error Handling
Exception Handling
try:
result = risky_operation()
except ValueError as e:
# Handle specific exception
logger.error(f"Value error: {e}")
except (TypeError, KeyError) as e:
# Handle multiple exceptions
logger.error(f"Type or Key error: {e}")
except Exception as e:
# Catch all other exceptions
logger.error(f"Unexpected error: {e}")
else:
# Runs if no exception
print("Success!")
finally:
# Always runs
cleanup()
# Raise with context
try:
process_data()
except DataError as e:
raise ProcessingError("Failed to process") from e
Custom Exceptions
class ValidationError(Exception):
def __init__(self, message, code=None):
super().__init__(message)
self.code = code
# Usage
if not valid_email(email):
raise ValidationError("Invalid email format", code="INVALID_EMAIL")
Concurrency & Parallelism
Threading (I/O-bound)
import threading
import concurrent.futures
# Basic thread
def worker(name):
print(f"Worker {name} starting")
time.sleep(2)
print(f"Worker {name} done")
thread = threading.Thread(target=worker, args=("A",))
thread.start()
thread.join()
# Thread pool
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(worker, i) for i in range(10)]
results = [f.result() for f in futures]
Multiprocessing (CPU-bound)
import multiprocessing
def cpu_intensive(n):
return sum(i*i for i in range(n))
# Process pool
with multiprocessing.Pool() as pool:
results = pool.map(cpu_intensive, [1000000, 2000000, 3000000])
AsyncIO (I/O-bound, single thread)
import asyncio
import aiohttp
async def fetch_data(session, url):
async with session.get(url) as response:
return await response.json()
async def main():
async with aiohttp.ClientSession() as session:
urls = ['http://api1.com', 'http://api2.com']
tasks = [fetch_data(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# Run async function
asyncio.run(main())
Web Frameworks
Flask (Minimal)
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/users/<int:user_id>')
def get_user(user_id):
user = db.get_user(user_id)
return jsonify(user)
@app.route('/users', methods=['POST'])
def create_user():
data = request.get_json()
user = db.create_user(data)
return jsonify(user), 201
if __name__ == '__main__':
app.run(debug=True)
FastAPI (Modern, async)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
email: str
age: int
@app.get("/users/{user_id}")
async def get_user(user_id: int):
user = await db.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@app.post("/users", response_model=User)
async def create_user(user: User):
return await db.create_user(user.dict())
Django (Full-featured)
# models.py
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
# views.py
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
def get_user(request, user_id):
user = get_object_or_404(User, pk=user_id)
return JsonResponse({
'id': user.id,
'name': user.name,
'email': user.email
})
RESTful APIs
REST Principles
# Resources as URLs
/users # Collection
/users/123 # Single resource
/users/123/posts # Sub-resource
# HTTP Methods
GET /users # Read collection
GET /users/123 # Read single
POST /users # Create
PUT /users/123 # Update (full)
PATCH /users/123 # Update (partial)
DELETE /users/123 # Delete
# Status Codes
200 OK # Success
201 Created # Resource created
204 No Content # Success, no body
400 Bad Request # Client error
401 Unauthorized
404 Not Found
500 Internal Server Error
API Versioning
# URL versioning
@app.route('/api/v1/users')
@app.route('/api/v2/users')
# Header versioning
version = request.headers.get('API-Version', 'v1')
Pagination
@app.route('/users')
def get_users():
page = int(request.args.get('page', 1))
per_page = int(request.args.get('per_page', 20))
users = User.query.paginate(page, per_page)
return jsonify({
'users': [u.to_dict() for u in users.items],
'total': users.total,
'page': page,
'pages': users.pages
})
Databases
SQL (PostgreSQL/MySQL)
import psycopg2
from contextlib import contextmanager
@contextmanager
def get_db():
conn = psycopg2.connect("dbname=test user=postgres")
try:
yield conn
finally:
conn.close()
# Parameterized queries (prevent SQL injection)
with get_db() as conn:
cursor = conn.cursor()
cursor.execute(
"SELECT * FROM users WHERE email = %s",
(email,)
)
user = cursor.fetchone()
SQLAlchemy ORM
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50))
email = Column(String(120), unique=True)
# Query examples
users = session.query(User).filter(User.age > 18).all()
user = session.query(User).filter_by(email=email).first()
# Bulk operations
session.bulk_insert_mappings(User, user_data)
session.commit()
MongoDB (NoSQL)
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client['mydatabase']
users = db['users']
# Insert
user_id = users.insert_one({
'name': 'Alice',
'email': 'alice@example.com',
'tags': ['python', 'backend']
}).inserted_id
# Query
user = users.find_one({'email': 'alice@example.com'})
active_users = users.find({'status': 'active'}).limit(10)
# Update
users.update_one(
{'_id': user_id},
{'$push': {'tags': 'django'}}
)
Redis (Cache/Message Queue)
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
# Cache operations
r.setex('user:123', 3600, json.dumps(user_data)) # TTL: 1 hour
cached = r.get('user:123')
if cached:
user = json.loads(cached)
# Pub/Sub
# Publisher
r.publish('notifications', json.dumps({'type': 'user_update', 'id': 123}))
# Subscriber
pubsub = r.pubsub()
pubsub.subscribe('notifications')
for message in pubsub.listen():
if message['type'] == 'message':
data = json.loads(message['data'])
Caching
Caching Strategies
# Cache-aside (Lazy loading)
def get_user(user_id):
# Check cache first
cached = cache.get(f'user:{user_id}')
if cached:
return json.loads(cached)
# Load from DB
user = db.get_user(user_id)
if user:
cache.setex(f'user:{user_id}', 3600, json.dumps(user))
return user
# Write-through
def update_user(user_id, data):
# Update DB
user = db.update_user(user_id, data)
# Update cache
cache.setex(f'user:{user_id}', 3600, json.dumps(user))
return user
# Cache invalidation
def delete_user(user_id):
db.delete_user(user_id)
cache.delete(f'user:{user_id}')
Decorator for Caching
def cache_result(ttl=3600):
def decorator(func):
def wrapper(*args, **kwargs):
cache_key = f"{func.__name__}:{str(args)}:{str(kwargs)}"
cached = cache.get(cache_key)
if cached:
return json.loads(cached)
result = func(*args, **kwargs)
cache.setex(cache_key, ttl, json.dumps(result))
return result
return wrapper
return decorator
@cache_result(ttl=7200)
def expensive_calculation(x, y):
return x ** y
Authentication & Security
Password Hashing
import bcrypt
# Hash password
password = "user_password"
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
# Verify password
is_valid = bcrypt.checkpw(password.encode('utf-8'), hashed)
JWT Authentication
import jwt
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key"
def generate_token(user_id):
payload = {
'user_id': user_id,
'exp': datetime.utcnow() + timedelta(hours=24),
'iat': datetime.utcnow()
}
return jwt.encode(payload, SECRET_KEY, algorithm='HS256')
def verify_token(token):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
return payload['user_id']
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
Rate Limiting
from functools import wraps
from flask import request, jsonify
def rate_limit(max_calls=100, window=3600):
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
key = f"rate_limit:{request.remote_addr}:{f.__name__}"
try:
current = int(cache.get(key) or 0)
if current >= max_calls:
return jsonify({'error': 'Rate limit exceeded'}), 429
pipe = cache.pipeline()
pipe.incr(key)
pipe.expire(key, window)
pipe.execute()
return f(*args, **kwargs)
except Exception as e:
# Log error but don't block request
return f(*args, **kwargs)
return wrapped
return decorator
@app.route('/api/search')
@rate_limit(max_calls=10, window=60)
def search():
pass
Input Validation
from marshmallow import Schema, fields, validate, ValidationError
class UserSchema(Schema):
name = fields.Str(required=True, validate=validate.Length(min=2, max=50))
email = fields.Email(required=True)
age = fields.Int(required=True, validate=validate.Range(min=0, max=150))
# Usage
schema = UserSchema()
try:
user_data = schema.load(request.json)
except ValidationError as err:
return jsonify({'errors': err.messages}), 400
Testing
Unit Testing
import unittest
from unittest.mock import Mock, patch
class TestUserService(unittest.TestCase):
def setUp(self):
self.service = UserService()
def test_create_user(self):
user_data = {'name': 'Alice', 'email': 'alice@test.com'}
user = self.service.create_user(user_data)
self.assertEqual(user.name, 'Alice')
@patch('services.database.save')
def test_save_user(self, mock_save):
mock_save.return_value = True
result = self.service.save_user({'name': 'Bob'})
self.assertTrue(result)
mock_save.assert_called_once()
Pytest
import pytest
from pytest import fixture
@fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_get_user(client):
response = client.get('/users/1')
assert response.status_code == 200
assert response.json['name'] == 'Alice'
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("", ""),
])
def test_uppercase(input, expected):
assert input.upper() == expected
Integration Testing
class TestAPI(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.client = self.app.test_client()
self.db = create_test_db()
def tearDown(self):
self.db.drop_all()
def test_create_and_get_user(self):
# Create user
response = self.client.post('/users',
json={'name': 'Test', 'email': 'test@test.com'})
self.assertEqual(response.status_code, 201)
user_id = response.json['id']
# Get user
response = self.client.get(f'/users/{user_id}')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json['email'], 'test@test.com')
Performance Optimization
Profiling
import cProfile
import pstats
# Function profiling
def profile_func(func):
def wrapper(*args, **kwargs):
profiler = cProfile.Profile()
profiler.enable()
result = func(*args, **kwargs)
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10) # Top 10 functions
return result
return wrapper
# Memory profiling
from memory_profiler import profile
@profile
def memory_intensive():
large_list = [i for i in range(1000000)]
return sum(large_list)
Database Optimization
# Use indexes
class User(Base):
__tablename__ = 'users'
email = Column(String, index=True)
created_at = Column(DateTime, index=True)
# Batch operations
# Instead of:
for user in users:
db.session.add(user)
db.session.commit()
# Do:
db.session.bulk_insert_mappings(User, users)
db.session.commit()
# Eager loading (prevent N+1 queries)
users = User.query.options(joinedload(User.posts)).all()
# Query only needed columns
users = db.session.query(User.id, User.name).all()
Caching Expensive Operations
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_computation(n):
# Cache results in memory
return sum(i**2 for i in range(n))
# Custom cache with TTL
from cachetools import TTLCache, cached
cache = TTLCache(maxsize=100, ttl=300) # 5 minutes
@cached(cache)
def get_user_permissions(user_id):
# Expensive permission calculation
return calculate_permissions(user_id)
Design Patterns
Singleton
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Thread-safe singleton
import threading
class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if not cls._instance:
with cls._lock:
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
Factory Pattern
class DatabaseFactory:
@staticmethod
def create_database(db_type, **kwargs):
if db_type == 'postgres':
return PostgresDB(**kwargs)
elif db_type == 'mysql':
return MySQLDB(**kwargs)
elif db_type == 'mongodb':
return MongoDB(**kwargs)
else:
raise ValueError(f"Unknown database type: {db_type}")
# Usage
db = DatabaseFactory.create_database('postgres', host='localhost')
Repository Pattern
class UserRepository:
def __init__(self, db):
self.db = db
def get_by_id(self, user_id):
return self.db.query(User).filter_by(id=user_id).first()
def get_by_email(self, email):
return self.db.query(User).filter_by(email=email).first()
def create(self, user_data):
user = User(**user_data)
self.db.add(user)
self.db.commit()
return user
def update(self, user_id, updates):
user = self.get_by_id(user_id)
for key, value in updates.items():
setattr(user, key, value)
self.db.commit()
return user
Dependency Injection
class EmailService:
def send(self, to, subject, body):
# Send email implementation
pass
class UserService:
def __init__(self, email_service: EmailService, db: Database):
self.email_service = email_service
self.db = db
def register_user(self, user_data):
user = self.db.create_user(user_data)
self.email_service.send(
user.email,
"Welcome!",
"Thanks for registering!"
)
return user
# Usage
email_service = EmailService()
db = Database()
user_service = UserService(email_service, db)
Common Interview Problems
Two Sum
def two_sum(nums, target):
seen = {}
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [seen[complement], i]
seen[num] = i
return []
LRU Cache
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key)
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False)
Rate Limiter (Token Bucket)
import time
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity
self.tokens = capacity
self.refill_rate = refill_rate
self.last_refill = time.time()
def consume(self, tokens=1):
self.refill()
if self.tokens >= tokens:
self.tokens -= tokens
return True
return False
def refill(self):
now = time.time()
tokens_to_add = (now - self.last_refill) * self.refill_rate
self.tokens = min(self.capacity, self.tokens + tokens_to_add)
self.last_refill = now
URL Shortener Design
import hashlib
import string
import random
class URLShortener:
def __init__(self):
self.url_map = {}
self.reverse_map = {}
def shorten(self, long_url):
if long_url in self.reverse_map:
return self.reverse_map[long_url]
# Generate short code
short_code = self._generate_short_code()
while short_code in self.url_map:
short_code = self._generate_short_code()
self.url_map[short_code] = long_url
self.reverse_map[long_url] = short_code
return f"http://short.url/{short_code}"
def expand(self, short_url):
short_code = short_url.split('/')[-1]
return self.url_map.get(short_code)
def _generate_short_code(self, length=6):
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
Producer-Consumer Pattern
import queue
import threading
class ProducerConsumer:
def __init__(self, max_size=10):
self.queue = queue.Queue(maxsize=max_size)
self.running = True
def producer(self, item):
self.queue.put(item)
print(f"Produced: {item}")
def consumer(self):
while self.running:
try:
item = self.queue.get(timeout=1)
# Process item
print(f"Consumed: {item}")
self.queue.task_done()
except queue.Empty:
continue
def start_consumers(self, num_consumers=3):
consumers = []
for i in range(num_consumers):
t = threading.Thread(target=self.consumer)
t.start()
consumers.append(t)
return consumers
Best Practices
Code Organization
# Project structure
myproject/
├── app/
│ ├── __init__.py
│ ├── models/
│ ├── views/
│ ├── services/
│ └── utils/
├── tests/
├── config.py
├── requirements.txt
└── README.md
# Import organization
# 1. Standard library
import os
import sys
# 2. Third-party
import flask
import requests
# 3. Local application
from app.models import User
from app.utils import validate_email
Configuration Management
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')
REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379')
@classmethod
def validate(cls):
required = ['SECRET_KEY', 'DATABASE_URL']
missing = [var for var in required if not getattr(cls, var)]
if missing:
raise ValueError(f"Missing required config: {missing}")
Logging
import logging
import sys
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
# Usage
logger.info("User %s logged in", user_id)
logger.error("Failed to connect to database", exc_info=True)
Documentation
def calculate_discount(price: float, discount_percent: float) -> float:
"""
Calculate the discounted price.
Args:
price: Original price of the item
discount_percent: Discount percentage (0-100)
Returns:
Discounted price
Raises:
ValueError: If discount_percent is not between 0 and 100
Example:
>>> calculate_discount(100, 20)
80.0
"""
if not 0 <= discount_percent <= 100:
raise ValueError("Discount must be between 0 and 100")
return price * (1 - discount_percent / 100)
Quick Reference
Time Complexity
- O(1): dict/set operations, list.append()
- O(log n): binary search, heap operations
- O(n): list traversal, dict.values()
- O(n log n): sorting (Timsort)
- O(n²): nested loops, bubble sort
Space Complexity
- Be aware of hidden space usage
- Recursion uses O(depth) stack space
- Slicing creates new objects
Python Gotchas
# Mutable default arguments
def bad(items=[]): # Don't do this!
items.append(1)
return items
def good(items=None):
if items is None:
items = []
items.append(1)
return items
# Late binding closures
funcs = []
for i in range(3):
funcs.append(lambda: i) # All return 2
# Fix:
funcs.append(lambda i=i: i)
# Integer caching
a = 256
b = 256
a is b # True (cached)
a = 257
b = 257
a is b # False (not cached)
Performance Tips
- Use built-in functions (they're implemented in C)
- Use generators for large datasets
- Profile before optimizing
- Consider using
__slots__for classes with many instances - Use
collections.dequefor queues - Prefer
join()over string concatenation in loops - Use set/dict for membership testing (O(1) vs O(n))
Key Concepts & Comparisons
Python Data Types & Mutability
| Type | Mutable | Use Case | Time Complexity (Access) | Memory Efficiency |
|---|---|---|---|---|
| int, float, str | No | Basic data storage | O(1) | High |
| tuple | No | Immutable sequences, dict keys | O(1) | High |
| list | Yes | Dynamic arrays, stacks | O(1) by index | Medium |
| dict | Yes | Key-value mappings | O(1) average | Medium |
| set | Yes | Unique collections, fast lookups | O(1) average | Medium |
| frozenset | No | Immutable sets, dict keys | O(1) average | High |
Web Framework Comparison
| Framework | Type | Learning Curve | Performance | Use Case | Async Support |
|---|---|---|---|---|---|
| Flask | Micro | Low | Good | Small to medium apps, APIs | Limited (with extensions) |
| FastAPI | Modern | Medium | Excellent | High-performance APIs, async | Native |
| Django | Full-stack | High | Good | Large applications, admin panels | Limited (async views) |
| Tornado | Async | Medium | Excellent | Real-time applications | Native |
| Sanic | Async | Medium | Excellent | High-performance async APIs | Native |
Database Integration Patterns
| Pattern | Implementation | Use Case | Pros | Cons |
|---|---|---|---|---|
| Raw SQL | psycopg2, pymysql |
High-performance queries | Full control, optimal queries | SQL injection risk, database-specific |
| SQLAlchemy Core | Expression language | Complex queries with flexibility | Type safety, database agnostic | Learning curve |
| SQLAlchemy ORM | Object-relational mapping | Standard CRUD operations | Productivity, relationships | Performance overhead |
| Django ORM | Built-in ORM | Django applications | Integrated, admin interface | Django-specific |
| Peewee | Lightweight ORM | Small applications | Simple, lightweight | Limited features |
Async vs Sync Comparison
| Aspect | Synchronous | Asynchronous | Best For |
|---|---|---|---|
| Execution Model | Sequential, blocking | Concurrent, non-blocking | I/O-bound: Async, CPU-bound: Sync |
| Memory Usage | Higher (thread stacks) | Lower (single thread) | Async for many connections |
| Complexity | Low | Medium to High | Sync for simple apps |
| Debugging | Easier | More complex | Sync for development speed |
| Scalability | Limited by threads | High for I/O operations | Async for high concurrency |
Advanced Caching Strategies
| Strategy | Implementation | Use Case | Consistency | Complexity |
|---|---|---|---|---|
| Cache-Aside | App manages cache | Read-heavy workloads | Eventual | Low |
| Write-Through | Write to cache and DB | Strong consistency needed | Strong | Medium |
| Write-Behind | Async write to DB | High write performance | Eventual | High |
| Refresh-Ahead | Proactive cache refresh | Predictable access patterns | Good | Medium |
Python Concurrency Models
| Model | Mechanism | Best For | GIL Impact | Use Cases |
|---|---|---|---|---|
| Threading | threading module |
I/O-bound tasks | Limited by GIL | File I/O, network requests |
| Multiprocessing | multiprocessing module |
CPU-bound tasks | No impact | Data processing, calculations |
| AsyncIO | async/await |
I/O-bound, many connections | Single thread | Web servers, network clients |
| Concurrent.futures | Thread/Process pools | Batch processing | Depends on executor | Parallel task execution |
Testing Strategies
| Type | Framework | Purpose | Scope | Speed |
|---|---|---|---|---|
| Unit Tests | unittest, pytest | Individual functions/methods | Single function | Fast |
| Integration Tests | pytest, unittest | Component interactions | Multiple components | Medium |
| End-to-End Tests | pytest, selenium | Full application flow | Entire application | Slow |
| Performance Tests | pytest-benchmark, locust | Performance validation | System performance | Variable |
Security Best Practices
| Threat | Mitigation | Implementation | Priority |
|---|---|---|---|
| SQL Injection | Parameterized queries | Use ORM or prepared statements | Critical |
| XSS | Input sanitization | Escape user input, CSP headers | High |
| CSRF | CSRF tokens | Framework middleware | High |
| Weak Authentication | Strong password hashing | bcrypt, Argon2 | Critical |
| Insecure Sessions | Secure session management | HTTPOnly, Secure flags | High |
| Information Disclosure | Error handling | Generic error messages | Medium |
Performance Optimization Techniques
| Technique | Implementation | Impact | Complexity | Use Case |
|---|---|---|---|---|
| Database Indexing | Database-level indexes | High | Low | Query optimization |
| Query Optimization | Efficient queries, joins | High | Medium | Database performance |
| Caching | Redis, Memcached | High | Medium | Frequently accessed data |
| Connection Pooling | Database connection pools | Medium | Low | Database connections |
| Lazy Loading | Load data on demand | Medium | Medium | Large datasets |
| Profiling | cProfile, py-spy | N/A | Low | Performance analysis |
Design Pattern Applications
| Pattern | Python Implementation | Use Case | Benefits |
|---|---|---|---|
| Singleton | __new__ method override |
Database connections | Single instance globally |
| Factory | Factory functions/classes | Object creation | Flexible object creation |
| Repository | Data access abstraction | Database operations | Separation of concerns |
| Dependency Injection | Constructor injection | Service dependencies | Testability, flexibility |
| Observer | Event-driven programming | Notifications, pub-sub | Loose coupling |
| Strategy | Algorithm abstraction | Different implementations | Runtime algorithm selection |
Common HTTP Status Codes
| Code | Meaning | Use Case | Client Action |
|---|---|---|---|
| 200 | OK | Successful GET, PUT | Continue normally |
| 201 | Created | Successful POST | Resource created |
| 204 | No Content | Successful DELETE | No response body |
| 400 | Bad Request | Invalid input | Fix request format |
| 401 | Unauthorized | Missing/invalid auth | Provide credentials |
| 403 | Forbidden | Access denied | Check permissions |
| 404 | Not Found | Resource doesn't exist | Check URL/resource |
| 409 | Conflict | Resource conflict | Resolve conflict |
| 422 | Unprocessable Entity | Validation failed | Fix input data |
| 500 | Internal Server Error | Server error | Contact support |
Environment & Deployment Strategies
| Strategy | Description | Pros | Cons | Use Case |
|---|---|---|---|---|
| Virtual Environments | venv, virtualenv |
Isolation, reproducibility | Management overhead | Development |
| Docker Containers | Containerized applications | Consistency, portability | Learning curve | Production deployment |
| uWSGI/Gunicorn | WSGI servers | Production-ready | Configuration complexity | Web applications |
| Kubernetes | Container orchestration | Scalability, resilience | High complexity | Large-scale applications |
| Serverless | AWS Lambda, Google Cloud Functions | No server management | Cold starts, limitations | Event-driven applications |
Quick Reference & Best Practices
Essential Python Packages for Backend
| Category | Package | Purpose | Installation |
|---|---|---|---|
| Web Framework | Flask | Micro web framework | pip install flask |
| Web Framework | FastAPI | Modern, fast API framework | pip install fastapi uvicorn |
| Database | SQLAlchemy | SQL toolkit and ORM | pip install sqlalchemy |
| Database | psycopg2 | PostgreSQL adapter | pip install psycopg2-binary |
| Caching | redis | Redis client | pip install redis |
| HTTP Client | requests | HTTP library | pip install requests |
| Async HTTP | aiohttp | Async HTTP client/server | pip install aiohttp |
| Testing | pytest | Testing framework | pip install pytest |
| Validation | marshmallow | Serialization/validation | pip install marshmallow |
| Environment | python-dotenv | Environment variables | pip install python-dotenv |
Performance Best Practices Checklist
✅ Database Optimization
- Use database indexes for frequently queried columns
- Implement connection pooling for database connections
- Use bulk operations for multiple inserts/updates
- Optimize queries with EXPLAIN ANALYZE
- Implement proper database normalization
✅ Caching Strategy
- Cache frequently accessed data (Redis/Memcached)
- Implement cache invalidation strategies
- Use ETags for HTTP caching
- Cache expensive computations with
@lru_cache - Consider CDN for static content
✅ Code Optimization
- Use list comprehensions over loops when possible
- Prefer generators for large datasets
- Use
__slots__for classes with many instances - Profile code with cProfile before optimizing
- Use built-in functions (implemented in C)
✅ Async Programming
- Use async/await for I/O-bound operations
- Implement proper connection pooling for async
- Avoid blocking operations in async code
- Use async context managers for resources
- Handle exceptions properly in async code
✅ Security Implementation
- Hash passwords with bcrypt or Argon2
- Use parameterized queries to prevent SQL injection
- Implement proper session management
- Validate and sanitize all user input
- Use HTTPS in production
- Implement rate limiting for APIs
Common Python Gotchas & Solutions
# ❌ Mutable default arguments
def bad_function(items=[]):
items.append(1)
return items
# ✅ Correct approach
def good_function(items=None):
if items is None:
items = []
items.append(1)
return items
# ❌ Late binding closures
funcs = [lambda: i for i in range(3)] # All return 2
# ✅ Correct approach
funcs = [lambda i=i: i for i in range(3)]
# ❌ Modifying list while iterating
for item in items:
if condition:
items.remove(item) # Can skip elements
# ✅ Correct approach
items = [item for item in items if not condition]
Development Environment Setup
# Virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# Install requirements
pip install -r requirements.txt
# Freeze dependencies
pip freeze > requirements.txt
# Run development server
export FLASK_ENV=development
flask run
# Or with FastAPI
uvicorn main:app --reload
Testing Best Practices
# ✅ Use fixtures for setup/teardown
@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
# ✅ Test edge cases
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError):
divide(10, 0)
# ✅ Use parametrized tests
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("", ""),
])
def test_uppercase(input, expected):
assert input.upper() == expected
# ✅ Mock external dependencies
@patch('requests.get')
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {'status': 'ok'}
result = make_api_call()
assert result['status'] == 'ok'
Logging Configuration
import logging
import sys
# Production logging setup
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('app.log'),
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
# Usage examples
logger.info("User %s logged in", user_id)
logger.warning("Rate limit exceeded for IP %s", ip_address)
logger.error("Database connection failed", exc_info=True)
Environment Configuration Management
# config.py
import os
from dataclasses import dataclass
@dataclass
class Config:
SECRET_KEY: str = os.getenv('SECRET_KEY', 'dev-secret')
DATABASE_URL: str = os.getenv('DATABASE_URL', 'sqlite:///app.db')
REDIS_URL: str = os.getenv('REDIS_URL', 'redis://localhost:6379')
DEBUG: bool = os.getenv('DEBUG', 'False').lower() == 'true'
def __post_init__(self):
if not self.SECRET_KEY or self.SECRET_KEY == 'dev-secret':
raise ValueError("SECRET_KEY must be set in production")
# Usage
config = Config()
Essential Interview Topics Summary
- Python Fundamentals: Data types, list comprehensions, generators, decorators
- OOP Concepts: Classes, inheritance, polymorphism, abstract base classes
- Async Programming: async/await, asyncio, concurrent programming patterns
- Web Frameworks: Flask vs FastAPI vs Django comparison and use cases
- Database Integration: SQLAlchemy, raw SQL, connection pooling, transactions
- Caching: Redis integration, caching strategies, cache invalidation
- Testing: Unit testing, mocking, pytest fixtures, test-driven development
- Security: Authentication, authorization, input validation, password hashing
- Performance: Profiling, optimization techniques, database query optimization
- Design Patterns: Repository, Factory, Singleton, Dependency Injection
This comprehensive cheat sheet covers the essential topics for Python backend development interviews. Focus on understanding the trade-offs between different approaches and be prepared to implement these concepts with proper error handling and testing.