🎯 Chapter Insight
Some problems feel impossible because the solution space has been made too small before we even begin.
We accept assumptions as if they were facts. We inherit restrictions from earlier decisions. We treat existing architecture, process, or convention as fixed simply because nobody has challenged it.
Then we try to solve the problem inside those boundaries.
And sometimes there is no solution there.
Pragmatic developers learn to distinguish between real constraints and assumed constraints.
A real constraint might come from regulation, physics, an external contract, or a genuine business requirement.
An assumed constraint sounds different:
“We have always done it this way.”
“This component cannot change.”
“The integration has to work exactly like this.”
“We only have these two options.”
Those statements may be true.
But they may also be assumptions disguised as requirements.
When a problem appears impossible, the most useful question is sometimes not:
“How do I solve this?”
but:
“Why am I assuming I have to solve it this way?”
💡 Developer Lens
Software development is full of invisible boundaries.
Imagine a feature request that appears technically impossible within the existing architecture.
The initial response may be to search for increasingly complicated workarounds.
More abstractions.
More conditionals.
More background jobs.
More state synchronization.
More exceptions to existing rules.
Eventually the solution becomes so complicated that the difficulty of the implementation seems to confirm that the original problem really was hard.
But perhaps the problem was not the feature.
Perhaps the problem was one assumption about how the feature had to fit into the existing system.
What if a workflow could change?
What if a component could expose a different interface?
What if a requirement applied only to some users rather than everyone?
What if an operation did not actually need to happen synchronously?
What if the data did not need to live where everyone assumed it did?
The moment one false constraint disappears, an apparently impossible problem can become surprisingly ordinary.
Constraints Shape the Solution Space
Every constraint removes possibilities.
That is not necessarily bad.
Constraints are essential to engineering. They define what a solution must respect and prevent us from exploring options that are not viable.
The problem begins when we confuse assumptions with constraints.
Suppose someone says:
“The new service must use the existing database.”
Why?
Perhaps there is a real operational requirement.
Perhaps transactions need to span both systems.
Perhaps regulatory controls prohibit another storage mechanism.
Or perhaps the existing database was simply used by every previous service, so everyone assumed the new one should use it too.
Those situations are completely different.
If the limitation is genuine, it belongs in the design.
If it is merely inherited habit, it may be unnecessarily restricting the solution.
Good problem solving therefore begins by understanding which boundaries are actually fixed.
“Impossible” Often Means “Impossible Under These Assumptions”
This distinction matters far beyond architecture.
Consider a deadline.
Someone might say:
“We cannot deliver this feature by Friday.”
That may be completely true if all current assumptions remain in place.
But what exactly is fixed?
Must every capability ship together?
Must the first version support every edge case?
Must all integrations be available immediately?
Could the feature be delivered incrementally?
Could the scope change while still solving the most important user problem?
Questioning constraints does not mean pretending deadlines or requirements do not matter.
It means understanding exactly what they constrain.
Sometimes the impossible deadline remains impossible.
But sometimes the real requirement is much smaller than the assumed one.
The same applies to performance, integrations, legacy systems, infrastructure, and product behaviour.
A problem may genuinely be impossible under the current set of constraints.
That does not automatically mean all of those constraints are real.
Existing Systems Create Psychological Constraints
Legacy systems are particularly good at generating invisible assumptions.
A certain module has worked one way for ten years, so developers begin to treat that behaviour as fundamental.
A database schema is awkward, but nobody changes it because too many things depend on it.
A manual process exists outside the application, so new features are designed around that process rather than questioning whether the process should still exist.
Over time, historical decisions begin to look like natural laws.
They are not.
They are decisions.
Some may still be correct. Some may be expensive to change. Some may have dependencies that make changing them impractical.
But those are engineering considerations, not reasons to stop asking questions.
There is an important difference between:
“We cannot change this.”
and:
“Changing this would cost more than the problem is worth.”
The second statement is a decision based on trade-offs.
The first closes the conversation entirely.
Pragmatic developers prefer understanding over unquestioned permanence.
Reframe the Problem
When you are stuck, changing the framing can be more valuable than spending another hour inside the existing one.
Suppose you are trying to make an operation significantly faster.
The obvious question is:
“How can we execute this operation faster?”
But other questions may create better possibilities:
“Why are we performing this operation so often?”
“Does it need to happen at all?”
“Can the result be computed earlier?”
“Can we process less data?”
“Does the user need the exact result immediately?”
Each question challenges a different assumption.
The solution may turn out not to be optimization.
It may be removing the work.
This connects to a broader pragmatic principle: solving the right problem is more valuable than optimising the wrong solution.
Look for Degrees of Freedom
When a puzzle appears impossible, identify where you still have freedom.
You may not control an external API, but you can control how your application interacts with it.
You may not control a legal requirement, but you may control where enforcement happens.
You may not control a deadline, but you may control scope.
You may not control a legacy database, but you may control the abstraction around it.
You may not control user behaviour, but you may control how safely the application responds to it.
Problem solving becomes easier when you stop viewing constraints as one large wall and start examining them individually.
Some are solid.
Some can move.
Some disappear completely once questioned.
Those movable boundaries are where new solutions emerge.
Be Careful Not to Question Everything
There is also a trap in the opposite direction.
Questioning assumptions does not mean endlessly reopening every decision.
Engineering still requires progress.
Some constraints have already been investigated and deliberately accepted. Some alternatives are technically possible but economically unreasonable. Some restrictions exist because removing them would create greater problems elsewhere.
The goal is not perpetual debate.
The goal is conscious constraint.
Understand why a boundary exists.
If it is real, design around it.
If it is a deliberate trade-off, acknowledge the trade-off.
If it is only an assumption, challenge it.
That distinction turns constraint analysis into a practical engineering tool rather than an excuse for endless redesign.
“Why?” Is a Powerful Debugging Tool for Problems
Developers often use “why?” while debugging code.
Why is this value null?
Why was this branch executed?
Why did this request happen twice?
The same technique works for apparently impossible engineering problems.
“We cannot change this interface.”
Why?
“Because another system depends on it.”
Does that dependency require the exact interface?
Could we preserve compatibility while adding another path?
Could an adapter isolate the change?
Suddenly the conversation becomes more precise.
Each answer either confirms a real constraint or reveals another assumption worth examining.
The goal is not to argue with every limitation.
It is to understand where it actually comes from.
🧭 Reflection
Think about a technical problem that currently feels difficult or impossible.
What constraints surround it?
Which ones come from genuine requirements?
Which come from technology?
Which come from cost or time?
And which ones exist simply because that is how the system currently works?
Have you accepted a solution space that is smaller than it needs to be?
Would the problem change if just one assumption disappeared?
Sometimes creativity in engineering is not about inventing a brilliant solution.
It is about noticing that one of the walls around the problem was never really there.
⚙️ Practical Tip
Take one problem you are currently stuck on and write down every constraint surrounding it.
Do not evaluate them yet. Just list them.
Then classify each one:
- Fact: something you genuinely cannot change
- Requirement: something the solution must satisfy
- Trade-off: something you could change, but at a cost
- Assumption: something being treated as fixed without a clear reason
Now choose one assumption.
Temporarily remove it.
Ask yourself:
“If this were no longer true, what solutions would become possible?”
You do not necessarily have to adopt one of those solutions.
The exercise itself is valuable because it expands the problem space.
And sometimes the new solution reveals that the “impossible” problem was only impossible inside boundaries nobody had thought to question.
Challenge the constraints before accepting the impossibility.
🔢 #46 of 53 | The Pragmatic Programmer Series
This post is part of my 53-week series summarizing The Pragmatic Programmer, one timeless principle each week, translated into modern software practice and reflection.








