The Real Job of Engineering Is Not Control, It Is Choosing Your Constraints Wisely
Hatched by min dulle
Jun 20, 2026
10 min read
1 views
86%
The hidden question behind every engineering disagreement
Why do so many software teams spend enormous effort arguing about lint rules, type systems, functional purity, monoliths, microservices, and architecture patterns, only to end up with systems that are still hard to change? The surprising answer is that most of these debates are not really about tools. They are about where a team chooses to place friction.
That friction can live in code style, in type signatures, in database design, in runtime boundaries, or in deployment architecture. Some teams try to remove friction everywhere, then discover they have made the system fragile. Others add friction indiscriminately, then discover they have made the system miserable. The real craft is not minimizing friction. It is placing the right kind of resistance in the right place.
That is why a simple prompt like “Strict with a stricter Core Web Vitals rule-set” can be more revealing than it looks. It is not just a configuration choice. It is a philosophy: constrain the system early so you can trust it later. The same instinct appears when experienced developers say that typed languages matter more in mixed-seniority teams, that SQL often beats an ORM, that monoliths are still a sensible default, and that chasing theoretical scalability before you need it often becomes self-inflicted complexity.
These are all variations on one deeper truth: good engineering is constraint design.
Complexity is not a badge of honor, it is a tax
There is a seductive myth in software: that the best engineers are the ones who can hold the most complexity in their heads. In practice, this often becomes a form of ego. We admire the person who can navigate a maze, then accidentally build more mazes to prove we are equally clever.
But complexity is not valuable by itself. It is only valuable when it buys capability that simpler systems cannot provide. Most of the time, complexity is just a tax paid for unclear assumptions, premature abstraction, or architecture chosen to impress future reviewers rather than serve present users.
This is where the maturity of experience changes judgment. After enough years, “this seems easy” starts to feel suspicious. Easy often means one of three things: you have not yet faced the edge cases, the cost has been pushed downstream, or the system is quietly depending on assumptions that nobody has named. The easiest designs are often the ones that postpone payment.
If a system feels effortless too early, you may be confusing invisibility with understanding.
That is why the best engineers often grow more skeptical over time, not more optimistic. They become less interested in elegant slogans and more interested in the shape of failure. They ask: where will this break, who will bear the cost, and how expensive is it to recover?
Consider frontend development, which many experienced engineers now describe as a Kafkaesque maze. That complaint is not nostalgia for a simpler past. It is a signal that the frontend stack has accumulated many layers of hidden complexity: build tooling, state synchronization, browser differences, dependency churn, deployment fragmentation, and design pressure to do ever more in the client. The domain itself is not cursed. But it is a perfect example of what happens when a layer that should simplify experience instead becomes a museum of accidental complexity.
The lesson is not “avoid frontend.” The lesson is that every layer should earn its complexity budget.
The strongest abstractions are the ones that fail honestly
One of the most useful ideas in mature engineering is that there are very few true universal abstractions. This is uncomfortable because abstraction feels like progress. We want one elegant interface that solves every problem. We want a framework, a paradigm, or a design pattern that makes everything feel coherent.
Reality is more irritating. Abstractions are local. They work beautifully inside a domain, then become awkward or misleading outside it. Object-oriented design excels when the world is naturally organized around interacting entities with stable responsibilities. Functional programming excels when transformations and composability matter. Typed languages shine when the cost of misunderstanding is high and team experience varies. DynamoDB is excellent when your access pattern fits its model. SQL is superb when you actually need the database to reason about your data.
The error is not using abstractions. The error is worshipping them.
A useful mental model here is the difference between a map and a terrain-preserving tool. A universal abstraction promises the same map for every landscape. But real systems are not one landscape. They are a mountain range, a river delta, and a city block all at once. The better question is not, “What is the most elegant abstraction?” It is, “What constraint reveals the shape of this problem most honestly?”
This is why abstract purity can become a trap. A functional-only worldview may ignore the fact that some domains are easier to express with objects, state, and explicit boundaries. An ORM may promise to hide SQL, but in doing so it often hides the very query planner behavior that determines whether the system performs well. A “write once, use everywhere” abstraction may spare the first developer effort, then impose a permanent cognitive debt on everyone else.
An abstraction is successful when it makes the right things easy and the wrong things hard.
That second clause matters. A good system does not merely reduce effort. It directs effort toward the places where judgment matters most.
Management, types, and tests are all the same conversation
At first glance, management, type systems, and test documentation seem unrelated. In fact, they are all about making knowledge explicit before it becomes expensive.
Typed languages are often more valuable in mixed-experience teams because they move some misunderstandings from runtime into compile time. That is not just a technical benefit. It is a coordination benefit. A type signature becomes a social contract, visible to everyone and checked by the machine. The same logic explains why strong management matters so much. A good manager is not merely a traffic cop. They are a translator of ambiguity into shared expectations. They prevent the organization from becoming a system where every uncertainty turns into invisible work.
Tests deserve the same treatment. A test suite is not just a set of pass or fail checks. It is a repository of intent. The reason test code can never have enough comments is that tests are where future readers go to understand what the system is supposed to mean, not just what it happens to do today. A clean test without explanation can still be opaque if the business rule is subtle, counterintuitive, or historically rooted.
This is also why code coverage is such a poor proxy for quality. Coverage tells you what ran. It says almost nothing about whether the system’s intent is legible, its failure modes are understood, or its boundaries are sensible. A highly covered but poorly designed codebase can still be a swamp.
The same applies to management. A team can be “busy” and still be poorly managed if priorities are fuzzy, ownership is diffuse, or the cost of decision-making is repeatedly pushed onto the wrong people. Good management is a type of architecture: it shapes how knowledge flows, where accountability lands, and how quickly the team can recognize when it is wrong.
Think of it this way: types, tests, and management are all mechanisms for narrowing the gap between what people think is true and what the system can safely assume is true.
The architecture mistake is usually a prediction mistake
Many architecture debates sound technical but are really speculative economics. Teams choose microservices because they predict scale. They choose serverless because they predict flexibility. They choose distributed patterns because they predict future boundaries. Then reality arrives, and the prediction was wrong.
This is why scaling-first design often backfires. Most systems never need the operational complexity they were architected to handle. Even large organizations frequently have projects that do not require scale in the dramatic sense. They require reliability, clarity, and the ability to evolve safely. Those goals are not served by spraying complexity everywhere just in case the future becomes dramatic.
Monoliths remain a sensible choice not because they are trendy, but because they concentrate complexity where it can be managed. They make transactions simpler, make local reasoning easier, and reduce the surface area of failure. Microservices are justified only when there is a concrete reason to split responsibility, deployment cadence, or failure domains. Otherwise, they often create a distributed systems problem to solve a problem that was not distributed in the first place.
Serverless functions can be wonderful for narrow tasks. But building a whole system on top of them can produce a choreography of hidden state, cold starts, vendor coupling, and fragmented observability. The system looks lightweight from far away and exhausting up close. That is the recurring failure mode of architecture by optimism: it treats future coordination cost as someone else’s problem.
The same caution applies to distributed locks, query planners, and databases. These are not philosophical debates. They are domains where the machine is brutally honest. A query planner will not care about your abstraction preferences. A distributed lock will expose every assumption you made about failure. A database will either fit your workload or punish you for forcing it.
If there is a single practical maxim here, it is this: prefer the architecture that makes the system easiest to understand under stress, not the one that looks most sophisticated in a slide deck.
The useful tension: discipline without dogma
A mature engineering culture needs two things that are often in conflict: discipline and anti-dogmatism.
Discipline means using strict linting, types, tests, review standards, and conventions where they genuinely reduce ambiguity. That “Strict” prompt is not bureaucracy for its own sake. It is the recognition that some constraints are worth paying upfront because they prevent diffuse, much larger costs later. In the same way, a typed interface or a carefully chosen schema can feel restrictive in the moment while actually liberating the team over time.
Anti-dogmatism means refusing to turn any tool into a religion. Functional programming is not inherently superior. Object-oriented programming is not inherently stale. SQL is not old-fashioned, it is precise. Java is not interesting because it is novel, it is interesting because it reliably solves hard coordination problems. Even “clean code” can become an aesthetic cult when it stops serving the actual system.
The best teams understand that good constraints are contextual. They do not ask, “What is universally best?” They ask, “What is the least powerful constraint that still makes this system trustworthy?” That one question can settle countless architecture debates.
A practical way to use this idea is to ask three questions before adopting any framework, abstraction, or architecture pattern:
- What specific failure does this prevent?
- What complexity does this introduce?
- Will the people on this team be able to maintain it six months from now?
If you cannot answer those clearly, you are probably buying complexity on credit.
Key Takeaways
- Treat complexity as a cost, not a virtue. If a system is getting more elaborate, demand a concrete reason that cannot be met more simply.
- Use constraints to reveal truth earlier. Types, tests, lint rules, and good management all make hidden assumptions visible before they become expensive.
- Prefer local fit over universal elegance. SQL, monoliths, objects, functions, and serverless can all be excellent when the problem matches the model.
- Be skeptical of architecture built on predictions. If your design depends on future scale, future team size, or future usage patterns, you are making a bet, not an engineering decision.
- Optimize for understandability under stress. The best system is not the one that looks most impressive. It is the one that remains legible when things are failing.
What changes when you stop worshipping abstractions
The deepest shift is not technical, it is moral. When you stop treating complexity as a marker of sophistication, you become more honest about tradeoffs. You stop confusing cleverness with stewardship. You stop building systems to prove you can manage complexity, and start building systems that spare other people from having to.
That is the real meaning of mature engineering. Not more layers. Not more patterns. Not more machinery. It is the ability to choose constraints that make reality easier to face.
The best software is not the software with the most abstract beauty. It is the software whose assumptions are visible, whose failures are understandable, and whose complexity is earned. Once you see that, the old debates change shape. Lint rules, types, database choices, architecture styles, and management quality are no longer separate preferences. They are all expressions of one central discipline: designing systems that tell the truth early.
And that is a far more important skill than merely making systems feel clever.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣