Here’s what it was like to work on a project, turning a prototype web application into a functional MVP in just a few months. Read about how we dealt with a high amount of technical debt and which solutions we found to overcome the challenges we faced along the way.
Understanding the Starting Point
When we took over the project, the application already had some core features in place, providing a functional backbone for the system.
At the same time, several planned features were only partially implemented or not fully integrated across the platform. For example, In some cases, users were able to fill out form fields in the UI, but certain information was never actually stored in the database.
In other words, the feature looked functional on the surface, but some data entered by the admin didn’t persist. This made it clear that further work was required to turn it into a fully operational feature.
Understanding how the system was structured, which parts were working, which weren’t, and why, became our first major task. Only with this clarity could we safely plan the next steps and prepare the platform for production.
Challenges we faced
Technical Debt
As we dug deeper into the code, a clear pattern emerged: the codebase was a mix of components at very different stages of maturity. Some parts were well-structured and stable, while others were incomplete, inconsistent, or even deprecated.
Understanding the system was further complicated by limited code documentation and the several-month gap between the last updates and our own start.
Once we had a general understanding, we faced the next challenge: making necessary updates without losing ourselves in the code. Outdated dependencies required migrations to newer versions, and every refactoring step had the potential to trigger a chain reaction across multiple components. We constantly had to find a balance: updating enough to keep the system maintainable and future-proof, but not so much that we risked introducing new issues or losing focus on the actual task.
Another aspect was keeping the backend API and frontend client in sync. While both sides were mostly aligned, all data types and endpoints had been defined manually in each codebase. That meant any change in the backend required a corresponding manual update in the frontend, and vice versa. It’s easy to see how small oversights in that process could lead to inconsistencies or unexpected bugs. On top of that, the system lacked endpoint validation, meaning the frontend could call an endpoint that never existed, and errors would only surface at runtime. So as a result, there was a high risk for errors leading to inconsistencies and unexpected bugs and additionally those errors often went unnoticed until testing at runtime.
YAGNI: You Aren’t Gonna Need It
YAGNI is the principle that reminds us that not every “best practice” or elegant abstraction is actually useful for your current project. Sometimes, we tend to over-engineer solutions, building layers, patterns, or features that sound clean or future-proof, but add unnecessary complexity for what we actually need for our purpose.
As we went on analyzing the application, we stumbled upon a few implementation decisions that perfectly illustrated this. Some parts of the system had been designed with complex frameworks and abstractions that were great in theory for large-scale projects, but for the size of our project, the setup felt overly complex, introduced additional boilerplate and a steeper learning curve for relatively simple tasks it was built around.
Unclear or Evolving Requirements
While beginning to implement new features and refining existing ones, it soon became clear that some requirements weren’t fully defined, or some details were missing. This was another natural consequence of the project’s long pause, during which much of the original context had simply been lost.
In practice, the descriptions of certain features often raised more questions than they answered. Without proper documentation, it was sometimes difficult to determine exactly what was intended. Some aspects that seemed clear on paper turned out to be ambiguous once we dug into the code, and the reasoning behind earlier decisions wasn’t always easy to reconstruct. As a result, we often had to make our own assumptions about how a feature should actually be implemented.
This lack of clarity often slowed down our workflow. We’d make progress on a task, only to hit a point where something didn’t add up and we needed to stop and ask for clarification. Waiting for answers meant delays, and estimating effort became tricky when requirements could shift or expand midway.
Many Stakeholders, Different Levels of Involvement
In a software development process, there are always many stakeholders with different opinions, not only about the overall vision, but also about the technical implementation. Here we had the same experience. Our stakeholders had to face decisions where multiple technical approaches seemed equally valid, but followed different priorities.
In these situations, we as developers played a key role in bridging the gap. By analyzing the pros and cons of each option, we could propose implementation approaches that offered a balanced middle ground, combining the strengths of both sides while avoiding system-breaking changes and keeping the flexibility needed for future development.
What may become challenging in the long run, though not directly tangible during our work, is that the project is currently steered exclusively by one stakeholder. While this is not a problem for a project driven by one customer, this application had to satisfy multiple stakeholders and institutions with equal level of interest. While this workflow ensures clear decision-making and consistency, it also means that important perspectives of other partners within the network might not always be equally represented. A broader inclusion of voices in the decision-making process could help ensure that the platform reflects the diverse needs of its entire user base.
Although this alone may not be enough, the fact that the projects underlying source code is planned to be made publicly available, allowing anyone to contribute to the project, may help balance this centralization. Keeping the codebase accessible to all partners invites collaboration, feedback, and transparency.
Solutions we found
- Take your time to understand what you are working with
Especially in a project environment with little to no documentation and high technical debt, the first and most important step is always to explore and understand the system thoroughly. In larger projects, it’s often impossible to fully grasp every single detail up front. Still, taking the time to understand as much as reasonably possible pays off. Rushing into development without this knowledge can lead to mistakes, unexpected bugs, or unnecessary rework. Investing time up front saves effort in the long run and helps build confidence in the changes you make.
One thing that greatly sped things up in that process was to make use of AI. There are a lot of tools available helping you to get a better understanding of what you are working with. For our purposes, we chose a combination of Claude and Mermaid to generate powerful diagrams from our project’s context, breaking down the overall structure and dependencies between components. - Refactor in small steps
When touching deprecated code or introducing better alternatives, it’s easy to get carried away and try to restructure everything at once. Instead, it’s important to find a balanced approach.
With only a limited timeframe of a few months we we decided to modernize and improve what was necessary, always trying to keep the existing functionality intact and maintain system stability. Pragmatic decisions always help moving forward without getting stuck in endless refactoring. - Challenge your assumptions
Unclear or evolving requirements often force you to make assumptions about how a feature should work.
For us It was essential to challenge our assumptions up front, both within the development team and with stakeholders. To do this, we held daily meetings within the dev-team to discuss uncertainties and reached out to our customers whenever necessary. This prevented us from drifting too far into our own interpretation of the requirement and having to undo or refactor large parts later. - Keep a consistent API-Contract
Keeping your backend api and frontend client consistent can be quite challenging, especially in large projects where you are dealing with numerous endpoints and complex data models. That’s why it’s essential to maintain a consistent API approach, ensuring that the frontend always knows exactly what the backend expects and vice versa. So how did we approach this challenge?
Since a large part of the backend functionality and its endpoints were already implemented, we went for a backend code-first strategy and decided to use openapi-typescript to generate our frontend client, including all TypeScript types and REST endpoints from the existing OpenAPI specification. By relying exclusively on these generated types in the frontend, we eliminated duplicated code and ensured full type safety for both DTOs and parameters. Furthermore, this approach not only provides code completion for our frontend client but also validates at compile time that we’re using the API correctly. - Keep It Simple stupid
Best practice and maintainability is good and all, but it’s also important to understand what your project really needs and to keep your implementation as intuitive as possible for other developers. How many user will have my Application when its live? Does your app offer many features, or is it just a simple tool for one or two main tasks? Always consider the size and scope of your project when choosing a framework or design pattern.
Wrapping it up
Taking over a project with little documentation comes with its own set of challenges. Take your time to understand the intentions behind the existing implementation. It can be frustrating, but once you have the bigger picture, you’ll be able to identify what should be changed and what is better left untouched — at least for now — so that new features can be safely integrated into the system. As with most things, and especially in projects, where a lot of things are not defined in all detail, communication is key. Make sure everyone knows what you’re working on and how you’re approaching the problem.
Explore Our Latest Insights
Stay updated with our expert articles and tips.
Discuss Your Web Development Project with Our Experts Today.
Discover how our tailored web development solutions can elevate your business to new heights.
Stay Connected with Us
Follow us on social media for the latest insights and updates in the tech industry.

