Several times, I was among the first developers and front-end leads in various startups. Some have grown into mature companies with hundreds of developers, but unfortunately, most did not survive the growth stage. Architectural design mistakes made in the early stages significantly contributed to the failure of these startups.
For the team, these mistakes resulted in stress, overtime, and an inability to develop the product effectively. For the company, it meant losing precious time, users, money, or even closure.
Let’s look at what these mistakes are and how to avoid them.
A startup is a company designed to grow fast. (c) Paul Graham
Every startup goes through several stages to get from an idea to a product with millions of users. The first one is the development of an MVP to validate the business idea. The next stage is to find the product market fit by testing different product hypotheses. If all goes well, there is the growth stage when you deal with an expanding user base, growing team, and product complexity. Each of these stages requires very different approaches and architectural decisions. Let's focus more on the first two stages, using web app development as an example.
Mistakes made in these stages are the most expensive.
Initially, a startup has a small team, little time, and no clear vision of the product and target audience. To test the business idea, we usually build an MVP. Before you even get to coding, you will have to make many architecture decisions: you need to decide on the high-level design, choose the components of the tech stack, and much more.
Enterprise solution. In mature companies, analysts and POs typically contribute to product development before implementation begins. Hence, the product has a clear roadmap and a budget, sometimes for a couple of years in advance, and developers have an opportunity to implement a complex but reliable and scalable architecture.
In a startup, when the requirements are unclear, if you decide to start the development from a complex architecture, you risk finding out in a couple of months that it does not fit the changed requirements.
Example. You set up your frontend application with micro-frontends in a mono repo. You created a dozen microservices with GraphQL API on the backend from day one. You spent a month setting up the project.
Past experience. It is typical when team members' experience and personal preferences limit architectural decisions. You are familiar with the architecture of a project you worked on in the past, so you decided to apply the same approaches to the new project. As a result, you may end up with a rather complex architectural solution without considering the specifics and requirements of the new project.
Example. In your previous project, you developed a social network. In your new project, you decided to build an e-commerce marketplace on the same stack. Soon, you discovered that SEO is essential for marketplaces, which was not considered necessary in your previous project.
To Play Around. You've long wanted to try new technology that fits your startup well. Unlike big companies, you can not afford to devote a few months to researching and adapting this technology.
Example. Svelte is a very cool framework with a fast-growing and delighted community. But let's say you have no experience with it. If you decide to build MVP on it, you must spend significant time rediscovering solutions to typical tasks: data-fetching, state management, working with styles, etc. Do you have time for that when building an MVP in a startup?
There are three criteria to guide your choice of technology stack and architectural decisions when developing an MVP.
All other things being equal, you should prefer the simplest option or solution, even if it seems suboptimal. And more complex architecture should be achieved through an evolutionary process. If you choose from several options (e.g., next.js remix or vite?), it is worth checking whether each option best meets your current requirements. You can hardly guess how the product will develop in six months or a year, so accounting for all possible scenarios in the app architecture is counterproductive.
Example. You are choosing between Next.JS and a simple SPA based on Vite. If now, among the requirements of your product, there is no need for good indexability by search engines, you should not seriously consider this criterion when choosing a framework.
Example. You may doubt which state manager to use for your React application: Redux, Effector, Jotai, Zustand, or MobX. Ask yourself if you can do without a state manager at all and make a decision at a later stage of development.
After the MVP is released, you may discover its features do not meet real users' needs. Your team will have to test different hypotheses to build a product that users will really need. At this stage, the product usually does not have a clear roadmap, requirements change frequently, and architectural decisions made at the beginning are no longer fit.
The code has to be constantly rewritten, and entire features have to be discarded.
To try to implement a complex feature all at once. Let's say you are developing a social video-sharing platform. You decided to add a web-based video editor with tons of features: subtitles, crossfade transitions, watermarks, and so on. You should not try to implement such an editor in its entirety - you should first implement only the basic functionality (e.g., trimming the video) and add the rest later, confirming that the users liked the editor.
Overengineering. We are all doing our best to apply the best engineering practices that allow us to scale our software. For example, in the case of our video player, you may want to implement a very flexible modular architecture with strict boundaries or maybe adopt Feature Sliced Design. The problem is that when the requirements change unpredictably, and you need to test hypotheses quickly, the ceremonial code or overly complex solution will hinder rather than help.
Reinvent the wheel. It may seem like a good idea to develop your own library of UI components from scratch. You'll quickly discover that making them flexible and accessible is a time-consuming task. You'll also complicate the learning curve for new team members - they'll have to learn your component's API.
Features in a startup are a test of a hypothesis
It means that you should always start with the most straightforward implementation in terms of app functionality and code structure. More advanced engineering practices should be postponed until you verify that users need the feature.
It's not worth wasting time on your own state manager, component library, calendar, drag-and-drop, caching system, and other typical tasks for which there are proven solutions or libraries.
In addition, it is worth mentioning Backend-as-a-Service/API-as-a-Service. Examples are Auth0, Algolia, Contentful, Twilio, Stripe. These services provide ready modules for implementing various functionalities in an application. On the one hand, they allow developers to speed up development and reduce infrastructure support costs significantly.
On the other hand, as your product grows, the costs for these services may increase so much that it will be cheaper to implement them on your own.
Time spent on setting up development tools pays off very quickly. Make sure that at least a minimum amount of the following practices are used in the project from the beginning:
At the beginning of the project, we had to sacrifice the quality of the architecture in favor of development speed. This approach is good for MVP development but unsuitable for a company at the growth stage. Sooner or later, you will have to implement better engineering practices and architectural approaches to replace the ones adopted at the beginning. How do we realize that this day has come?
The signals to watch for:
Every time you see these signals, it's worth investigating the problem, planning the refactoring, and revisiting the decisions you made earlier.
In a startup, navigating architectural decisions can be challenging, but there are some principles to follow that increase your chances of success: