Article

What Shipping Seventy Projects Taught Me About Being a Better Engineer

·6 min read min read·👁 1
Dharmendra Singh Yadav

Dharmendra Singh Yadav

AI Full-Stack Engineer

Reflective illustration representing lessons learned across many shipped software projects.

Almost nothing that made me meaningfully better at this job was a framework.

That is not an argument against learning tools. Tools matter and I have learned plenty. But looking back across five years and something north of seventy shipped projects, the changes that actually moved my effectiveness were mostly changes in judgement — and judgement turned out to be much harder to acquire than syntax.

Here is what I would tell myself at the start.

1. Most Decisions Are Cheap. A Few Are Not. Learn to Tell Them Apart.

Early on I gave every decision roughly equal weight. Which state management library, which folder structure, which CSS approach, which database — all of it felt consequential, so all of it got the same anxious deliberation.

It is not equal. Most technical decisions are trivially reversible. You can swap a component library over a weekend. You can restructure folders with an afternoon of find-and-replace. Getting these slightly wrong costs you very little.

A small number of decisions are effectively permanent. Your data model. Your tenancy strategy. Your authentication and permissions boundaries. Your public API contract. Changing any of these after launch means migrations, coordinated releases, and touching code you had forgotten existed.

The skill is triage: spend disproportionate time on the second category and move fast on the first. Most of the "over-engineering versus under-engineering" debate dissolves once you sort decisions this way, because the answer differs by category.

2. Your Estimates Are Wrong in a Consistent Direction

I used to think estimation was a skill I lacked and would eventually acquire. It is more useful to treat it as a measurement problem.

Track your estimates against actual time for a few months. Almost everyone finds a stable personal multiplier. Applying that multiplier improves your accuracy more than any technique, because the error is systematic rather than random.

The reason is that we estimate the part we can picture — writing the code — and omit everything around it. Integration with the thing that turned out to work differently than documented. The edge case discovered during testing. The review cycle. The deployment. The bug found on day two.

Estimating "the coding" and calling it an estimate for "the work" is the single most reliable source of missed deadlines I have seen, in myself and in every team I have worked with.

3. Write the Code That Is Easy to Delete

Reusability was the value I was taught to optimise for. I now think deletability matters more.

Code gets deleted far more often than it gets reused. Requirements change, features get cut, approaches get replaced. The abstraction I built to serve three future use cases usually ended up serving one, while making that one harder to understand.

Deletable code has clear boundaries, few dependents, and does not require a coordinated refactor to remove. Premature abstraction is the opposite: it tangles unrelated features together so that removing one means untangling all of them.

The heuristic I use now is to wait for the third occurrence before abstracting. Two similar things are often coincidence. Three is usually a pattern.

4. The Bug Is Almost Never Where You Think It Is

Hours I have lost to this are genuinely uncountable.

The instinct when something breaks is to start reading the code you most recently changed. Sometimes correct. Frequently not — particularly for the class of bug that only appears in production, only under load, or only for one user.

What actually works is boring and disciplined: reproduce it reliably before attempting a fix. If you cannot reproduce it, you are not debugging, you are guessing, and a fix that follows a guess is indistinguishable from a fix that follows understanding until it fails again next month.

Then bisect. Change one variable at a time. Read the error message completely, including the parts that look like noise. Check the assumption you are most confident about, because that is the one you have not verified.

5. Technical Debt Is Fine. Undocumented Technical Debt Is Not.

Shipping something imperfect to hit a deadline is a legitimate engineering decision. Projects have constraints and pretending otherwise is not rigour, it is naivety.

The problem is not the shortcut. It is the shortcut nobody recorded.

Debt you took deliberately, wrote down, and scheduled is a tool. You know what you traded and roughly what reversing it costs. Debt that accumulated because nobody thought about it is a landmine for whoever touches that code in eight months — often you, having forgotten entirely.

A comment explaining why a shortcut exists and what the proper solution would be costs thirty seconds and saves someone half a day.

6. Communication Is the Actual Senior Skill

This is the one I resisted longest, because it sounded like the thing people say when they have stopped writing code.

It is not. The senior engineers I have learned the most from were not distinguished by writing cleverer code. They were distinguished by explaining a trade-off so that a non-technical stakeholder could make an informed decision, by writing a design document that surfaced a problem before anyone built the wrong thing, and by giving code review feedback that taught rather than just corrected.

Beyond a certain point your impact is bounded by what you can get other people to understand. Code you write alone has a ceiling. Decisions you help a team make correctly do not.

7. Ship Things End to End

If I could give one piece of advice to someone early in their career, it would be this.

Engineers who only ever build features in the middle of an existing system learn slowly. Not because the work is easy, but because they never experience the consequences of their own decisions. Someone else set up the deployment. Someone else handled the incident. Someone else maintained it after launch.

Build something small and own the whole thing — the data model, the deployment, the monitoring, the bug reports six months later. The feedback loop of living with your own choices teaches things that no amount of reading transfers.

That loop is the entire reason a portfolio of shipped work is worth more than a list of technologies. Anyone can learn a framework. Far fewer people have felt what happens when a decision they made in week one becomes a problem in month nine.

Where I Have Landed

Five years in, my working definition of a good engineer has shifted. It used to be someone who knew a lot. Now it is someone who consistently makes decisions that their future colleagues do not curse — who can tell an expensive decision from a cheap one, who writes down why, and who explains it well enough that others can build on it.

I am still working on all of it.

If you are hiring or building something and this way of thinking resonates, have a look at what I have shipped or say hello.

Frequently Asked Questions

Quick answers to the questions readers ask most.

Not knowledge of more frameworks. The clearest difference is how they handle uncertainty. A junior engineer picks a solution and starts building. A senior engineer identifies which decisions are expensive to reverse, spends disproportionate time on those, and moves quickly on everything else. Seniority is largely the ability to tell those two categories apart.

Track your estimates against actuals for three months. Most engineers discover a consistent personal multiplier — commonly somewhere between 1.5x and 3x — and simply applying that multiplier improves accuracy more than any estimation technique. The reason estimates fail is rarely the coding itself; it is the integration, the edge cases, the review cycle and the deployment, which people forget to include.

Be a generalist who is genuinely deep in one or two areas. Pure generalists struggle to demonstrate the depth that senior roles are hired for. Pure specialists are vulnerable when their specialty shifts. The combination — broad enough to own a whole system, deep enough to be the person others come to about something specific — is both the most useful and the most defensible.

Debt taken deliberately, written down, and scheduled is a legitimate tool. Debt taken accidentally because nobody thought about it is what hurts. The practical test is whether you can articulate what you traded and what it would cost to undo. If you can, it is a decision. If you cannot, it is an accident waiting to be discovered by whoever maintains the code next.

Ship things end to end, including deployment and the aftermath. Engineers who only ever write features in the middle of an existing system learn slowly, because they never see the consequences of their decisions. The feedback loop of building something, releasing it, and then maintaining it for six months teaches more than any number of tutorials.

Let's talk.

Building production-grade SaaS, AI agents and mobile apps end-to-end.

Hiring for a senior role or have an interesting problem to solve? Drop a note — I read every message.