
Career · · 6 min read
The Hardest Part of Software Development Isn't Coding
- Career
- Leadership
- Opinion
- Productivity
The code is rarely what sinks a project. What sinks it is a requirement nobody wrote down, a decision nobody owned, or a "quick change" that quietly doubled the scope. Those problems don't show up in a stack trace.
When I started as a UI/UX designer, I thought developers had the hard job. Then I became a developer and thought the hard part was state management, or deployment, or that one bug that only happened on Safari. Now that I lead projects at NovaNext and still write code most weeks, I've changed my mind again.
Writing code is the part I'm most prepared for. I have tools, docs, tests, a type checker, and AI assistants that can draft a component in seconds. What I don't have a tool for is figuring out what the client actually means when they say "make it simple."
Understanding the problem is the real work
Most bugs I've chased in production weren't logic errors. They were understanding errors. The code did exactly what we built it to do. We just built the wrong thing.
Say a restaurant owner asks for "split billing." That sounds like one feature. In practice it raises a dozen questions:
- Split how: by item, by equal share, or by custom amount?
- Taxes and service charge: are they split proportionally or added to one bill?
- Partial payments: what happens if one person pays by cash and the other leaves?
- Reprints: can a split bill be merged back after printing?
- Permissions: can a waiter split, or only a cashier?
When I worked on NovaRestro, a feature like this could take two days to code and a week to define properly. If you skip the defining, you still pay for it later, just with interest. I wrote more about that project in Building NovaRestro: From Idea to Restaurant Management Platform.
The cheapest place to fix a bug is in a conversation, before anyone opens an editor.
These days I try to write the edge cases down as questions before estimating anything. If I can't answer them, the estimate is fiction.
Communication is a technical skill
Developers like to treat communication as a soft skill, something separate from the "real" work. I disagree. Explaining a trade-off clearly is as technical as writing a migration. You have to understand the system deeply enough to describe its consequences in plain words.
A few things I've learned the hard way:
- Say what you're not doing. "We'll ship reservations this sprint, but not deposits" prevents a surprise later.
- Write decisions down. A two-line note in the ticket beats a perfect memory of a meeting.
- Translate, don't simplify. A client doesn't need to know what a database index is. They do need to know why the report page is slow and what it costs to fix.
- Ask the dumb question early. It's only dumb in week one. In week six, it's a crisis.
Working remotely with Inovetri made this obvious. When your team isn't in the same room, every unclear message turns into a half-day delay. Async work forces you to be precise, and that habit carried over into everything else.
Scope moves, and someone has to notice
Scope creep almost never arrives as one big request. It arrives as small, reasonable asks. A filter here. An export button there. "Can the kitchen ticket also show the table's notes?" Each one is fine on its own. Together they eat a sprint.
My job as a technical lead is partly to notice the drift and name it out loud. Not to say no to everything, but to make the trade-off visible: this new thing is in, so which old thing moves out or moves later?
I keep a simple habit for this. Any request that arrives mid-sprint goes into a list, and at the next planning session we decide on all of them together. It removes the pressure of deciding in the moment, which is when bad decisions happen.
Code review is about people as much as code
Reviewing code taught me more about communication than any meeting did. A comment like "this is wrong" teaches nothing. A comment like "this will re-fetch on every keystroke, could we debounce it or move it to submit?" teaches a pattern.
When I mentor developers and interns, the hardest part isn't knowing the right answer. It's giving feedback in a way that makes them better at finding the answer next time, without making them afraid to open a pull request. I've had to unlearn my own instinct to just rewrite the thing myself. That's faster today and slower forever.
The move from frontend developer to lead was mostly about this kind of shift. I wrote about it in What I Learned Moving From Frontend Developer to Technical Project Manager.
Deciding when it's good enough
Perfectionism looks like quality from the inside. From the outside it looks like a project that hasn't shipped.
Every feature has a point where more polish stops paying for itself. Finding that point is a judgement call, and no linter can make it for you. I ask a few questions:
- Does it solve the user's actual problem today?
- Is it safe: no data loss, no security hole, no broken flow?
- Can we change it later without a rewrite?
- Will anyone notice the thing I'm still tweaking?
If the answers are yes, yes, yes and no, it ships. The rough edges go into the backlog with an honest label.
AI made this more true, not less
I use Claude and ChatGPT every day, and coding agents now handle a real share of the typing. That makes the non-coding parts even more important. An agent can produce a working endpoint in a minute. It can't tell you whether the endpoint should exist, who's allowed to call it, or what the restaurant owner meant by "simple."
If anything, the bottleneck has moved further upstream. When implementation gets cheap, a vague requirement gets expensive faster, because you can build the wrong thing at high speed. I covered how I keep that in check in How I Use AI Without Letting AI Write My Entire Codebase.
What actually helps
None of this is mysterious. It's a set of habits, and they're boring on purpose:
- Write a short spec before building anything non-trivial, even if it's five bullet points.
- List edge cases as questions and get answers before estimating.
- Keep a visible "not doing" list next to the sprint goal.
- Record decisions where the team will find them, not in a chat thread.
- Review code with the goal of teaching, not winning.
- Demo early, even when it's ugly. A rough screen gets better feedback than a perfect description.
What I'd tell you
If you're early in your career, keep getting better at code. It matters. But start practising the other part now: asking clarifying questions, writing things down, saying "I don't know yet," and explaining trade-offs to people who don't care about your framework.
The developers I most want on a team aren't always the fastest coders. They're the ones who understand the problem before they solve it, and who tell me early when something is going sideways. That skill doesn't show up in a GitHub contribution graph, but it shows up in every project that ships on time.
