- Complexity accumulates through individually reasonable decisions — simplicity is the result of repeatedly deciding not to add more than the problem requires.
- Essential complexity belongs to the business problem. Accidental complexity is introduced by how you build it — and it's the accidental kind worth hunting down.
- AI makes code faster to generate, which makes it easier to generate more code than a problem needs — judgment about what should exist matters more, not less.
- The Simplicity Test: eight questions worth asking about anything before you ship it.
When I was learning to code, getting something to work felt like the finish line. Working on production software taught me that it's usually the beginning of a different problem: can someone else understand this, change it, test it, and extend it later?
A feature starts with one requirement. Then an edge case shows up. Then an integration. Then a config option. Then an abstraction to "keep things flexible." Then a layer that's there because it might be useful someday. None of those additions is unreasonable on its own — that's exactly the problem. Complexity rarely arrives as one bad decision. It compounds from several good-sounding ones. The question I keep coming back to is simple: does this complexity solve a real problem, or did we create it ourselves?
01 — The default direction
Why Does Software Get Complicated by Default?
Software doesn't get complicated because someone makes an obviously bad call. It gets complicated because every feature can introduce its own conditions, states, dependencies, permissions, data flows, and edge cases — and a system can end up difficult to work in without anyone ever making a decision that looked wrong at the time.
The distinction I find useful is essential complexity versus accidental complexity. Essential complexity belongs to the business problem itself — it's there because the problem is genuinely that shaped, and you can't engineer it away without lying about what the software actually needs to do. Accidental complexity is introduced by how the solution gets implemented — the layers, options, and abstractions that exist because they seemed reasonable in the moment, not because the problem demanded them.
A concrete example of essential complexity: one of our products has to talk to multiple storage backends — SiteLink, StorEdge, ESS, CC Storage, and others — because the business really does span vendors. That adapter surface is real. Same with CSV import for membership data: dry-run, row-level errors, and an audit trail are more moving parts than a naive upsert, but each one maps to an actual operational risk. I'm fine with that kind of complexity. Removing it would mean lying about the problem.
The goal isn't zero complexity. It's making sure we don't add complexity the business problem never asked for.
02 — Easy vs. simple
Why Isn't "Simple" the Same Thing as "Easy"?
"Easy" can mean familiar, quick, or convenient for whoever is writing the code right now. "Simple" means the responsibilities and relationships in the system are easier to understand and reason about later. Those two things are not the same, and confusing them is where a lot of accidental complexity comes from — a shortcut that's easy today can create coupling, duplication, or special cases that make next month's work expensive.
I learned this one directly. On a call-management product, I started a "Customer Lookup Enhancements" pass that ballooned across every storage-provider adapter. It worked, in the sense that the code compiled and the shape looked more complete — but it made a large surface area harder to reason about. We reverted the whole enhancement in one shot: roughly half the churn removed.
"The lesson wasn't 'don't improve lookup.' It was that a wide, speculative rewrite across every provider is easy to start and expensive to own. Cutting back was the disciplined move."
— Ian Anderson, Software Engineer, Virgo Development
03 — Where it sneaks in
Where Do Engineers Accidentally Create Complexity?
A few sources of accidental complexity show up often enough to be worth naming directly:
- Premature abstraction — creating a reusable system before the team understands what actually needs to be reused
- Speculative "future-proofing" — building for hypothetical requirements that may never arrive
- Unnecessary dependencies — adopting a package, framework, service, or tool when the problem is smaller than the tool
- Clever code — optimizing for compactness or novelty rather than readability and explicit intent
- Excessive configurability — turning every decision into an option, even when the product doesn't need that flexibility
- Feature creep — letting secondary requirements reshape an MVP before the core value has been validated
- AI-generated expansion — accepting a larger solution because AI can produce it quickly, rather than asking whether all of the generated structure is necessary
The dangerous part is that most unnecessary complexity arrives wearing the label "this might be useful later."
04 — The discipline
What Does It Actually Take to Keep Software Simple?
Simplicity isn't one design choice. It's a habit of repeatedly challenging unnecessary moving parts:
- Start with the requirement, not the preferred technology
- Build what's needed now; preserve room to change without implementing hypothetical futures in advance
- Prefer small, reviewable changes that are easier to understand and test
- Keep responsibilities separated so one change doesn't unexpectedly affect unrelated parts of the system
- Use clear names and explicit control flow when cleverness would save only a few lines of code
- Refactor when real patterns emerge instead of predicting every abstraction ahead of time
- Use code review as a simplification step — ask whether the same requirement can be satisfied with fewer concepts or dependencies
- Be willing to say "not yet." Deferring a feature or abstraction is often a stronger engineering decision than implementing it early
On a food-truck map app, the map used to wait behind a mapReady gate until geolocation or a saved view resolved. That felt careful, but it delayed the thing users actually wanted: seeing the map. I switched to showing the map immediately, seeding location from a small last-known cache in local storage, then refining once GPS arrived. The same idea shows up in our Parade of Homes Capacitor apps — one shared web shell configured per association, rather than treating every client like a from-scratch native build (a good companion read: our Capacitor vs. native app development post, on choosing an implementation approach based on requirements rather than assuming the most elaborate path is automatically better).
That's also how we try to choose technology at Virgo — for the project and the requirement, not because a stack is fashionable or personally preferred. See our Custom Software Development page for how that plays out end to end.
05 — Why it matters to the business
Why Should a Business Care About Software Simplicity?
Clients don't purchase "simple architecture." They purchase the ability to keep improving their product without every future change becoming disproportionately expensive. In practice, that shows up as:
- Faster changes — engineers can understand the impact of a request more quickly
- More predictable estimates — fewer hidden dependencies and side effects make work easier to scope
- Easier debugging — fewer moving parts reduce the number of places a defect can hide
- Lower onboarding cost — a new engineer can understand the system without reconstructing years of implicit decisions
- Safer expansion — clearly separated components are easier to extend as the product grows
- Less maintenance drag — future development budget goes toward new value instead of repeatedly decoding old complexity
I built a membership-management prototype with its own models and a status API. When we merged it into the real HBA platform, the brief was explicit: don't stand up a second Django app, a second Vue shell, or a parallel "is this member active?" endpoint. Bid Board already gated on existing membership predicates. The valuable piece was GrowthZone CSV import (dry-run → apply + audit) wired into MemberBase — not a second source of truth. Understanding both the prototype and the production app is what let us delete the duplicate path before it shipped.
Unnecessary complexity has a real cost attached to it, too — we break that down in How Much Does It Cost to Build a Custom Software Application in 2026?
06 — AI in the workflow
Does AI Make Software More Complicated?
AI dramatically reduces the friction involved in producing code, prototypes, boilerplate, tests, and alternative implementations. That's genuinely useful — and it also makes it easier to generate more code than the problem needs. An answer can compile, pass a quick test, or look sophisticated while still introducing unnecessary layers or assumptions. The engineer still has to understand the requirement, evaluate tradeoffs, review the architecture, test behavior, and decide whether each part belongs.
"When writing code becomes easier, deciding what code should exist becomes more important."
— Ian Anderson, Software Engineer, Virgo Development
The library's Insights hub is the clearest example I have of the first failure mode: an agent-assisted commit landed a Vue file over two thousand lines — close to the mockup and quick to produce, but more surface area than I'd casually own without pruning. It "worked" toward the design; owning and thinning that surface afterward was the human part.
The flip side happens too. On a Lesson Builder signup flow, I removed an entire first wizard step once it was clear the gate didn't need to be framed as a step at all. Agents help me try alternate implementations faster, especially across front-end and API seams on reporting and Lesson Builder — but the cleaner outcomes I can point to in git are usually me deleting a step or a gate after seeing the fuller version. AI accelerated the options. I still had to choose the smaller one.
This is the same judgment we lean on across app development and full-stack work generally — AI can produce a plausible answer fast; deciding which parts of it actually belong is still an engineering call.
07 — At Virgo Development
What Does This Look Like at Virgo Development?
"Turning complexity into clarity" is Virgo's brand line, but on our team it functions as an engineering principle, not just a marketing sentence. Technology gets chosen for the project and the requirement, not because it's fashionable or personally preferred. At the product level, our MVP/Aha Moment discipline asks the same question simplicity asks at the code level: what's the shortest credible path to the value the customer actually needs? More on that in The Aha Moment: How to Build an MVP Around What Customers Actually Want.
Weekly Agile iteration gives us a chance to learn before adding speculative scope or architecture, and full-stack and app development both reward clear interfaces and responsibilities — changes frequently cross front-end, back-end, data, and integration boundaries, and a clean boundary is the difference between a change that touches one place and one that touches everywhere.
In my first few months here, Adam would help me simplify my code during code review — almost every time. Lately, after learning to write production code better up front, those review sessions have gotten a lot shorter. That's really the whole philosophy in miniature: human engineering judgment stays central, even as the tools around it keep getting faster.
The Simplicity Test
A few questions I actually use before shipping something:
- Does this solve a requirement we actually have today?
- Could another engineer understand why this exists six months from now?
- Are we introducing a dependency because we need it — or because it's convenient?
- Are we creating an abstraction before we understand the pattern?
- Are two responsibilities coupled that could remain independent?
- Can we satisfy the requirement with fewer moving parts without sacrificing reliability or security?
- If AI generated this solution, do we understand why every important part of it exists?
- If we remove this layer, feature, option, or service, what real capability do we lose?
If the answer is "I'm not sure why this exists," that's usually a good reason to look at it again.
08 — Common questions
Quick Answers
What's the difference between essential and accidental complexity?
Essential complexity comes from the business problem itself — it's necessary, and removing it means solving a different, easier problem than the one you actually have. Accidental complexity comes from how the software was built and can usually be simplified without losing anything real.
Why does software get complicated even when no one makes a bad decision?
Because complexity compounds from individually reasonable choices — an edge case here, an integration there, a config option, an abstraction — each defensible on its own, together adding up to a system nobody can hold in their head.
Does AI make software more complicated?
Not by itself — but it makes it much easier to generate more code than a problem needs, which is why engineering judgment about what should exist matters more, not less, as AI adoption grows.
How do you know if a piece of software is overengineered?
A few signs: the same fact has two owners, a small change touches every provider or config, a screen waits on ceremony before doing the obvious thing, or you can't explain why a layer exists in one sentence.
Back to the question this piece opened with: does this complexity solve a real problem, or did we create it ourselves? Simplicity isn't minimalism for its own sake — reliability, security, scale, and real business requirements can and do justify complexity. The discipline isn't refusing complexity. It's refusing to add the rest of it without a reason.
"Good software can solve a complicated problem without forcing everyone who touches it afterward to carry unnecessary complexity with them."
Working through a system that's picked up more complexity than the problem requires? Book a Discovery Call and let's look at it together.
Ian Anderson is a Software Engineer at Virgo Development and a graduate of the Mobile App Development program at Dixie Technical College. He works across full-stack software and mobile app development and has become an active advocate within the Virgo team for exploring practical uses of AI-assisted development tools. He's especially interested in problem-solving, logic, and finding clear solutions to difficult technical problems. This is the fifth post in VirgoDev's ongoing engineer interview series — earlier installments covered custom software vs. off-the-shelf SaaS, how to scale software without adding more servers, how to cut a software budget without cutting what matters, and how to scope a software project the right way.