Developing an Engineering Mindset
Learn to Stay With a Problem
One of the most valuable abilities you can develop as an engineer is the ability to remain with a problem even when the solution is not immediately visible.
Many people are comfortable while progress is obvious. The real difference appears when progress disappears.
You may spend an hour investigating something and still not understand what is happening.
You may test five hypotheses and discover that all five were wrong.
You may read documentation, inspect logs, change the implementation, go back to the previous version, and still have no final answer.
That is normal. Engineering does not reward you immediately.
You need to become comfortable with periods in which you are working seriously but cannot yet see the result. This does not mean repeating the same action for five hours without thinking. Persistence is not stubborn repetition. If an approach does not work, change the approach:
- Ask another question.
- Reduce the problem.
- Create a smaller reproduction.
- Inspect another layer.
- Read the source.
- Measure something.
- Remove assumptions.
The important thing is that you do not mentally collapse simply because the answer has not appeared yet.
A large part of engineering consists of working in a state where:
I know that something is wrong.
I do not yet know why.
You need to remain functional inside that state. That is a skill. And like every skill, it can be trained. If every difficult moment immediately results in:
- ask someone
- ask AI
- copy a solution
- abandon the problem
you remove one of the most important mechanisms through which engineering ability develops. Sometimes you need help. There is nothing wrong with that, but before asking for the answer, give yourself enough time to actually encounter the problem.
Form hypotheses. Be wrong. Investigate. Try again.
The objective is not to suffer unnecessarily.
The objective is to develop the ability to continue thinking when certainty disappears.
Your ability to remain functional while you do not yet understand the problem is one of the most valuable engineering skills you can develop.
Learn to Work With Uncertainty
Real engineering rarely gives you perfect information. In training exercises, the problem may be clearly defined. You may know:
- the exact input;
- the exact expected output;
- the constraints;
- the environment;
- what the system is supposed to do.
Real systems are often different. You may have:
- incomplete logs
- partial documentation
- unclear requirements
- unknown historical decisions
- missing reproduction steps
- multiple possible causes
And you still have to move forward. You cannot always wait until you know everything. Sometimes knowing everything is impossible. A good engineer learns how to reason with incomplete information. That means separating:
- what I know
- what I assume
- what I suspect
- what I can verify
- what I still need to discover
This separation is extremely important. One of the easiest ways to make a bad technical decision is to treat an assumption as a fact. Instead, create hypotheses. For example:
- The database may be slow.
- The network may be dropping requests.
- The application may be blocking.
- The cache may contain stale data.
Then test them. One by one.
A hypothesis is not a conclusion. It is a direction for investigation.
As information increases, your understanding changes. Your responsibility is to adapt with it. Engineering is not always:
Problem
↓
Known Solution
Very often it is:
Incomplete Problem
↓
Hypothesis
↓
Experiment
↓
New Information
↓
Better Hypothesis
↓
Solution
Train yourself to work inside that process. Because the more complex the system becomes, the less likely it is that every important fact will be available to you at the beginning.
Think Before You Act
One of the easiest habits to develop in programming is starting implementation too early:
- You receive a task.
- You open the editor.
- You create a package.
- You add a dependency.
You start writing functions. And only later do you discover that the actual problem was not properly understood. Writing code feels productive because something visible is happening. But visible activity is not automatically progress. Before implementation, ask:
- What exactly are we trying to solve?
- Why does this problem exist?
- What do we already know?
- What do we not know?
- What are the constraints?
- What is allowed to change?
- What must remain compatible?
- What happens when this fails?
- What is the simplest valid solution?
- What are we probably going to need later?
Sometimes five minutes of thinking prevents five hours of unnecessary implementation. Sometimes one diagram prevents an entire incorrect architecture. Sometimes asking one question changes the task completely.
This becomes more important as your responsibility grows, when you are:
- modifying one isolated function, a local mistake may be easy to fix.
- defining an API, database model, communication protocol, migration strategy, or service boundary, your decisions may affect many other parts of the system.
Implementation is important, but implementation should follow understanding.
Do not use planning as an excuse never to act. Overanalysis can become another form of avoidance. The objective is not to predict the entire future.
The objective is to understand enough of the problem that your first action has a reason behind it.
Do not start by asking what code you should write. Start by asking what problem the code is supposed to solve.
Remove Ambiguity From Your Technical Vocabulary
During both learning and professional work, remove vague and undefined expressions from your technical vocabulary whenever they are being used as a substitute for analysis. Expressions such as:
- maybe
- probably
- most likely
- it should
- it should probably
- possibly
- I think it will
- it should work
are not technical explanations.
If you do not know something, say that you do not know it. If something has not been verified, say that it has not been verified.
If there are several possible outcomes, identify them. If a conclusion depends on a condition, define the condition.
Instead of saying:
It should probably work.
say:
I have not verified this yet. Based on the current implementation, I expect it to work if conditions A and B are satisfied. I still need to test condition C.
Instead of:
Maybe the database is causing the problem.
say:
The database is one of three current hypotheses. I need to measure query latency and connection saturation before I can confirm or reject it.
Instead of:
This will most likely scale.
say:
We have tested this implementation up to 5,000 concurrent requests. Behavior beyond that point has not yet been measured.
There is nothing wrong with uncertainty.
Engineering is full of uncertainty. The problem is undefined uncertainty.
Do not hide a lack of information behind vague language. Make uncertainty explicit. Define what is known. Define what is unknown. Define what still needs to be tested. Technical communication should reduce ambiguity, not create more of it.
When someone says:
- maybe
- probably
- most likely
- it should
my next question is usually:
Based on what?
If there is no clear answer to that question, then the statement has very little technical value.
Train yourself to speak in terms of:
- known
- unknown
- verified
- unverified
- measured
- assumed
- expected
- required
- observed
The objective is not to sound more confident than you really are.
The objective is to be precise about the level of confidence you actually have.
Uncertainty is acceptable. Ambiguity is not.
Learn to Explain What You Know
Technical understanding and technical communication are closely connected. You may believe that you understand something because it feels familiar. But try to explain it clearly. Explain:
- how it works;
- why it works;
- what problem it solves;
- what assumptions it depends on;
- where it can fail;
- why you chose it over another approach.
Very often, the moment you attempt to explain something, you discover gaps in your own understanding. That is useful. Explanation forces structure. It forces you to turn:
I kind of understand this.
into:
I can describe exactly what is happening.
A strong engineer should be able to explain the same system at different levels. To another engineer, you may discuss:
- consistency
- latency
- failure modes
- locking
- replication
To someone less experienced, you may need a simpler explanation. That does not mean making the explanation incorrect. It means understanding the subject well enough to choose the right level of abstraction.
This skill becomes increasingly important as you progress:
- Senior engineers explain decisions.
- Architects explain systems.
- Mentors explain concepts.
- Teams explain failures.
- Documentation explains knowledge to people who may not even be present today.
If knowledge exists only inside your head, its value to the organization is limited.
Practice explaining your work. Write documentation. Describe your architecture. Explain why you rejected an alternative. Teach someone else. A useful test is:
Can I explain this clearly without hiding behind terminology?
If the answer is no, investigate whether you actually understand it as well as you think.
Draw the Model Before You Implement It
Before writing any serious model, draw it.
Before creating structures, interfaces, database entities, relationships, services, communication flows, or larger architectural components, first create a diagram or at least a rough sketch.
It does not need to be beautiful. It does not need to be created with professional diagramming software. A piece of paper is enough.
The important thing is that the idea exists visually before implementation begins. For example:
Client
↓
Gateway
|
+------> Service A
|
+------> Service B
↓
Database
Or for a data model:
User
|
+---- has many ----> Project
|
+---- contains ----> Task
The purpose of the diagram is not decoration. The purpose is to force you to answer questions before code starts hiding the weaknesses in your idea.
A diagram immediately exposes things such as:
- missing relationships;
- unclear ownership;
- incorrect dependencies;
- circular dependencies;
- missing data flows;
- undefined responsibilities;
- unnecessary components;
- unclear boundaries;
- inconsistent cardinality;
- missing failure paths.
Code can create the illusion of progress. A diagram forces you to look at the whole thing.
Personally, if someone comes to me with a serious model and cannot show me even a basic diagram or sketch, I do not want to hear the implementation yet. For me, that immediately raises several questions:
- Do you actually know what the final structure should look like?
- Have you thought through the relationships?
- Do you understand where the boundaries are?
- Have you analyzed how information moves through the system?
- Can you explain the model clearly to another person?
- Or did you simply start implementing and allow the model to emerge accidentally?
If you cannot draw the idea, there is a good chance that the idea is not yet sufficiently developed. That does not mean the first diagram must be correct. It probably will not be.
That is exactly why you draw it before implementation:
- Change the boxes
- Move the arrows
- Remove components
- Add missing relationships
- Destroy the first version
- Draw another one
Doing that on paper takes minutes. Doing the same thing after thousands of lines of implementation may take days or weeks. The diagram is also one of the simplest tests of whether you can communicate your idea.
If you need twenty minutes of verbal explanation before another engineer can understand where the components are and how they interact, the model may not yet be clear enough. A good diagram gives the conversation a common starting point. You can:
- point to a component.
- question a relationship.
- identify a missing dependency.
- discuss the same system instead of two different mental interpretations of it.
The workflow should therefore be:
Problem
↓
Analysis
↓
Sketch
↓
Diagram
↓
Review
↓
Model
↓
Implementation
not:
Problem
↓
Start Coding
↓
Discover the Architecture While Implementing
There are cases where small, trivial changes do not require formal modeling. Use judgment, but as soon as you are designing something with meaningful relationships, responsibilities, states, dependencies, or data flow, draw it first.
If you cannot draw the model, you are probably not ready to implement the model.
Review Your Own Decisions
Experience alone does not automatically create good judgment. You can repeat the same mistake for ten years and call it ten years of experience. What matters is whether you learn from what happened. One of the best ways to improve engineering intuition is to review your own decisions.
When making an important decision, record:
- Problem
- Assumptions
- Available Information
- Decision
- Alternatives
- Expected Result
Then return to it later and add:
- Actual Result
- What Was Correct
- What Was Wrong
- What I Would Change
- What I Learned
This can be extremely valuable. Imagine that you selected a specific database architecture because you expected a certain traffic pattern. Six months later, the traffic pattern is different and the design causes problems. You can simply fix it and move on.
Or you can ask:
Why did I believe this was the right choice six months ago?
Use cases can be:
- your reasoning was good and reality changed.
- the data was incomplete.
- you ignored an important signal.
- you overestimated one risk and underestimated another.
Those distinctions matter. The objective is not to judge your past self unfairly. You made the decision with the information you had at the time.
The objective is to improve the process through which future decisions are made. This is one of the ways intuition becomes stronger. Intuition is not only created by experiencing many situations. It is strengthened by reflecting on those situations afterward.
If you never review your reasoning, you may remember the result but forget why you arrived there.
Experience gives you events. Reflection turns those events into judgment.
Separate Your Ego From Your Solution
Your code is not you. Your architecture is not you. Your idea is not you.
This sounds simple, but it is extremely important. You will make bad decisions. Everyone does. Someone less experienced than you may occasionally have a better idea. Someone may find a serious problem in something you designed. A code review may completely reject your approach. That should not become a personal conflict. If your objective is to create the best possible system, then discovering that your current solution is wrong is useful information.
The problem begins when the objective changes from:
Find the best solution.
to:
Prove that my solution was correct.
At that point, engineering becomes ego defense. You start ignoring evidence. You interpret criticism of the implementation as criticism of yourself. You defend complexity because you created it. You resist removing code because you spent three weeks writing it. That is dangerous.
A strong engineer should be able to say: "I was wrong" and continue working normally.
That sentence does not reduce your value.
Very often, the opposite is true.
The ability to change your opinion when better evidence appears is a sign of technical maturity.
You should defend ideas with reasoning, but once the reasoning no longer holds, let the idea go.
The objective is not to win an argument. The objective is to improve the system.
Do Not Confuse Speed With Progress
Software engineering has an unhealthy relationship with visible activity:
- More commits.
- More lines of code.
- More tickets closed.
- More generated output.
- More features.
- More speed.
Those numbers can mean something, but they can also mean almost nothing.
Imagine two engineers. The first engineer writes 5,000 lines of code in two days. The second spends one day analyzing the problem and writes 300 lines on the second day.
Who made more progress?
You cannot answer that from the numbers. The 5,000 lines can create unnecessary complexity. The 300 lines can solve the problem completely. You have to inspect the result. This becomes especially important with AI. If a model generates:
10,000 lines in ten minutes
that is an impressive generation rate. It does not automatically mean:
10,000 lines of engineering value
Generated output still needs to fit the system:
- It needs to be correct
- Maintainable
- Secure
- Testable
- Compatible
- Useful
Speed is valuable when you are moving in the right direction. Moving quickly in the wrong direction only increases the distance you later need to travel back. Do not measure your development only through visible volume. Sometimes:
- deleting 2,000 lines is progress.
- spending a day without writing code is progress.
- discovering that a feature should not exist is progress.
Remember:
activity ≠ progress
output ≠ value
speed ≠ quality
Train Observation
A large part of engineering begins with noticing something:
- strange line in a log.
- small latency increase.
- function that has a name that does not match what it actually does.
- dependency that seems to point in the wrong direction.
- recurring failure every few hours.
- memory pattern that looks slightly unusual.
- database query that should not be executed that often.
None of those things may initially look like a major problem, but someone has to notice them.
Good engineers develop observation.
They do not only wait for SYSTEM FAILED before becoming interested.
They notice smaller signals.
Observation leads to questions:
- Why is this happening?
- Has it always behaved this way?
- Why does this service depend on that service?
- Why is this request slower only sometimes?
- Why does memory usage increase but never return?
Questions lead to investigation. Investigation produces understanding.
This is also strongly connected with intuition. You cannot build intuition about systems if you do not pay attention to how systems behave.
Train yourself to observe:
- Read logs even when nothing is broken
- Look at metrics
- Inspect generated files
- Watch network traffic
- Read code you did not write
Compare expected behavior with actual behavior. Pay attention to things that seem slightly inconsistent. You do not need to investigate every unusual detail forever, but you should develop the habit of noticing them. Sometimes the first sign of a serious problem is simply:
Something here does not look right.
Create More Than You Consume
There is an enormous amount of technical content available today:
- courses
- videos
- books
- articles
- documentation
- tutorials
- AI explanations
All of them can be useful, but there is a danger in consuming technical information continuously without creating anything yourself.
Watching someone build a system creates familiarity.
Building the system yourself exposes understanding.
Those are not the same thing. You may watch a two-hour tutorial and understand every step. Then close the tutorial. Open an empty directory. And discover that you do not know where to begin. That is valuable information. Creation exposes the gaps that passive consumption can hide.
Instead of only reading about:
- networking
- databases
- concurrency
- Git
- Linux
- APIs
build something with them:
- Write a small server
- Create a protocol
- Break it
- Measure it
- Document it
- Delete it
- Build another version
If you are learning an algorithm, do not only read the solution.
Implement it. Change the constraints. Create new form.
Create another problem that requires the same idea. If you are learning architecture, design a system yourself before reading someone else’s architecture. Then compare.
Technical education should not become endless consumption. At some point, close the material and create something from nothing. That is where understanding becomes visible.
Consumption creates familiarity. Creation exposes understanding.
The purpose of learning is not to collect information.
The purpose is to become capable of doing something with it.
Learn to Finish
During the learning process, a crucial factor for progress is gradation:
- Starting something is easy.
- Finishing it is a different skill.
- The beginning of a project is exciting.
- You choose technologies.
- You create architecture.
- You write the first clean implementation.
Everything is new. Then comes the part that is usually less exciting:
- edge cases
- tests
- error handling
- migration
- documentation
- cleanup
- deployment
- monitoring
- performance problems
- compatibility
- bug fixing
- maintenance
This is where many projects become difficult and this is where a large part of engineering actually happens:
- feature is not finished because the primary happy path works.
- service is not finished because it starts successfully on your machine.
- system is not finished because the architecture diagram looks good.
You have to ask:
- Can it fail safely?
- Can we deploy it?
- Can we upgrade it?
- Can we monitor it?
- Can someone else understand it?
- Can we recover it?
- Can we maintain it six months later?
People who constantly move from one new idea to another can accumulate enormous amounts of beginnings and very little completed work. That creates familiarity with starting. It does not create experience with the complete lifecycle.
Finishing teaches you things that starting never will. It:
- exposes assumptions
- reveals edge cases
- forces compromises
- introduces maintenance
- teaches responsibility
Starting teaches enthusiasm. Finishing teaches engineering.
Learn to finish what deserves to be finished.
An Idea Without Execution Is Just Another Illusion
Ideas are easy to produce. Execution is what gives them value.
You can imagine:
- a new product;
- a better architecture;
- a new service;
- a business;
- a platform;
- a library;
- a completely different way of solving a problem.
But until you execute the idea, it exists only in your head. It has not:
- been tested
- met reality
nor been forced to deal with:
- constraints
- time
- cost
- failure
- complexity
- people
- maintenance
- unexpected behavior
That is why I do not place much value on an idea by itself. An idea can sound brilliant while it remains theoretical. Execution is the point where the idea is forced to prove itself. Reality starts asking questions:
- Does it actually work?
- Can it be built?
- Can it be maintained?
- Can someone else understand it?
- Does it solve the problem you thought it solved?
- Is the complexity justified?
- What happens when the assumptions are wrong?
Until those questions are answered, you do not really know what you have. You have a possibility. Not a result.
There is also a psychological trap here:
- Thinking about an idea can create a feeling of progress.
- Talking about it can create a feeling of progress.
- Drawing the architecture can create a feeling of progress.
- Explaining what you are going to build can create a feeling of progress.
But none of those things are the execution itself. At some point, you have to build. You have to test, fail, correct, finish. That is where the idea becomes real.
This is also why unfinished projects are not only unfinished products. They are unfinished lessons. If you:
- stop before deployment, you do not learn deployment.
- if you stop before real usage, you do not learn what users actually do.
- if you stop before maintenance, you do not learn what your decisions cost over time.
- if you stop before failure, you do not learn whether your recovery strategy was real or theoretical.
Execution closes the loop between imagination and reality. Without that loop, you can spend years believing that your ideas are better than they actually are, simply because they were never tested hard enough to prove otherwise.
An idea without execution is just another illusion.
The value is not in having the idea. The value is in turning the idea into something that survives contact with reality.