The Defaults I Would Learn First as a Software Engineer
Every roadmap for learning software engineering is the same technology list in a different order, and it tells you nothing about what to do when you are sitting in front of an unclear requirement on a Wednesday afternoon. Fourteen situations you will keep meeting, paired with the move that is usually right, from prototyping a vague requirement to blaming the system rather than the person.
Tauseef Fayyaz

Why heuristics and not a syllabus
Every roadmap for learning software engineering is the same list of technologies in a different order, and it fails the same way: it tells you what to study and nothing about what to do when you are sitting in front of a problem at four in the afternoon with an unclear requirement and a deadline on Friday.
What experienced engineers actually carry around is smaller than a syllabus. It is a set of pattern matches. A situation shows up, it rhymes with a hundred previous ones, and a default action comes to mind. That is all a heuristic is: a condition you can recognise, paired with the move that is usually right.
Below are fourteen of them, in the order you tend to need them. They are defaults, not laws. The right answer always depends on your actual constraints, and the value of knowing a default is that it gives you something specific to argue against.
Before you write any code
1. Vague requirement + a deadline → a prototype. Do not try to think your way to clarity in a document. Build the smallest possible fake version, show it to whoever asked, and watch them react. Nothing extracts a real requirement faster than someone looking at the wrong thing and saying so.
2. A decision + a reason you will forget → write down the why. Six months from now someone will find your odd choice and assume it was a mistake, and there is a fair chance that someone is you. A short note next to the code, or a paragraph in the pull request, costs two minutes and saves an afternoon of archaeology.
3. A new problem + excitement about a new tool → choose the boring one. Novelty is a cost you pay in bugs nobody has hit yet, documentation nobody has written and answers nobody has posted. Spend your limited appetite for novelty on the part of the system that is genuinely new, and let everything else be dull and well understood.
While you are writing it
4. Done by hand + more than twice → write a script. The third repetition is the signal. Not because the time adds up, though it does, but because a manual sequence is a sequence a tired person will eventually get wrong, and the wrong version is silent.
5. Two callers + the same logic → wait for the third. Two similar pieces of code often turn out to be different pieces of code that currently look alike. Deduplicate too early and you build an abstraction with a flag in it, then another flag, and the thing becomes harder to read than the duplication ever was.
6. Confusion + a name → rename it. When you keep having to explain what a variable holds, the name is wrong. Renaming is the cheapest refactor there is and the one with the best return, because every future reader pays the cost of a bad name and none of them will fix it.
7. Data + a schema → decide it before anything else. Application code can be rewritten in a weekend. Data cannot. Your schema is the one decision that hardens with every row inserted, so give it the disproportionate attention it deserves before there are a million rows shaped by it.
Making it safe to change
8. Changing code + fear → write tests. Fear is diagnostic. It means you cannot predict what your change will break, and no amount of care substitutes for that knowledge. Tests are not about correctness so much as about permission: they are what makes you willing to touch the thing at all.
9. A bug + a fix → the failing test comes first. Reproduce it in a test, watch it fail, then fix it. This proves you actually understood the bug rather than perturbing the code until the symptom moved, and it stops the same bug from arriving again in eight months wearing a different hat.
10. Hard to test + a design question → the design is the answer. Code that needs six mocks to instantiate is telling you it does too much or reaches too far. The difficulty of the test is a measurement of the design, and listening to it will teach you more about structure than any book on architecture.
Once it runs for real
11. Slow + a guess → measure it. Everyone's intuition about performance is wrong, including the intuition of people who are usually right. Profile before optimising, or you will spend a day making the fast part faster while the actual problem sits in a query nobody looked at.
12. Production + silence → logs, metrics and traces. A system you cannot observe is a system you debug by redeploying with print statements while users wait. Decide what you would want to know during an incident and emit it now, because during the incident is the worst possible time to discover you are blind.
13. An outage + a cause → a blameless postmortem. Ask what made the mistake easy rather than who made it. Every serious failure has a systems answer underneath the human one, and teams that go looking for the person instead of the system get quieter engineers and identical outages.
14. Stuck + an hour gone → ask. Set the limit in advance, because in the moment you will always feel like you are five minutes from the answer. Nobody has ever been judged for asking at sixty minutes. Plenty of people have been judged for asking on Thursday about something that blocked them since Monday.
What else belongs on this list
A few that nearly made it, and are worth arguing about. Keeping pull requests small, because a four hundred line diff gets approved and a forty line diff gets read, which makes the size of the thing you ask someone to hold in their head close to the only reliable lever you have on review quality. Deleting code, because removing a feature nobody uses is real work with real value and almost nobody counts it. Saying no, or more precisely saying not this quarter, which is a technical skill dressed up as a social one. Estimation, which you will be bad at forever, so learn to give ranges and to say what would make the range narrower.
And the one that underpins all of it: reading code. You will read far more than you write, most of it unfamiliar and much of it bad, and the ability to open a large unfamiliar codebase and find the four files that matter is close to the whole job.
The short version
Prototype when the requirement is vague. Automate the third repetition. Write down why. Choose boring tools. Let duplication live until the third caller. Treat the schema as permanent. Use fear as a signal to test, and difficulty of testing as a signal to redesign. Measure before optimising. Make the system observable before you need it. Blame the system, not the person. Ask after an hour.
None of this is exotic, and that is rather the point. The gap between an engineer in their first year and one in their eighth is not usually knowledge of some deep topic. It is that the second one recognises the situation faster and reaches for the boring correct move without having to think about it.
Comments (0)
Comments are closed for now.
No comments yet.
Stuck on something specific?
Writing only gets you so far. If you want an answer to your situation rather than the general case, book a session and we will work through it together. Every session is free; a few slots open each week.
Follow along
New writing, resources and project ideas land here first.