DEV Community

nyaomaru
nyaomaru

Posted on

How Does Technical Debt Pile Up? — Looking at “Just for Now” Examples (Structure Edition)

Hey folks!

Sometimes I think bugs love me too much — they just keep coming back no matter what I do 🐛❤️
Anyway, I’m @nyaomaru, a frontend engineer.

👉 Previous article

In that article, I introduced what technical debt is.
This time, let’s dive into how those “just for now” decisions actually accumulate — with a practical, code-based look.

Ready? Let’s go! 🚀

 

💸 The Process of Accumulating Debt

Very few implementations start out dirty. With reviews and decent quality gates, you can catch a lot.

But debt creeps in through small cracks, like:

  • The system grows in scale
  • Patches pile up during feature additions
  • Old code gets misread or misused
  • No one bothers to abstract or generalize

Each by itself seems harmless, but combined they cause debt to grow exponentially.

This time, let’s look at scale growth — using Vue as an example.

 

📦 Scale Growth

One classic case: directory structures slowly collapsing.

Take an Atomic Design–based component setup, for instance.

⚠️ Disclaimer: Atomic Design itself isn’t the villain.
It works fine if responsibilities are clearly separated.
But when you try to apply it everywhere, things go sideways.

🧱 The Good Old Days…

At first, everything was clean:
atoms had buttons, molecules had form groups, organisms had whole forms. Beautiful.

components/
├── atoms/
│   ├── Button.vue
│   ├── Input.vue
│   └── Label.vue
├── molecules/
│   ├── InputGroup.vue
│   └── RadioGroup.vue
└── organisms/
    ├── LoginForm.vue
    └── RegisterForm.vue
Enter fullscreen mode Exit fullscreen mode

Justice back then: “Sort by granularity!”

🧟‍♂️ But Then: Survival-Driven Compromises

  • “Just for now” → a login-only button sneaks into atoms/
  • “Close enough” → Input.vue overloaded with 7 props + extra hacks
  • “Looks reusable” → LoginFormWithSocial.vue shoved into organisms/
components/
├── atoms/
│   ├── Button.vue
│   ├── ButtonBlue.vue
│   ├── ButtonBlueSm.vue
│   ├── ButtonWithLoginIcon.vue
│   ├── Input.vue
│   └── Label.vue
├── molecules/
│   ├── Form.vue
│   ├── FormVer2.vue
│   └── FormVer2Copy.vue
└── organisms/
    ├── LoginForm.vue
    ├── RegisterForm.vue
    └── LoginFormWithSocial.vue
Enter fullscreen mode Exit fullscreen mode

Before you know it: classification collapse

  • atoms/ = “looks simple-ish” bucket
  • molecules/ = graveyard of misfits
  • organisms/ = pile of unrelated monsters

☠️ Root Cause of Debt: Structure Loses to Speed

  • Justice (Atomic Design) wasn’t enforced, so it was broken at will
  • No naming rules or classification guide → inconsistency everywhere
  • Soon: “Wait, what was this file for again?” — no one dares touch it

✅ Lesson: Structure Must Be Reviewed Regularly

  • Enforce rules with CI (e.g., block atoms/ from importing features/)
  • Treat directory layout itself as part of the design
  • Expect categories to rot within 6 months if left unchecked

 

🧱 When “Atoms” Absorbs Everything

Atoms were supposed to be reusable UI parts.
But gradually, generic UI and domain-heavy components got mixed together.

📌 Ideal Separation

Atomic Design focuses on UI granularity — not domain separation.
So recently, a responsibility-based split (ui/ vs features/) is becoming popular, close to Feature-Sliced Design.

components/
├── ui/              ← Generic reusable UI (buttons, modals)
│   ├── Button.vue
│   └── Modal.vue
├── features/        ← Domain-specific UI (business logic included)
│   └── transaction/
│       ├── TransactionStatusBadge.vue
│       └── TransactionActionButton.vue
Enter fullscreen mode Exit fullscreen mode

Idea:

  • ui/ = polished, reusable, domain-agnostic
  • features/ = business-context-bound, with logic and constraints baked in

🧟‍♂️ But Reality: Mix at Your Own Risk

😱 Actual structure that often emerges:

components/
├── atoms/
│   ├── Button.vue
│   ├── ButtonForOrder.vue ← 🤔 domain-specific but placed here?
│   ├── Modal.vue
│   ├── TransactionStatusBadge.vue ← 🤔 looks generic, but logic is domain-bound
│   └── ConfirmDialogWithReason.vue ← references customer IDs, business logic galore
Enter fullscreen mode Exit fullscreen mode

What happens:

  • “Looks like UI” → tossed into atoms/ even if domain-bound
  • Later devs assume atoms/ = safe reusable pieces → specs break
  • No culture of making features/ → everything looks “common”

☠️ Root of Debt: The Illusion of Reusability

  • Generic-looking code can still be domain-tied
  • If atoms/ contains “customerId” or “orderStatus,” it’s already features/

✅ Lesson: “Reusable” ≠ “Put in Atoms”

  • Judge by responsibility, not just looks
  • Split ui/ and features/ early — or regret later
  • Remember: Atomic Design = UI granularity design, not business logic design

👉 Simply being aware of the boundary between reusable UI and domain-tied UI cuts debt massively.

 

📦 Wrap-Up

Component architecture needs an initial plan.
But it’s not final — orgs must revisit structure as the product evolves.

Ignore this, and your “Atomic Design” turns into a monster.
Eventually, it’s either unfixable or costs a fortune to repair.

So: review structure regularly, keep it healthy, and keep development enjoyable.

Perfection isn’t possible — but recovery is! 💪

✅ Homework: check your project’s atoms/ today. If you see domain logic creeping in, fix just one thing.

This article focused on Vue, but React and other stacks face the same structural debt traps.

👉 Next time: feature additions and overlapping refactors, explored with React. Stay tuned!

 

✂️ That’s the gist!

Technical debt doesn’t appear out of thin air — it sneaks in through small “just for now” compromises.

This time we looked at structural drift, especially around Atomic Design gone wild.

Have you seen your own project’s atoms/ or molecules/ slowly turning into junk drawers?
Share your horror stories or survival tips in the comments — I’d love to learn how your team fights debt!

Top comments (0)