Learning that simple beats clever almost every time
Web
Learn why simple architectural patterns beat clever ones for long-term durability, especially in data and AI systems. A practitioner's guide to avoiding complexity.
I still remember the feeling. It was 2 a.m., and I was staring at a stack trace that spiraled through five different services. The system was clever. I was chasing a race condition across a custom Consul-based service discovery layer and an asynchronous RabbitMQ event bus that handled compensating transactions. On the whiteboard, it was a masterpiece. In production, it was a ghost that I, its creator, could no longer understand.
The bug, it turned out, was a subtle interaction that only emerged under a specific, unlucky timing of network latency. The fix was a single line of code. Finding it took three days. That was the moment the romance of complexity died for me.
The Seduction of a Beautiful Mechanism
Especially early in your career, complexity feels like progress. It’s intellectually stimulating to weave together intricate patterns. The temptation today isn't a custom ORM; it's jumping straight to a multi-service architecture with Saga patterns for a problem a modular monolith could handle for years. It's using a complex agentic framework like LangGraph for a workflow that is, at its heart, a simple, linear state machine.
In his canonical talk "Simple Made Easy," Rich Hickey gives us the vocabulary for this trap. We mistake "easy" — meaning familiar, or close at hand — with "simple." It’s "easy" to pull in a massive framework to solve a small problem with three lines of code. But we haven't made the system simpler; we've just entangled it with a huge, opaque dependency. The simple path, like writing a few dozen lines of straightforward, auditable code, might feel harder upfront but is profoundly less complex.
Accidental Complexity in the AI Stack
Nowhere is this temptation stronger than in the modern data and AI stack. The new "clever" is the Rube Goldberg machine for a simple RAG application. I’ve seen teams insist on syncing data between PostgreSQL, Elasticsearch, Redis for caching, and a dedicated vector database like Pinecone, all to power a single search box. The architecture diagram looks impressive, but the operational reality is a nightmare of synchronization failures and spiraling costs.
The simple, durable path is often to unify. A single PostgreSQL instance with the pgvector extension can handle relational data, text search, caching, and vector embeddings perfectly well for the first few million users. You trade a bit of theoretical, specialized performance for a massive gain in operational simplicity.
This principle is critical when building with LLMs. As Eugene Yan documents in his excellent guide to LLM-based systems, the most reliable patterns use deterministic logic to scaffold small, isolated calls to an LLM. The clever, fragile anti-pattern is to chain multiple agents in a non-deterministic loop and hope for the best. Simplicity is our primary guardrail against chaos.
The Exception: When Clever is Essential
Of course, the goal isn't to be naive. Some problems are inherently complex. In his 1987 essay "No Silver Bullet," Fred Brooks distinguished between essential complexity, which is inherent to the problem, and accidental complexity, which we inflict upon ourselves with our tools and architectures. Our job is to ruthlessly eliminate the accidental, so we have the capacity to deal with the essential.
If you're building a high-frequency trading platform, you need lock-free data structures. If you're building a real-time collaborative editor like Figma, you need to understand the "clever" world of Conflict-Free Replicated Data Types (CRDTs). In these cases, the simple path doesn't meet the core requirements. The key is to recognize when that complexity is truly essential, and even then, to isolate it behind the simplest possible interface.
The Maintenance Tax and The 3 a.m. Test
Every "clever" component comes with a tax you pay for the life of the system. There's the onboarding tax for new engineers, the cognitive load tax for everyone, and the debugging tax when things inevitably break. My personal heuristic is the "3 a.m. test." When I am paged awake and my brain is at 20% capacity, can I understand this system? Or will I be staring at my own beautiful, inscrutable machine, wishing I had just made a simple, boring choice instead?
Principles for Durable Systems
Fighting complexity is an active discipline. It's a sign of seniority, not a lack of skill. The best systems I’ve ever worked on, the ones that ran for years with minimal fuss, were never the most clever. They were the most obvious. Here are a few principles I try to build by:
- Optimize for Deletability. Build modular systems that are easy to throw away. This prevents you from getting too attached to a clever component that should have been replaced years ago.
- Choose the Tool with the Least Magic. Frameworks that do a lot for you are great until you step outside their happy path. Prefer fundamental, predictable parts.
- Solve Today's Problem. Resist building a grand platform for a hypothetical future. That future will be different than you imagine, and your premature abstractions will only get in the way.
- Bound the Non-Deterministic. Treat agentic AI components like a radioactive core. Contain them. Put clear, deterministic contracts and validation around them. The vast majority of your system should be stable, automatable, and boring.
The goal is not a flash of brilliance, but a disciplined, humble respect for the simple patterns that actually work.