Python Quick Reference: 30 Patterns Every Developer Should Have at Their Fingertips
This isnโt an introduction to Python. Itโs a working reference โ the kind of document youโd want open during a pairing session or before a technical interview. Each section shows a clean, usable pattern with a brief note on whatโs worth knowing about it beyond the obvious.
1. Variables and Data Types
username = "alex" # strscore = 142 # inttemperature = 36.6 # floatis_verified = True # boolmissing = None # NoneType
print(type(username)) # <class 'str'>Worth knowing: Python is dynamically typed but not loosely typed โ "5" + 5 raises a TypeError. Use isinstance() when you need to check types at runtime.
2. Conditional Statements
speed = 85
if speed > 100: print("Slow down")elif speed > 60: print("Cruising speed")else: print("Below limit")Worth knowing: Python has no switch statement pre-3.10. The match statement arrived in Python 3.10 and handles structural pattern matching โ worth learning if youโre on a modern version.
3. Loops โ for and while
# for loop with enumerate (use this instead of range(len(x)))fruits = ["apple", "banana", "cherry"]for index, fruit in enumerate(fruits): print(index, fruit)
# while loop with breakcount = 0while True: if count >= 3: break print(f"Count: {count}") count += 1Worth knowing: Prefer enumerate() over range(len(list)). Use zip() to iterate two lists in parallel. Both are more Pythonic and more readable in code review.
4. Functions and Arguments
def send_email(to, subject, body="No content", cc=None): cc = cc or [] print(f"Sending to {to}, CC: {cc}") print(f"Subject: {subject}")
send_email("alice@example.com", "Meeting", cc=["bob@example.com"])Worth knowing: Default mutable arguments (like def f(x=[])) is a classic Python trap โ the list is shared across all calls. Use None as the default and initialise inside the function.
5. List Comprehensions
# Standardsquares = [n ** 2 for n in range(1, 6)]
# With conditioneven_squares = [n ** 2 for n in range(1, 11) if n % 2 == 0]
# Nested (use sparingly โ readability drops fast)matrix = [[row * col for col in range(1, 4)] for row in range(1, 4)]
print(squares) # [1, 4, 9, 16, 25]print(even_squares) # [4, 16, 36, 64, 100]Worth knowing: List comprehensions are typically faster than equivalent for loops because theyโre optimised at the bytecode level. But if the logic requires more than one condition or a nested structure, a regular loop with a comment is usually clearer.
6. Dictionary Comprehensions
words = ["python", "data", "engineer"]word_lengths = {word: len(word) for word in words}print(word_lengths) # {'python': 6, 'data': 4, 'engineer': 8}
# Invert a dictionaryoriginal = {"a": 1, "b": 2, "c": 3}inverted = {v: k for k, v in original.items()}print(inverted) # {1: 'a', 2: 'b', 3: 'c'}7. String Formatting and Manipulation
name = "Python"version = 3.12
# f-strings (preferred)print(f"Welcome to {name} {version}")
# Useful string methodstext = " hello world "print(text.strip()) # "hello world"print(text.strip().title()) # "Hello World"print("hello".replace("l", "L")) # "heLLo"print(",".join(["a", "b", "c"])) # "a,b,c"print("a,b,c".split(",")) # ['a', 'b', 'c']Worth knowing: f-strings (available since Python 3.6) are the clearest and fastest string formatting option. Use them by default. .format() is still useful for templates; % formatting is legacy โ avoid it in new code.
8. Error Handling with try / except
def safe_divide(a, b): try: result = a / b except ZeroDivisionError: return None except TypeError as e: print(f"Type error: {e}") return None else: return result # runs only if no exception finally: print("Done") # always runs
print(safe_divide(10, 2)) # Done / 5.0print(safe_divide(10, 0)) # Done / NoneWorth knowing: Catch specific exceptions, not bare except:. Catching everything hides bugs. The else block on a try is underused โ it runs only when no exception occurred, which is semantically cleaner than putting that logic inside the try.
9. Custom Exceptions
class ValidationError(ValueError): """Raised when input fails validation rules.""" def __init__(self, field, message): self.field = field super().__init__(f"{field}: {message}")
def validate_age(age: int): if not isinstance(age, int): raise ValidationError("age", "must be an integer") if age < 0 or age > 130: raise ValidationError("age", f"{age} is out of valid range") return age
try: validate_age(-5)except ValidationError as e: print(e) # age: -5 is out of valid range10. File Handling
# Writingwith open("output.txt", "w", encoding="utf-8") as f: f.write("First line\n") f.writelines(["Second\n", "Third\n"])
# Reading all at oncewith open("output.txt", "r", encoding="utf-8") as f: content = f.read()
# Reading line by line (memory-efficient for large files)with open("output.txt", "r", encoding="utf-8") as f: for line in f: print(line.strip())Worth knowing: Always use with for file operations โ it guarantees the file is closed even if an exception is raised. Always specify encoding="utf-8" explicitly; the default varies by OS and creates subtle bugs.
11. Object-Oriented Programming
class BankAccount: def __init__(self, owner: str, balance: float = 0): self.owner = owner self._balance = balance # _ prefix = convention for "private"
def deposit(self, amount: float): if amount <= 0: raise ValueError("Deposit must be positive") self._balance += amount
def withdraw(self, amount: float): if amount > self._balance: raise ValueError("Insufficient funds") self._balance -= amount
@property def balance(self): return self._balance
def __repr__(self): return f"BankAccount(owner={self.owner!r}, balance={self._balance})"
account = BankAccount("Alice", 100)account.deposit(50)print(account.balance) # 150print(account) # BankAccount(owner='Alice', balance=150)Worth knowing: @property gives you controlled access to attributes without callers needing to change their syntax. __repr__ is what gets shown in the REPL and in error messages โ always implement it.
12. Importing Modules and Packages
import mathimport osfrom pathlib import Pathfrom collections import defaultdict, Counter
# Standard library โ always availableprint(math.pi) # 3.141592653589793print(os.getcwd()) # current working directory
# Path (prefer over os.path for most use cases)p = Path("data") / "output.csv"print(p.suffix) # .csv
# Counter โ counts hashable itemswords = ["the", "cat", "sat", "on", "the", "mat", "the"]print(Counter(words)) # Counter({'the': 3, ...})13. Virtual Environments
# Createpython -m venv .venv
# Activatesource .venv/bin/activate # Linux/macOS.venv\Scripts\activate # Windows
# Install and freeze dependenciespip install requests pandaspip freeze > requirements.txt
# Recreate environment from filepip install -r requirements.txt
# DeactivatedeactivateWorth knowing: Name your virtual environment .venv (with the dot) so itโs hidden in most file explorers and automatically excluded by .gitignore templates. Never commit a virtual environment directory to source control.
14. The random Module
import random
# Random integer in range (inclusive both ends)roll = random.randint(1, 6)
# Random float between 0 and 1probability = random.random()
# Choose from a listcolour = random.choice(["red", "green", "blue"])
# Shuffle in placedeck = list(range(1, 53))random.shuffle(deck)
# Reproducible resultsrandom.seed(42)print(random.randint(1, 100)) # Always 52 with this seed15. Lambda Functions
# Single-expression anonymous functionsquare = lambda x: x ** 2
# Most useful as sort keys and in functional operationspeople = [{"name": "Charlie", "age": 30}, {"name": "Alice", "age": 25}]sorted_people = sorted(people, key=lambda p: p["age"])print(sorted_people[0]["name"]) # AliceWorth knowing: Lambda functions are limited to a single expression. For anything more complex, define a named function โ itโs more readable and easier to test and debug. The main legitimate use case is short sort keys and callbacks.
16. Map, Filter, and Reduce
from functools import reduce
temperatures_c = [0, 20, 37, 100]
# map โ applies function to each elementto_fahrenheit = list(map(lambda c: c * 9/5 + 32, temperatures_c))
# filter โ keeps elements where function returns Truewarm = list(filter(lambda c: c > 20, temperatures_c))
# reduce โ accumulates a result across all elementstotal = reduce(lambda a, b: a + b, temperatures_c)
print(to_fahrenheit) # [32.0, 68.0, 98.6, 212.0]print(warm) # [37, 100]print(total) # 157Worth knowing: In Python 3, list comprehensions and generator expressions are often more readable than map and filter for simple cases. Use whichever is clearer in context. reduce has no comprehension equivalent, so it stays.
17. Generators and yield
def fibonacci(limit: int): """Yields Fibonacci numbers up to limit without storing them all.""" a, b = 0, 1 while a <= limit: yield a a, b = b, a + b
# Generator expression (like list comprehension but lazy)squares_gen = (x ** 2 for x in range(1_000_000))
# Only compute what you needfor fib in fibonacci(50): print(fib, end=" ")# 0 1 1 2 3 5 8 13 21 34Worth knowing: Generators produce values on demand. A generator expression for a million items uses almost no memory; a list comprehension stores all million items. Use generators when youโre processing large datasets or infinite sequences.
18. Decorators
import timeimport functools
def timer(func): """Measure and print execution time of a function.""" @functools.wraps(func) # preserves original function metadata 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
@timerdef slow_operation(): time.sleep(0.1) return "done"
slow_operation() # slow_operation took 0.1002sWorth knowing: Always use @functools.wraps(func) inside your decoratorโs wrapper โ without it, func.__name__, func.__doc__, and similar metadata are replaced by the wrapperโs metadata, which breaks introspection and debugging.
19. Context Managers
from contextlib import contextmanager
@contextmanagerdef managed_connection(host: str): """Context manager using contextlib โ simpler than __enter__/__exit__.""" print(f"Connecting to {host}") connection = {"host": host, "active": True} try: yield connection finally: connection["active"] = False print(f"Connection to {host} closed")
with managed_connection("db.example.com") as conn: print(f"Using connection: {conn}")Worth knowing: The @contextmanager decorator from contextlib lets you write context managers as generators without implementing __enter__ and __exit__ on a class. Itโs cleaner for most use cases.
20. Unit Testing with pytest
def add(a: float, b: float) -> float: return a + b
def divide(a: float, b: float) -> float: if b == 0: raise ValueError("Cannot divide by zero") return a / b
# pytest auto-discovers functions starting with test_def test_add_positive_numbers(): assert add(2, 3) == 5
def test_add_negative(): assert add(-1, 1) == 0
def test_divide_raises_on_zero(): import pytest with pytest.raises(ValueError, match="Cannot divide by zero"): divide(10, 0)# Run all testspytest
# With verbose outputpytest -v
# Stop after first failurepytest -xWorth knowing: pytest is the industry standard for Python testing. Itโs more expressive than the built-in unittest module and generates much more readable failure output.
21. Type Hints
from typing import Optional, Union, list as List
def find_user(user_id: int) -> Optional[dict]: """Return user dict or None if not found.""" users = {1: {"name": "Alice"}, 2: {"name": "Bob"}} return users.get(user_id)
def process_value(value: Union[int, float]) -> str: return f"Processed: {value * 2}"
# Python 3.10+ shorthand for Uniondef parse_id(value: int | str) -> int: return int(value)
result = find_user(1)print(result) # {'name': 'Alice'}Worth knowing: Type hints donโt affect runtime behaviour โ Python doesnโt enforce them. Their value is in tooling (mypy for static checking, IDE autocompletion) and documentation. Add them to any code thatโs meant to be maintained by others.
22. Logging (the Right Way)
import logging
# Configure once at module level โ not inside functionslogging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
logger = logging.getLogger(__name__)
def process_batch(items: list): logger.info("Starting batch of %d items", len(items)) for item in items: try: logger.debug("Processing: %s", item) # ... process item except Exception as e: logger.error("Failed on item %s: %s", item, e, exc_info=True) logger.info("Batch complete")
process_batch(["a", "b", "c"])Worth knowing: Never use print() for application logging in code you expect others to run or maintain. logging gives you level control, structured output, and the ability to redirect logs without touching the code.
23. Working with HTTP APIs
import requestsfrom requests.exceptions import HTTPError, Timeout
def get_user(user_id: int) -> dict: url = f"https://jsonplaceholder.typicode.com/users/{user_id}" try: response = requests.get(url, timeout=5) response.raise_for_status() # raises HTTPError for 4xx/5xx return response.json() except HTTPError as e: print(f"HTTP error {e.response.status_code}: {e}") return {} except Timeout: print("Request timed out") return {}
user = get_user(1)print(user.get("name")) # Leanne GrahamWorth knowing: Always set a timeout โ without one, requests can hang indefinitely. Use raise_for_status() to convert HTTP error responses into exceptions rather than silently returning bad data.
24. Data Structures โ Stack, Queue, and Deque
from collections import deque
# Stack (LIFO) โ use a liststack = []stack.append("first")stack.append("second")stack.append("third")print(stack.pop()) # "third" โ O(1)
# Queue (FIFO) โ use deque, not listqueue = deque()queue.append("first")queue.append("second")print(queue.popleft()) # "first" โ O(1)# list.pop(0) is O(n) because it shifts everything โ avoid it
# deque as a sliding windowwindow = deque(maxlen=3)for n in range(6): window.append(n) print(list(window))25. Binary Search
import bisect
# Manual implementation โ good to know for interviewsdef binary_search(arr: list, target: int) -> int: """Return index of target, or -1 if not found.""" lo, hi = 0, len(arr) - 1 while lo <= hi: mid = (lo + hi) // 2 if arr[mid] == target: return mid elif arr[mid] < target: lo = mid + 1 else: hi = mid - 1 return -1
# Standard library implementationsorted_list = [1, 3, 5, 7, 9, 11]idx = bisect.bisect_left(sorted_list, 7)print(idx) # 3
print(binary_search([1, 3, 5, 7, 9], 7)) # 3print(binary_search([1, 3, 5, 7, 9], 6)) # -126. Threading and Multiprocessing
import threadingimport concurrent.futures
# Threading โ good for I/O-bound work (API calls, file reads)def fetch_data(url: str): import requests return requests.get(url).status_code
urls = [ "https://httpbin.org/delay/1", "https://httpbin.org/delay/1",]
# ThreadPoolExecutor is cleaner than managing threads manuallywith concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: results = list(executor.map(fetch_data, urls))
print(results) # [200, 200] โ both fetched concurrentlyWorth knowing: Pythonโs Global Interpreter Lock (GIL) means threads donโt speed up CPU-bound work. Use multiprocessing or ProcessPoolExecutor for CPU-heavy tasks (number crunching, image processing). Use threading or asyncio for I/O-bound tasks.
27. JSON Serialisation
import jsonfrom datetime import datetime
# Basic serialise/deserialisedata = {"name": "Alice", "scores": [95, 87, 91], "active": True}json_str = json.dumps(data, indent=2)restored = json.loads(json_str)
# Custom encoder for types JSON doesn't handle nativelyclass DateEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, datetime): return obj.isoformat() return super().default(obj)
payload = {"event": "login", "timestamp": datetime.now()}print(json.dumps(payload, cls=DateEncoder))28. SQLite Database Operations
import sqlite3from contextlib import contextmanager
@contextmanagerdef get_db(path: str = ":memory:"): conn = sqlite3.connect(path) conn.row_factory = sqlite3.Row # rows act like dicts try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close()
with get_db() as db: db.execute(""" CREATE TABLE products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL ) """) db.execute("INSERT INTO products (name, price) VALUES (?, ?)", ("Widget", 9.99)) rows = db.execute("SELECT * FROM products").fetchall() for row in rows: print(dict(row)) # {'id': 1, 'name': 'Widget', 'price': 9.99}Worth knowing: Always use parameterised queries (? placeholders) rather than string formatting โ SQL injection is a real risk in code that handles user input, even in internal tools.
29. Flask โ Minimal Web Application
from flask import Flask, request, jsonify
app = Flask(__name__)
# In-memory store for demo purposesusers = {}
@app.route("/users", methods=["GET"])def list_users(): return jsonify(list(users.values()))
@app.route("/users", methods=["POST"])def create_user(): data = request.get_json() if not data or "name" not in data: return jsonify({"error": "name required"}), 400 user_id = len(users) + 1 users[user_id] = {"id": user_id, "name": data["name"]} return jsonify(users[user_id]), 201
if __name__ == "__main__": app.run(debug=True)Worth knowing: debug=True enables the interactive debugger and auto-reloading, which is useful in development. Never run with debug=True in production โ it exposes the debugger console.
30. Code Quality and Best Practices
# Tools to run on any Python project# pip install black isort mypy pylint
# black โ opinionated auto-formatter, zero config# isort โ sorts and organises imports# mypy โ static type checking based on type hints# pylint โ linting, style, and complexity checks
# Example: well-structured function with all best practices appliedfrom pathlib import Pathfrom typing import Optionalimport logging
logger = logging.getLogger(__name__)
def load_config(path: Path) -> Optional[dict]: """ Load JSON config from path.
Returns None if file is missing or malformed. Logs specific errors rather than silently failing. """ import json
if not path.exists(): logger.warning("Config file not found: %s", path) return None
try: with path.open(encoding="utf-8") as f: return json.load(f) except json.JSONDecodeError as e: logger.error("Invalid JSON in %s: %s", path, e) return NoneWorth knowing: The four tools above โ black, isort, mypy, pylint โ form a solid quality baseline for any Python project. Running them in CI ensures consistent style and catches type errors before they reach production. Configure them in pyproject.toml to avoid per-developer configuration drift.
Interview and Exam Quick Review
These are the patterns that come up most frequently in Python technical interviews:
| Topic | What to Know |
|---|---|
| List comprehensions | Syntax, with condition, vs map/filter |
| Generators | yield, memory efficiency, next() |
| Decorators | @functools.wraps, closure mechanics |
| OOP | __init__, @property, __repr__, inheritance |
| Error handling | Specific exceptions, else, finally |
| Type hints | Optional, Union, ` |
| Context managers | with, @contextmanager |
| Threading vs multiprocessing | GIL, I/O-bound vs CPU-bound |
| Binary search | Manual implementation + bisect |
| Default mutable args | The def f(x=[]) anti-pattern |
The code patterns above are not just for reference โ theyโre the building blocks for every larger Python programme. If you can write each one from memory and explain why itโs structured the way it is, you have a solid foundation for interviews, production code, and pair programming sessions alike.