Avoiding ORM Traps and the N+1 Problem in Java
Introduction
An ORM like JPA/Hibernate makes database access feel like working with plain objects. That is exactly the problem: a single innocent-looking loop can fire hundreds of SQL queries, and you won’t see it until production slows to a crawl.
This post covers the ORM traps that bite Java teams most often — starting with the notorious N+1 query problem — and the concrete techniques that fix them.
The N+1 Problem
Consider loading authors and printing their books:
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 Java
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 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 Java
Introduction
LLMs are usually associated with Python, but a great deal of production software — banking, enterprise backends, long-lived services — runs on the JVM, and those systems increasingly need to call language models too. Java’s strong typing and mature tooling are genuine assets here: they push you toward exactly the discipline reliable LLM applications require.
The core mindset is the same in any language: treat model output as a hypothesis to verify, not a fact to trust. This post covers the practices that make Java LLM applications production-grade, using Anthropic’s Claude and the official anthropic-java SDK.
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 Java Developers
Introduction
Fixing N+1 queries (see the previous post) gets your Hibernate app down to a handful of queries per request. The next bottleneck is what each of those queries costs once your tables have millions of rows — and that is almost always a question of indexing.
An index turns “scan every row” into “look it up directly.” Get the index wrong — or skip it — and a query that took 2ms in development takes 4 seconds in production once real data volume shows up.
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 Java
Introduction
Most Java systems don’t fail because a single class was written badly. They fail because the boundaries between classes were drawn in the wrong place — a database library leaks into business logic, a payment provider’s SDK shows up in twelve unrelated files, and changing one third-party dependency means touching half the codebase.
Designing for change is not about predicting the future correctly. It’s about isolating what varies so that when it inevitably does vary — a new payment provider, a swapped database, a different message broker — the blast radius is one adapter, not the whole system.
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.