Trade-offs Under Pressure
How to make decisions when there are no perfect choices and how to live with technical debt responsibly.
This article is not about how to do things right. It's about making decisions when there are no perfect choices—and how to live with those decisions responsibly.
Every Project has Trade-offs. The Question is: Are You Proactive?
During the 2–3 months of building the microservice system, I made many decisions not because they were technically "right"—but because they were the most viable options in that context.
There are decisions I'm proud of. There are decisions I knew were technical debt as soon as I made them.
The difference between a responsible decision and a careless one isn't whether it's "right"—it's whether you understand what you are trading off.
Trade-off 1: Skipping CI/CD to Accelerate Feature Delivery
In the early stages, manual deployment is faster. Setting up a CI/CD pipeline takes time—writing configs, testing the pipeline, handling edge cases.
Decision: skip CI/CD, deploy manually, record technical debt.
The Price Paid: Every deployment is manual work. The risk of human error increases over time. When the team grows (even by just one person), coordinating deployments becomes immediately more complex.
If I Were to Do It Again: I would setup CI/CD from day one, even if simple—just auto-running tests on push and auto-deploying to staging. Don't leave it for "later" because "later" often never comes.
Trade-off 2: Shared Database Instead of Database per Service
Ideal microservice: each service has its own database. My reality: some services share a database because migrating data and setting up each service separately took time we didn't have.
The Price Paid: Services are coupled at the data layer. Schema changes must be carefully checked for impact on other services. You cannot scale the database of one service without affecting others.
Mitigating the Damage: Even if sharing a database, enforce boundaries at the code level—each service only accesses its own tables, and never queries another service's tables directly. This isn't a perfect solution, but it significantly reduces coupling.
Trade-off 3: Using AI to Write Code Faster
I used AI during development, and I'm not afraid to admit it.
AI helped me write boilerplate code faster, look up syntax quicker, and explore options more efficiently. But there were code snippets generated by AI that I knew weren't optimal—I accepted them because they were good enough for now, and noted them for later refactoring.
The real issue isn't whether to use AI or not—it's whether you understand the code you're shipping. If you accept AI code without understanding what it does, you are creating a black box within your own system. When bugs occur, you won't know where to start debugging.
The rule I set for myself: AI can write code, but I must be able to explain every line of it to someone else.
Trade-off 4: Low Test Coverage
Writing good tests takes time—often more time than writing the main code. Under deadline pressure, tests are usually the first thing to be cut.
The Price Paid: Every refactor is a source of anxiety. Without tests, you don't know if you're breaking anything—unless you manually test the entire system every time you make a change.
A More Practical Approach Than "Test Everything": Prioritize tests for the most critical and breakable parts—core business logic, integration between services, and paths where errors cause the most severe consequences. 80% coverage on the 20% most important code is better than 20% coverage spread thin everywhere.
Trade-off 5: Lack of Documentation
No time to write full documentation. This is the most common trade-off and also the most "invisible" technical debt.
Invisible because it doesn't cause bugs immediately. But when someone new joins, when you return to the code after 3 months, or when you need to hand it over—that's when you pay the price.
Minimum Viable Documentation: At least a README for each service (how to run, how to test, required environment variables), ADR (Architecture Decision Records) for important decisions, and API documentation for all public endpoints. It doesn't need to be much in total—but it must exist.
Principles I've Learned
Intentional Trade-off vs. Unintentional Trade-off. An intentional trade-off is when you know what you are skipping, why, and when you will return to address it. An unintentional trade-off is when you skip something without knowing—that's not a trade-off, that's hidden technical debt.
Record what you know but haven't done. A simple TECHNICAL_DEBT.md file, recording what shortcuts were taken and why. This helps whoever takes over (or your future self) have the context to prioritize refactoring in the right places.
There is no perfect system—but there are responsibly built systems. Responsibility here means: knowing what you are trading off, communicating that with the team, and having a plan to pay it back.
Conclusion
Deadline pressure is a reality, not an excuse to stop thinking about architecture.
You can't do everything right from the start—but you can do the most important things right, and know what you owe on the rest.
That is the difference between technical debt and technical negligence.
Series • Part 1 of 20