Avoiding ORM Traps and the N+1 Problem in Python
Introduction
Python’s ORMs — SQLAlchemy and the Django ORM — are a joy to write and dangerously easy to make slow. A loop over a queryset that reads one related field can silently fire hundreds of queries. It looks fine in development and falls over on real data.
This post covers the ORM traps Python teams hit most, starting with the N+1 query problem, in both SQLAlchemy and Django.
The N+1 Problem
You load authors and print each author’s book count:
Building Agentic Workflows in Python
Introduction
“Agent” has become the word for any program that calls an LLM more than once, which makes it a word worth being precise about. An agent, in the sense this post uses, is a loop: the model decides which tool to call next, your code executes it, and the result feeds back in — repeating until the model decides it’s done. That’s a genuinely different (and riskier) shape than a single request/response call.
Building Reliable LLM Applications in Python
Introduction
Calling an LLM API is easy. Building an application on top of one that is reliable — that fails predictably, doesn’t hallucinate its way into wrong answers, and doesn’t surprise you with a bill — is a real engineering discipline.
The core mindset shift: treat model output as a hypothesis to verify, not a fact to trust. This post covers the practices that make Python LLM applications production-grade, using Anthropic’s Claude and the official anthropic SDK.
Database Indexing and Query Optimization for Python Developers
Introduction
Fixing N+1 queries with select_related/prefetch_related or selectinload (see the previous post) gets you down to a small, sane number of queries per request. The next bottleneck is what each query costs once the table has millions of rows — and that is almost always about indexing.
An index turns “scan every row” into “look it up directly.” Skip it, and a query that’s instant in development takes seconds once real data volume shows up in production.
Designing for Change: Boundaries, Contracts, and Dependency Inversion in Python
Introduction
Python’s flexibility makes it easy to wire everything together directly — call the requests library from inside your business logic, import the ORM model straight into your pricing rules, instantiate a third-party SDK client wherever it’s needed. It works, right up until the payment provider changes, or you want a fast unit test that doesn’t need a live database.
Designing for change means drawing boundaries around the parts of the system likely to move — third-party services, storage, transport — so that a swap or a test double touches one small adapter instead of rippling through the codebase.
Error Handling Best Practices in Python
Introduction
Python’s error handling reads deceptively simply — try, except, done. But the difference between code that fails clearly and code that fails mysteriously comes down to a handful of habits: catching the right exception, preserving the original cause, and knowing when not to raise at all.
This post covers the practices that keep Python failures debuggable in production.
Prefer EAFP over Look-Before-You-Leap
Python idiom favors EAFP — “Easier to Ask Forgiveness than Permission.” Rather than checking whether an operation will succeed, attempt it and handle the failure:
Production-Grade Docker Images for Python
Introduction
The typical first-draft Python Dockerfile — FROM python, COPY . ., pip install -r requirements.txt — produces an image that is close to a gigabyte, reinstalls every dependency whenever a single source file changes, and runs as root with a Python process that ignores SIGTERM.
This post covers how to fix all of that: small images, cached dependency installs, a non-root runtime, and clean shutdowns.
Pick the Right Base Image
python:3.12-slim is the pragmatic default: a Debian-based image with Python but without the large build toolchain. Avoid the full python:3.12 (hundreds of MB of tools you don’t run) and be cautious with alpine — its musl libc frequently breaks or slows down packages with C extensions (NumPy, pandas, database drivers), forcing slow source builds.
Testing Best Practices in Python
Introduction
Python’s testing tools are lightweight enough that it’s easy to write a lot of tests without writing good ones. A suite that mocks every collaborator, duplicates the same assertion ten times with different inputs pasted in by hand, or chases a coverage number will pass in CI and still miss real bugs.
pytest gives you fixtures, parametrize, and monkeypatch — the tools that make it just as easy to write the right tests as the wrong ones. This post covers how to use them well.