The Difference Between Solving and Removing a Problem
Human beings are naturally good at solving problems—that's how we've survived this long. But we're also naturally good at finding problems, whether they exist yet or not. It takes real intentionality to avoid that tendency, especially when you spend most of your working life solving problems deliberately.

Take a real example: a family member once asked for help building an app that combined Zoom, Tito, and Google Calendar into one product, so that musicians sidelined during the pandemic could teach remotely. She was describing a solution in search of a problem. The better move was simply to use the three existing tools, and only consider building something custom when those tools actually failed her. At that point, she'd have real experience with the problem and a much better foundation for solving it.
Ultimately, she dropped the project—which was fine, because she never had to solve problems that only existed in her imagination. Many of us have done the opposite at some point. (How many tests have been written for code that was deleted before it ever landed in a commit?) The best possible outcome is not a clever solution; it's that the problem never gets a chance to manifest. That doesn't mean you can't plan ahead—it just means avoiding "painting yourself into a corner" is always preferable to fixing the corner later.
Avoiding problems is better than solving them.
Solving vs. Eliminating: Why the Distinction Matters
Sometimes avoidance is impossible. At that point, engineers naturally jump into solution mode—it feels productive. But a solved problem demands ongoing maintenance. An eliminated problem simply ceases to exist, freeing you from upkeep and letting you focus on other work.
When you solve a problem, you get a solution you must maintain. When you eliminate a problem, you don't have to think about it at all.
Problem Elimination in Hardware and Manufacturing

Tesla's all-electric platform is a textbook example. By completely removing the internal combustion engine and its supporting systems, they've eliminated decades of standard parts and assembly requirements. That reduction in complexity lets them concentrate on the problems their new approach introduces. It also changes the ownership experience: an EV owner rarely worries about oil changes, transmission failure, or brake pad wear. Fewer parts mean fewer failure points, with all the maintenance burden that comes with them.

Their Gigapress takes this further. A single-piece casting replaces entire sections of stamped steel parts that dozens of robots must bolt and weld together. Again, the strategy isn't to build better welding robots—it's to eliminate the seams that made welding necessary. This approach separates Tesla's manufacturing strategy from long-standing industry practice, and it's worth studying regardless of your industry.
Eliminating Problems in Code

More familiar territory: React development before hooks required writing classes that extend React.Component, scattering related logic across constructor, componentDidMount, componentDidUpdate, componentWillUnmount, and render. Grouping logic into a single, reusable abstraction wasn't straightforward when that logic cut across multiple lifecycle methods.
Chasing code reuse led to patterns like Higher Order Components and Render Props. These worked, but they introduced layers of nesting, awkward typing, and indirection. They were solutions—not eliminations.
Hooks changed that: group code into a useWhatever function, just like you'd share any JavaScript code. The pain that motivated HOCs and render props was eliminated, not merely patched.
Another example: pre-context React made prop drilling a genuine pain. State had to be piped through every intermediate component to reach the ones that needed it. The official React docs cautioned against using the context API, so many reached for Redux, which relied on context under the hood. When context became officially supported—and especially when hooks made it comfortable to use—many teams found the original motivation for pulling in Redux simply gone.
(Redux still serves other purposes for some teams, but the prop-drilling problem that drove initial adoption has largely been eliminated.)

Remix, as a React metaframework, is another solid example of problem elimination:
- Nested routing eliminates shared layout problems that other frameworks leave to the developer.
- Direct response cache headers deliver static-site benefits without requiring complex incremental rebuild logic.
- Server
loaderfunctions sit right next to the component file. You filter server-side before sending data, so over-fetching is gone. Remix also fetches only for the changed layouts during a page transition—a benefit that depends on nested routing. - First-class
<form>support removes the need for elaborate form state handling; the<Form>component provides the same semantics under client-side routing. - Loaders are re-run automatically when data gets changed, meaning cache invalidation isn't something you need to handle.
- Page-specific
linktags prevent CSS on one route from affecting anything else—no need for a CSS-in-JS approach to solve that problem, because it doesn't arise. - Progressive enhancement is the default, so applications still work even on an unreliable network or if JavaScript loading breaks.
Remix focuses primarily on standard web APIs rather than establishing its own abstractions. This has dramatically cut its documentation volume (comparisons to MDN fill the gaps) and flattened the learning curve. Skills built while working in Remix—writing <form> tags, understanding the web’s core APIs—help you write plain HTML later on.
The Trade-Off: Bigger Problems for Smaller Ones
It's natural to note that every designed system introduces fresh problems, and this holds for any alternative—including doing nothing at all. The goal is not to reach zero friction, but to trade durable, systematic pain for smaller, cheaper-to-address friction points. EV owners trade one set of maintenance issues for longer refueling times. Hooks overcome class-based verbosity but introduce learning about value identity and memoization. Each refactoring brings in its own sharper pains, usually easier to handle than their predecessors.
Eliminate big problems in exchange for smaller ones.
A Better Instinct for Problem-Solving
As a culture, we're wired to celebrate solving hard technical challenges—managing complexity, fixing tricky bugs, and shipping clever workarounds. But the lasting value comes from removing whole classes of friction so the complexity and bugs never surface.
Instead of regarding every friction point as an invitation to code, try the detour: Start by not seeking problems to solve. If an honest problem exists, look first for ways to remove it; turn to solving it only when elimination proves impossible. Approach improvements with the goal of steadily shrinking the territory where problems can grow.



