Back to writing

Sometimes the Gross Solution Is the Right One

June 9, 2026

The right engineering decision is not always the cleanest one. Sometimes the safer move is to contain the mess instead of triggering a risky rewrite.

We recently needed a second app that reused parts of our React admin.

The "correct" solution was obvious.

  • extract the shared code into a package
  • separate the apps cleanly
  • define proper boundaries

We did not do that.

Instead, we put two apps behind a switch inside the same main.tsx. A server flag decides which one to render.

It is gross. We knew it was gross. We shipped it anyway.

And it was the right call.

The problem was not just the code

At the time, extracting the shared parts was not cheap.

The code was not structured for it.

  • shared logic depended on app-level singletons
  • auth, analytics, HTTP client, and session were all global
  • there were no clear injection boundaries

Pulling things out properly would have meant redesigning how half the app worked.

That is not an extraction.

That is a rewrite.

So we did not do it.

We took the shortcut, shipped the second app, and moved on.

Then the situation changed

Later, we actually needed real separation.

A new app. More shared surface. More reason to invest.

This time, the investment made sense.

But here was the catch.

The system we were extracting from was already shaped by the earlier decision.

The singletons were everywhere.

The coupling was real.

And now we had a choice.

The clean solution we did not take

We could have removed the singletons, introduced proper dependency injection, moved things behind providers, and redesigned the infra layer.

That would have looked more correct.

It also would have been:

  • slow
  • risky
  • hard to validate incrementally
  • likely to break subtle assumptions

In other words, high blast radius.

What we did instead

We kept the pattern.

But we contained it.

We introduced an admin-infra package that defined clean interfaces for things like auth, analytics, session, and HTTP. The app passes concrete implementations at startup through a setup() function. The package stores them internally, yes, as singletons.

Then the shared code imports hooks and utilities from that package, which read from the configured setup.

So now:

  • the boundary is explicit
  • the wiring happens in one place
  • the shared package is usable
  • and internally, it still relies on global state

It is not clean.

But it is controlled.

Why this worked

We did not fix the architecture.

We stabilized it.

  • no large refactor
  • no need to untangle every dependency
  • no breaking of existing assumptions
  • migration can happen gradually

Most importantly, the risk is now bounded.

That mattered more than elegance.

The uncomfortable part

Engineers like to fix things properly, especially when something feels wrong.

Singletons feel wrong. Hidden dependencies feel wrong. Global state feels wrong.

So the instinct is simple.

Now that we are touching this, let's fix it.

That instinct is often expensive.

Because the system does not care what feels wrong.

It cares what it already depends on.

What actually changed

Before:

  • messy system
  • implicit globals
  • hard to extract

After:

  • still messy internally
  • but the mess is centralized
  • the dependency is explicit at the boundary
  • extraction is now possible

That is real progress.

Even if it does not look clean.

What this is really about

This is not really about singletons.

It is about scope.

We did not need to redesign the system.

We needed to move part of it safely.

Those are very different problems.

The mistake is turning one into the other.

The senior tradeoff

The real decision was not clean versus ugly.

It was controlled mess versus risky rewrite.

Senior engineering is often about choosing based on second-order effects.

  • What breaks if this goes wrong?
  • How much of the system do I have to understand to be confident?
  • Can I roll this back?

Our solution was reversible.

A full refactor probably would not have been.

Boundaries

This approach is not always correct.

If the pattern is actively causing production issues, if the system is small enough to refactor safely, or if you are early enough to fix it cheaply, then fix it.

But when the system already depends on the pattern, the coupling is wide, and the risk is unclear, you do not get points for purity.

Takeaway

If a bad pattern already owns part of your system, your job is not always to eliminate it immediately.

Your job is to contain it, make it explicit, and stop it from spreading further.

Sometimes that means writing code you do not love.

That is fine.

Because the goal is not clean code.

The goal is a system that keeps working while you change it.

Related writing