“You don’t have a scaling problem – until you have a scaling problem.”
– Me
Having spoken this phrase countless times over the years it’s time to dive in a little deeper to the sentiment behind it.
As developers, it’s easy to fall for the siren song of building the perfect system. Planning for every contingency, designing for millions of users, and ensuring perfect code coverage all seem noble. Surely this guarantees no bugs and perfect alignment with customer needs, right?
In most cases, we’re not saving lives, launching rockets, or driving cars. We’re building software. Unlike companies flush with venture capital, many of us ship fast and adjust on the fly. The goal? Deliver solutions that users can test and provide feedback on—quickly. Over the years, I’ve seen two extremes in engineering organizations:
The Over-Engineered Approach
Companies spend months designing scalable solutions, achieving perfect code coverage, and overloading their codebase with abstractions—all before validating market fit. While thorough, this approach is slow and expensive.
The Under-Engineered Approach
On the other end, some companies prioritize speed above all else. Unit tests, documentation, and thoughtful system architecture are seen as unnecessary obstacles. While fast, this often leads to fragile systems riddled with basic functionality bugs.
Both approaches lead to issues. The over-engineered solution slows feature development due to excessive bureaucracy, while the under-engineered approach struggles to maintain stability. Neither is ideal.
🍸Moderation is Key
Dogmatic adherence to any one method—be it unit tests, linters, or horizontal scaling—can blind teams to better solutions. Instead, aim for balance.
📈Scaling Developer Velocity
The true goal is shipping high-quality features as efficiently as possible. One way to achieve this is by designing code for your fellow developers. Systems should be:
- Easy to decompose
- Easy to test (when necessary)
- Easy to modify
For deeper insights into building effective teams, check out books like Code Complete and The Pragmatic Programmer.
📦The Importance of Modularity
Breaking functionality into logical blocks makes sense, but overdoing it can create problems. For instance:
- A sprawling microservices architecture can frustrate your DevOps team.
- A single
utils
module with 50+ files makes maintenance a nightmare.
Keep it simple. Ensure your abstractions are practical, not excessive.
☑️The Role of Standards
Establishing consistent architecture standards is critical, especially for cross-module interactions. For example, if you’re writing an ETL pipeline and another developer is adding AI analysis, ensure both systems interact in a way that aligns with the rest of the codebase. Clean, consistent interfaces make it easier for teams to scale and adapt.
📄Documentation: Your Silent MVP
Would libraries like Pandas have succeeded without documentation? Unlikely. Documentation doesn’t need to be exhaustive—a concise README.md
for each module is often enough.
By documenting your interfaces, you not only clarify design choices but also improve AI-assisted tools like GitHub Co-Pilot. Remember, a little documentation goes a long way.
🚢Shipping a Production-Proof Proof of Concept
The primary goal of any software is to solve end-user problems. Writing code isn’t the end—it’s the means. To entice users, your proof of concept (POC) must have production-level quality. However, it should remain lightweight and adaptable.
🧬When the POC Evolves
As your user base grows, so will your needs:
- Performance Profiling: Ensure your servers can scale.
- Testing: Expand your test suite to give new developers confidence.
- User Experience: Invest in UX designers or add simple tooltips to guide users.
⛳Case Study: Pin Sheet Pro
I’m currently working on Pin Sheet Pro, a SaaS that helps golf courses share daily hole locations with golfers. To validate the idea, I launched a lightweight Django application hosted on Linode, backed by a scalable SQLite database1.
The setup is simple—no containers, just a script for deployment via GitHub Actions. Over time, features like PostgreSQL, OAuth login, and extensive tests will be added as needed. For now, the priority is delivering value to golf course operators.
Until then, I don’t have a scaling problem—and neither do you.
1. Hopefully you caught the sarcasm here. There’s a lot of debate in the Django community about using SQLite in production. As usual, the answer is: it depends.