
Solve Your Users' Problems, Not Your Own
Please stop rebuilding databases and spreadsheets.
A software engineer’s job is to manage complexity on behalf of our users. Tesler’s law of conservation of complexity reminds us that every system retains an essential amount of complexity. We can move that burden between ourselves and our users, yet we can’t make it disappear.
When teams fixate on their own pain points rather than the challenges facing users, they often end up recreating the same tools they as engineers already use. The result is more complexity, not less. Below are three common examples drawn from my experience, along with healthier alternatives.
Schemas as content
Content management systems frequently tempt us with the idea of storing schema definitions as content. The theory is appealing: “If we let content editors manage schemas, we won’t need to be involved in every change.”
In practice, the complexity of schema design never leaves. Versioning, template compatibility, validation, and data migrations all still require the same discipline. The work simply shifts from engineers to non-technical users who didn’t sign up for it. Inevitably the engineering team steps back in to untangle the resulting mess, now maintaining both the original tooling and the custom schema editor.
Ask yourself: is the customisation I’m exposing to my user significantly less flexible than what I can do with my own engineer’s tools? If not, then it’s very likely you’re just moving the complexity from you to them.
A more supportive approach is to keep schema evolution as an engineering responsibility while making the workflow lightweight. Provide clear request channels, automate migrations, offer preview environments, and document patterns that allow editors to move quickly without taking on the full weight of schema governance.
When the UI mirrors the database
Another sign we’ve focused on our own convenience is when the user interface resembles the underlying data model. If the forms, screens, and error messages look like database tables, we’ve essentially built a less capable database client.
Ask yourself: what does my software do that my database client couldn’t? If my users had a MySQL (or whatever) login, would they be any worse off? A database client can typically list, filter, add, update, delete, perform bulk actions, and so on. It even has access controls, some down to the row level. Does your software offer significantly more than that? If not, it might just be another layer.
Users typically want to accomplish tasks in language that reflects their goals, not our implementation details. Rather than exposing rows and columns, design interactions around outcomes: “Schedule a campaign,” “Review a submission,” “Approve a change.” Translate data structures into concepts and flows that hide incidental complexity. The database is already great at being a database. Our job is to turn that power into experiences people enjoy using.
Explaining away user pain with technical detail
Sometimes we respond to a user-reported problem with a technical explanation of why the behaviour is “technically correct” or why the problem can’t be accurately solved. I once watched a teammate point to a design document and explain that losing unsaved changes was an unavoidable consequence of a difficult computational problem. The user left frustrated and none the wiser.
Ask yourself: am I using my problem as an answer to a user’s problem? If so, then you’re forcing the complexity of your job onto them.
Technical limitations are real, but empathy goes further than accuracy. Acknowledge the inconvenience, explore imperfect mitigations, and communicate transparently about constraints. Could we warn the user before data is discarded? Auto-save drafts? Handle the common cases even if the edge cases remain hard? Creative compromises are part of managing complexity responsibly and building genuine value.
A more sustainable mindset
It’s easy to dream about building systems so customisable that change requests disappear. It’s easy to solve our own problems, and not our users’. It’s easy to focus on what makes our job difficult, rather than what makes the user’s job difficult.
In reality, any complexity we push onto users finds its way back to us as support tickets, confusion, and lost trust. Instead of recreating our own tooling - or making our users pay for the complexity of our jobs - let our products carry an opinionated point of view. Make deliberate choices about what is configurable - and why - so that we keep the hard parts on our side of the wall. Ensure our software offers valuable behaviour to our users, over-and-above what our own tools are capable of. Do not leave users high and dry because we are facing a complex problem.
A few guiding principles help:
- Solve user problems first. If a feature primarily makes your team’s own work easier, scrutinise whether it adds user value or simply moves work around.
- Design with intent. Simpler, opinionated workflows often outperform flexible systems that ask users to understand our architecture.
- Invest in feedback loops. If stakeholders need frequent changes, optimise the path for engineers to deliver them safely and quickly instead of pushing that responsibility outward.
- Craft imperfect solutions. Where perfect solutions are impossible, get creative - don’t make the user pay because for problems we can’t solve.
The goal is not to eliminate complexity but to steward it. When we take ownership of the hard parts, our users get the effortless experiences they were promised - and we get software that is easier to evolve with confidence.