I started as “the IT guy” – “Ivan, do everything.” DevOps engineer and platform engineer were not common job titles back then. To me, the engineering world looked simple: someone brought you a technical problem, you found the right tool and built “what they needed.”
As I gained experience, I saw teams make their most expensive mistakes before anyone wrote code. We misunderstood the problem, agreed on different things, picked an elegant or fashionable solution, or used one strong specialist to compensate for badly organized work.
I paid for each of these lessons with time, rework, and stress.
1. A problem that looks technical often starts with people misunderstanding each other
People can sit in the same meeting and leave with different versions of the project in their heads. This mismatch affects everything, from strategy to small implementation choices.
You’ve probably seen it. Everyone “approved” the decision: nods on a call, green check marks in chat. But a few days later, you find out that each person approved a different version of the decision, and now the team has to redo a lot of work.
Then we blame “bad implementation”, even though the failure happened before the code. Nobody stopped to restate the task in their own words. I spent too long treating all of this as a normal part of IT work. A disaster.
Now I pay close attention to what my colleagues mean, not only to the words they use. The words matter, but the meaning behind them matters more. First, I notice when I don’t understand something. Then I watch for the same desync between other people working in the same “process”.
Trying to track every word and reverse-engineer other people’s worldviews is exhausting, but it saves much more time than it costs.
2. Don’t choose architecture because it is fashionable or intellectually attractive
This applies to more than architecture. Any technology choice based on emotion or “tribal” loyalty is embarrassing.
I like X because Y is bad. I refuse to use Y because I once saw it cause trouble. I will use X because I like how it feels.
Or: we will build it “this way” because Google, Netflix, or whoever does it this way. That last one is a disgrace.
I will never forget those distributed monoliths running in Kubernetes clusters for a business whose systems would never need to handle more than 200–300 RPS. The engineers spent their time maintaining complexity they had created instead of solving actual business problems.
3. Collecting frameworks does not replace learning how to think
Six or seven years ago, I kept thinking: “There is so much interesting and useful stuff!”
Programming languages, databases, methodologies, and more, more, more. You could spend your whole life trying all of it.
Thanks to my mentor, I didn’t waste too much time on this (but I still wish I had realized it earlier and saved even more time). His rule was almost literal: “Either you study with me, or… farewell.”
The itch for shiny new things disappeared only after I saw how much investing in better ways of thinking paid off. I started building a “type machine” in my head instead of collecting another stack.
When juniors ask, “Which programming language should I learn?”, experienced programmers often answer: “Don’t learn a language. Learn to program.”
The same rule applies one level above. Software engineering books, functional programming, and relevant mathematics changed how I think and make decisions much more than hundreds of random tutorials from the internet.
Of course I still use all those nice tools. How else would I work? But they stopped being the goal and stopped giving me a fake sense of progress.
4. Strong specialists cannot compensate for badly designed teamwork
I used to believe the opposite. Strong specialists can carry everything for a while. In a large production system, that arrangement does not last.
I learned this firsthand. I wasn’t a manager watching from the outside. I was the person people tried to put into the “Terminator” role, and my mistake was accepting it with enthusiasm.
At first, it feels great. People line up for your help. Everyone else waits. You keep ten different work contexts in your head at once. There are almost no proper handoffs, but “you’ll figure it out, you’re a smart guy!” comes from every direction.
My burnout did not come from “difficult code”. It came from too much work flowing through one person.
The bottleneck is you. Or the other dude. It doesn’t matter. This is a biological limit, and brute force will not fix it: one person cannot carry that many contexts without losing quality.
Without organized work, an overloaded specialist will burn out and become ineffective. It is only a matter of time.
It is much easier to find a good mid-level or senior developer, DevOps engineer, or QA engineer than to find T-shaped unicorns who can carry the whole system on their backs until they drop.
5. I learned too late to ask whether the task even exists
A loud, confident “we need this” can sound like a fact. Especially when someone says: “We needed it yesterday! Management is pushing me!”
We need a new service. We need to rewrite the old one. We need to automate X. We also need to add AI, remember?
I took part in this myself. I think most of us have.
Then, “suddenly” (and far too often), the team finds out that the original problem was poorly defined, or that we could have solved it with a tenth of the effort.
Sometimes the problem existed only in someone’s head, and nobody stopped to ask: What outcome do we want in the real world? Why do we think this work will produce it? Why do we need to do it right now?
Few things feel worse for the engineer who built “it”. You spend time and energy, and the system you created is useless. Nobody uses it. Nobody ever needed it.
And yet it got built. Few people will admit: “Sorry, we failed at analysis, sales, marketing, or organizing the work.”
The best code is the code you never had to write. To avoid writing it, you need to stop everyone from rushing into implementation and dig into the reasons behind that loud “we need this.”
This takes more skill than cranking out code, especially now that AI agents can crank it out much, much faster.
I have seen smart people solve nonexistent problems too many times.
A team that never checks whether everyone shares the same understanding may build a good solution to an imaginary problem.
What I check before a major technical decision
1. Do we understand the current situation in the same way?
If you ask everyone to explain the task in their own words, do they describe the same reality? Do we understand each other’s words, or are we talking about different things?
2. What criteria are we using to choose a solution?
What do we have besides taste, habit, “large companies do it”, or “someone wrote it on Twitter”?
3. What methods are we using to think about the problem?
Are we studying the system or reaching for the tools we already know? Are we choosing a method, or working “intuitively”?
4. Can this project survive without two irreplaceable Terminators?
Can we find people for the required roles when we need them? Is the work organized? Does everyone understand their responsibilities? Have we covered every role needed to keep the project moving?
5. What outcome do we want, and do we need this specific work to get it?
If we cannot answer one of these questions, we cannot nod and move on. We need to dig into what people want, check our assumptions, and define the problem.
Sometimes there is nothing left to implement. More often, the solution becomes much simpler than it looked at the start.
And yes, writing zero lines of code can be a successful engineering result.