Technical Debt Killed My Project
A candid look at how unchecked technical debt and feature creep forced me to kill two major projects.
A candid look at how unchecked technical debt and feature creep forced me to kill two major projects.
So, recently, I killed two projects. Both were technically alive. Active commits, growing features, regular work. I was deep in them β especially one of them, which I'll admit was one of my favorite things I've ever built.
You might ask: Why kill something that's thriving?
The answer is technical debt. The silent, creeping kind. The kind you only see clearly when you step away.
I took a break β just one month β and when I came back, it hit me. The project was bloated. It was messy. It had grown so fast, and I had been so deep in the weeds, that I didn't notice how far I'd drifted from clean architecture, maintainable code, and long-term sanity.
Looking at it with fresh eyes? Brutal. It felt like returning to your childhood room and realizing you never cleaned up after the chaos.
This wasn't some side experiment either. I built this project like it mattered β because it did. It had so many features, so much potential. But all of that was buried under bad decisions, rushed implementations, and "I'll fix it later" code that never got fixed.
It was too much to handle. Refactoring wouldn't cut it. Rewriting parts wouldn't solve the underlying mess. I had to let it go.
Heartbreaking? Absolutely. But necessary.
I'll go into the gritty details later in this blog. I'll walk you through how it got here, what I learned, and what I'm doing differently next time. But for now, this post is about honoring the hard truth: sometimes, you don't salvage β you start over.
Not because you're weak. But because now, you know better.
This wasn't just any project. This was the project. One of my all-time favorites.
I started building it while playing Hypixel SkyBlock, frustrated by how many little problems I saw in the game. There were annoyances everywhere β things no mod seemed to fix β so I figured, screw it, I'll fix it myself.
That itch turned into a full-blown mission. Before I knew it, I had written almost 40,000 lines of Java. It grew fast. It was ambitious. It solved real pain points. And at the time, I genuinely loved working on it.
So, what went wrong?
Ironically, the project looked organized from the outside. Files were there. Things were working. But under the hood? A slow-moving disaster.
The real problem started when I got addicted to shipping features. Feature after feature after feature β more logic, more complexity, more chaos. I stopped tracking where things were. I couldn't remember which file imported what. Utilities were scattered. Some days, I'd just dump code into the first file I saw, thinking "I'll clean it later."
Spoiler: I never did.
Here are some concrete examples of what piled up:
Spaghetti Code in the API Manager:
Config Management Nightmare:
Java-Kotlin Integration Chaos:
Feature Dependencies:
The impact on maintainability was severe. Here's a specific example: When the API Manager bug surfaced, it took a week to fix what should have been a simple patch. Why? Because:
Eventually, I reached the point where I'd open a file and have no idea what was going on. The architecture wasn't just messy β it was non-existent. A few thousand lines in a single class. Zero documentation. Circular dependencies. Patchwork fixes on top of patchwork fixes.
It was working, but barely. And I had no idea how to move forward without making it worse.
At one point, things were still manageable. Yeah, there was some mess, but nothing I couldn't work around. The structure held β barely. I was still having fun.
But then I made a decision that wrecked everything:
I started building an Exotic Scanner.
If you're not familiar with Hypixel SkyBlock, here's the deal: "exotics" are armor pieces with colors that shouldn't exist. You can't dye items like that anymore, which makes those old items rare, aesthetic flexes. They're basically digital drip β niche, collectible, valuable.
So naturally, I thought, why not build a tool to scan for exotics in people's inventories or auction listings? Something to help me and others spot these rare pieces and maybe make some money off flipping them. Seemed simple enough.
That was the beginning of the end.
The scanner began its life in version 1.2.5 as a simple feature. Little did I know it would evolve into a monstrous codebase. Here's the journey:
This is where technical debt began its exponential growth:
The project had transformed from a simple mod into a comprehensive inspection toolkit.
The system reached critical mass:
The final result? A codebase that grew from 40,000 to 65,000 lines of Java, with approximately 25,000 lines dedicated to the scanner alone.
The scanner's growth exposed several critical issues:
No Clear Boundaries:
Performance Issues:
Testing Nightmare:
Maintenance Challenges:
And the worst part? I couldn't maintain any of it. Not cleanly. Not confidently.
Everything was duct-taped together. Dependencies crossed wires. Scanner code relied on internal stuff that had nothing to do with it. Changes in one area broke others. I started fearing commits. Avoiding refactors. I felt trapped.
I thought about rolling back to v1.2.5 β the last "clean" version before this monster feature bloated the project. But I didn't. Why?
Sunk cost fallacy.
I had put in so much time. So much effort. So many late nights solving dumb bugs I created myself. The idea of just throwing that away? It felt like betrayal.
But the truth was: I had already lost control. I just didn't want to admit it yet
By this time, I didn't even want to commit anymore.
Every line felt risky. Every fix felt like it would break five other things I'd forgotten existed. And then came the final straw β not from the code, but from a friend.
One of my friends was using the mod and hit me up, frustrated. The API Manager wasn't calculating requests properly. The Config Manager was straight-up failing if you turned off certain options. Things that should've taken me hours to patch ended up taking a week β not because the bugs were complex, but because the technical debt turned every fix into surgery.
Nothing was isolated. Nothing was safe to touch.
Even worse? Some of the scanner features were so busted, I was scared to even open the files. I knew if I tried to fix one thing, it'd be like pulling a thread that unraveled the whole damn sweater.
And yet... I couldn't walk away.
Sunk cost fallacy.
I kept telling myself I had to make it work.
That it would be a waste to stop now.
That I owed it to the project β and to myself β to fix what I broke.
But deep down, I knew the truth:
The project didn't need a patch.
It needed a funeral.
Finally, on June 7th, I pulled the trigger. I deprecated the old project and decided to start over β from the ground up.
Sounds easy, right? Spoiler: it's not.
Starting a new project is the hardest part. When you've already built something big, you think you know what to do next. But when you actually face the blank slate? The "where do I even start?" moment hits like a ton of bricks.
For me, the first real task was a config manager.
My old project used something called OneConfig. This time, I wanted to try something new β a library called MoulConfig.
Great idea, in theory.
In reality? The lack of documentation made it an absolute nightmare.
It took me two days just to render a single category in the config. Not even to build the feature β just to get that one thing visible.
I swear, I wanted to quit more than once. A genuine plea to the MoulConfig devs:
Please. Write. Docs.
I love the power of the library, but the frustration nearly broke me.
One saving grace? I decided to write this part in Kotlin. I'd only learned Kotlin a few months earlier β maybe two or three β and I was itching to put it into practice.
So yeah, while I was knee-deep in config hell, at least I was getting some good Kotlin reps.
After replicating the project groundwork, I ran into a brutal roadblock: zero documentation for Moldberry. Seriously, just a simple annotation, nothing more. For two days, the config just wouldn't render. I was stuck.
Finally, I cracked it: I had to use Java inside the Kotlin project.
Wait, what?
Most of the Kotlin projects I've worked on were pure Kotlin β clean, simple. But here, I was forced to mix Kotlin and Java. And that turned out to be a nightmare.
Every time I imported Java into Kotlin, something broke elsewhere. It was a tangle of mismatched expectations, bugs popping up like weeds. Trying to switch from the main config to the essential config? Spent a full day wrestling that mess, to no avail.
The whole experience felt like technical debt all over again β but this time, preemptively.
I didn't want to repeat the same mistakes. I didn't want to end up with a Frankenstein's monster of Java-Kotlin mashups. I just wanted something that works β clean, maintainable, and without the headache of chasing bugs born from language clashes.
The starting point was brutal. Setting up imports and configs felt like wading through mud. But I knew this was the hardest step. Once the foundation was stable, I could iterate and build up without that crushing weight.
Starting is always the hardest part β whether you're coding or cleaning up legacy mess.
I wasn't about to let history repeat itself.
This time, I promised myself: no shortcuts. No messy hacks. No piling up technical debt like a ticking time bomb.
That meant:
I'm treating this as a marathon, not a sprint.
Yes, it's frustrating and slow at times β especially when starting from scratch is such a grind β but it's necessary.
Because a clean foundation lets you build confidently. It keeps the project alive long after the initial excitement fades. It means you spend your time writing new features, not chasing ghosts from the past.
This is how I'm moving forward.
The old project was a lesson β brutal, painful, but invaluable. Now, with every line of Kotlin, every config I render, I'm building not just software but a mindset that respects code, process, and sanity.
No more tech debt death traps.
Just smart, sustainable progress.
"Technical debt kills projects."
Simple. Brutal. True.
It's the silent assassin behind every abandoned codebase and every burned-out developer.
You don't see it piling up day-to-day β until one day, it's too heavy to carry.
When I started rebuilding, I had to choose between two configuration libraries: OneConfig and MoulConfig. Here's how they stack up:
Pros:
Cons:
Pros:
Cons:
The lack of documentation for MoulConfig had severe consequences:
Development Time:
Code Quality:
Maintenance Issues:
The decision to use Kotlin brought its own set of challenges:
Language Integration Issues:
Build System Complexity:
Development Workflow:
Performance Considerations:
The experience taught me valuable lessons about language choice and integration:
Clear Boundaries:
Build Process:
Team Considerations:
These challenges reinforced the importance of proper tool selection and documentation in software development.
I fell into the trap of sunk cost fallacy. I had invested so much time and effort into the old project that I couldn't let it go, even when it was clear it was beyond repair. It's a common pitfall in software development, but recognizing it is the first step to moving forward.
The sunk cost fallacy manifested in several ways:
Time Investment:
Emotional Attachment:
Fear of Starting Over:
Pride and Ego:
Sometimes, the best decision is to start over. It's not a sign of weakness but of wisdom. Knowing when to kill a project versus trying to fix it can save you from endless frustration and wasted time.
Key indicators that it's time to start over:
Technical Signs:
Process Signs:
Business Signs:
I've learned to prioritize clean architecture and maintainable code over quick wins. It's a shift from hacking to disciplined building, and it's made all the difference.
The mindset shift involved several key changes:
Planning Over Hacking:
Quality Over Speed:
Long-term Over Short-term:
Process Over Product:
This shift wasn't easy, but it was necessary. It required:
Self-awareness:
Discipline:
Patience:
Humility:
The result? A more sustainable, maintainable, and enjoyable development process. One that doesn't lead to burnout or technical debt. One that allows for growth and improvement. One that builds better software.
Building small, incremental features instead of dumping everything into one giant monolith helps maintain clarity and control. Here's how to implement this approach:
Feature Planning:
Implementation Strategy:
Review Process:
Deployment Strategy:
Every module and feature should have its place and scope. This ensures that changes in one area don't break others.
Architecture Principles:
Code Organization:
Testing Strategy:
Documentation Requirements:
Even if it's just notes for yourself, thorough documentation is essential. It saves you from future headaches and helps others understand your code.
Code Documentation:
Architecture Documentation:
Process Documentation:
Maintenance Documentation:
Choose tools and libraries with future maintenance in mind. Consider the long-term impact of your choices.
Library Selection Criteria:
Build System Requirements:
Testing Framework Needs:
Monitoring and Logging:
How I'm planning to structure the new project and what I'm doing differently this time to avoid the same traps.
Project Structure:
Development Process:
Quality Assurance:
Maintenance Plan:
By following these practices, I'm building a foundation for sustainable development. One that allows for growth, maintains quality, and prevents technical debt from accumulating. It's not just about writing code β it's about building software that lasts.
π§ Meta Technical Debt: This Blog Itself Funny thing? The very blog you're reading right now is its own example of technical debt.
Iβve rewritten this site three times already.
First, it was built in React + MDX.
Then I switched to Contentlayer for better MDX parsing.
Then I ported it into Next.js with custom routing and MDX enhancements.
Now it's a Frankenstein that works⦠mostly.
This site is full of technical content, but ironically, it's also a graveyard of uncommitted refactors. I only use it to write β I havenβt committed anything beyond blog posts in months. Not because itβs perfect, but because I know one day Iβll nuke it and rebuild it from the ground up. Again.
Mark my words: This blog will be rewritten. Just not today.
Because today, Iβm too busy fighting technical debt elsewhere.