Why we keep over-engineering tiny apps like they’re prepping for a global launch and what actually happens when real traffic shows up.
Somewhere along the way, the dev community collectively decided that every project from a side-gig todo app to someone’s “Uber but for houseplants” prototype needs to be architected like it’s about to handle Netflix-level traffic during a world-changing event. I don’t know when this happened, but it feels recent… like the moment Kubernetes became the Bitcoin of engineering conversations: everyone talked about it, half of them used it, and almost none of them needed it.
The spark for this article? A GitHub issue where someone asked how to “gracefully scale” their personal note-taking app… which currently had three users. Three. And one of them was their dog’s vet.
At some point, we started treating simplicity like it’s “junior,” and complexity like it’s an achievement badge. Meanwhile, most apps never make it past the “friends and cousins” user base. But somehow they still end up with CI pipelines longer than the README.
It reminds me of when I once built out Terraform modules, a full VPC layout, and a microservice mesh… for a prototype feature that didn’t survive past week one. Peak dev energy.
TL;DR: Most apps don’t need microservices. Most products won’t hit real scale. And you probably don’t need a distributed system until you have actual humans stressing your database. Let’s walk through why we overbuild, what scaling actually looks like, and how to stop coding like your weekend project is secretly Netflix in disguise.
How we all became part-time SREs for no reason
Somewhere in modern dev culture, we quietly accepted a new identity: part engineer, part infrastructure gremlin. It’s like we woke up one day and realized every project even the “I just want to track my protein intake” app now ships with Docker, Kubernetes, a reverse proxy, and a CI pipeline that looks like a boss fight.
When did this happen?
When did a simple web app become a multi-stage architecture diagram worthy of a Netflix tech blog post?
Open GitHub and you’ll see it everywhere. Boilerplates that come pre-loaded with distributed tracing. Starter kits that assume you’re deploying to four regions and need zero-downtime migrations. Even hobby projects have Makefiles that require a tutorial video.
And yeah… I fell into it too. I once spent longer configuring CI/CD than building the actual feature. Honest moment: the feature never shipped, but that pipeline? Oh, it was chef’s kiss. Twelve steps, caching layers, parallel tests basically a Formula 1 pit crew for code nobody used.
There’s this subtle pressure in dev spaces:
if your stack looks too simple, people assume you’re “not serious.”
Use Django? “Why not microservices?”
Use Heroku? “But what about autoscaling?”
Use SQLite? “Bro… are you okay?”
It’s wild. We’ve built a culture where complexity feels like competence, and simplicity feels like a beginner move even though most teams actually thrive with the simplest thing that works.
The irony? Most of us are building apps that would run beautifully on a single VPS with a Postgres instance, and life would be easier for everyone involved especially us.
Why devs over-engineer (and why it feels so good)
Let’s be honest: over-engineering is basically the developer version of cosplay. We know we’re not building Netflix, but wow does it feel good to pretend for a minute. There’s something intoxicating about architecting a system so massive, so meticulously designed, that it could theoretically survive a meteor strike even if the actual product is a mood-tracking app for your cat.
Part of this is fear. Not fear of traffic fear of embarrassment.
The “what if” spiral goes like this:
- What if my app actually blows up?
- What if I get roasted on Reddit for not using queues?
- What if someone asks about my architecture diagram and it looks too… normal?
And then suddenly you’re deep in Kubernetes docs at 1 a.m., convincing yourself that an Istio service mesh is the only thing preventing your app from collapsing under the tremendous load of… five beta testers.
The other part? Dopamine.
Dev brains love shiny tools. We love diagrams. We love the feeling of “engineering the future.” I can’t tell you how many times I’ve procrastinated real product work by redesigning my system architecture in Excalidraw. One time I built an event-driven Kafka pipeline before realizing the feature it powered didn’t actually need to exist. The diagram survived longer than the idea.
There’s also cultural gravity.
Browse r/ExperiencedDevs or Hacker News on any given day and you’ll find someone shouting “monoliths don’t scale!” while conveniently forgetting that Shopify, Stripe, GitHub, and Basecamp all started as monoliths and scaled just fine.
Rhetorical question time:
When did “monolith” become a slur?
Honestly, half of us aren’t building Netflix. But we’re definitely building the Netflix diagram.
And hey I get it. Over-engineering feels like progress. But most of the time, it’s just really fancy procrastination.
Your app doesn’t need this… yet
There’s a whole category of tech that quietly sneaks into projects long before it belongs there. Microservices. Multi-region failover. Kubernetes clusters with names longer than the actual app. Event-driven everything. It’s like we’re all prepping for architecture finals nobody signed us up for.
Here’s the truth: if you haven’t hit a real bottleneck, you’re just guessing.
A surprising number of performance issues vanish when you… wait for it… profile your code. Not rewrite your whole stack. Not introduce Kafka. Not spin up a distributed cache. Just measure what’s actually slow.
I learned this the hard way. I once set up Redis, a job queue, and a message bus to “handle future scale.” Weeks later, I deleted all of it and replaced the workflow with a single Postgres trigger. It worked flawlessly, and my ops stress dropped like a rock.
So many devs build as if a tidal wave of users is imminent. Meanwhile, Shopify famously ran a monolith for years. Basecamp still does. Heroku has powered countless successful companies with simple dynos and a boring Postgres setup. Reality: most apps don’t grow fast enough to justify complex systems early on and even if they do, refactoring usually happens way later than people think.
You don’t put nitrous on a Honda Civic before you pass your driving exam. Same energy here.
Start simple. Let problems introduce themselves before you introduce solutions.
What scaling actually looks like (and why you’re nowhere near it)
The funniest part of all this “planet-scale architecture” energy is that most devs have never actually seen real scale. They imagine it as this chaotic tsunami of traffic melting servers, when in reality it usually starts as a slow, boring curve that your current tech can handle for a surprisingly long time.
Postgres alone can take an absurd beating before you even think about sharding. Rails and Django apps quietly serve millions every day without breaking a sweat. Companies like Shopify and GitHub didn’t jump into microservices because of vibes they did it after hitting years of real growth and real constraints. Until traffic punches you in the mouth, you’re just over-preparing for a fight that isn’t happening.
I remember a “traffic spike” on a team I was on everyone panicked, Slack lit up, dashboards got screenshotted like we were in a war room. Then someone zoomed out the graph and realized the spike was literally our team logging in at the same time. Not scale. Just a badly written query.
Real scaling usually looks like:
- Adding basic caching once your performance dashboard tells you to
- Batching work when queues start to backlog
- Splitting off one part of the system when it becomes your problem child
- Maybe adding a second database after the first one actually begs for mercy
But none of that happens on day one or month one or sometimes even year one.
Most “scaling issues” devs fear are actually just bugs wearing trench coats.
You’ll know you’ve hit real scale when the system forces your hand, not when your imagination does.

The quiet superpower of boring tech
There’s a secret in the industry that senior engineers whisper about once you’ve unlocked enough XP: boring tech is ridiculously powerful. Not cute-powerful. Hulk-powerful. The kind of powerful that ships features while the “microservices enthusiast” on your team is still configuring Terraform modules.
Every time someone says “monoliths don’t scale,” a senior engineer somewhere rolls their eyes so hard they level up. Because the truth is simple: boring tech works. It’s predictable, debuggable, and you don’t need a dedicated ops squad just to deploy it. Shopify ran a massive Rails monolith for years. Basecamp still swears by theirs. Even DHH wrote a whole piece praising the “majestic monolith,” and the receipts are right there in their architecture.
I’ve felt this superpower firsthand.
There was a stretch where I ditched my microservices experiment and rebuilt everything as a single, clean, well-organized app. Suddenly deployments were calm. Logs made sense. My cognitive load went from “NASA launch prep” to “I can actually ship things again.” Productivity shot up because complexity went down.
And here’s the thing: boring tech doesn’t mean weak tech. SQLite paired with something like Litestream can handle traffic that would surprise a lot of engineers. A single Postgres database can carry you much further than people admit. A plain VPS with a simple deploy script? Absolute workhorse energy.
You don’t need a robotic arm when a screwdriver solves the problem.
Most companies aren’t suffering from a lack of innovation they’re suffering from a surplus of infrastructure cosplay.
Sometimes the most “senior” thing you can do is pick the simple option… and go build something people actually want.
Build what you need now, scale when you must
If there’s one mindset shift that instantly lowers stress and doubles shipping speed, it’s this: build for the users you have, not the users you dream about. Scale isn’t a personality trait it’s a reaction to real pressure. Until your system groans under actual usage, complexity is just cosplay.
The simplest way to avoid over-engineering is to follow a rule older than half the frameworks we use: YAGNI you aren’t gonna need it. There’s an entire GitHub thread arguing that 90% of “future-proofing” ends up being tech debt disguised as ambition. Hard truth? They’re not wrong.
Practical steps look like this:
- Start with a monolith it’s fine, you’re fine
- Profile before optimizing anything
- Add queues only when jobs actually pile up
- Cache based on data, not vibes
- Measure latency instead of imagining scale monsters
I’ve watched teams spend months preparing for “inevitable growth,” only to discover the real blocker wasn’t load it was that nobody wanted the feature they were scaling. Meanwhile, the simple prototype we nearly scrapped? That’s what ended up getting adoption.
Here’s a spicy question:
What if the thing that actually scales first is your technical debt, not your traffic?
Most systems don’t fail because they’re too simple. They fail because the team built a spaceship when a bike would’ve gotten them there faster.
Build small. Watch it break a little. Fix only what breaks. That’s real scaling.

Conclusion:
Here’s the twist nobody tells new developers: not building Netflix is a blessing. Netflix has to operate at a scale that melts databases for sport. They solve problems you’ll (hopefully) never meet in the wild. Meanwhile, most of us are building normal apps for normal humans and that’s exactly why we don’t need to architect like we’re preparing for global streaming traffic.
Once you zoom out, it becomes pretty obvious: complexity rarely makes a product better. But shipping consistently? Listening to users? Updating features while your system still makes sense? Those are the things that actually create momentum. The future of dev work isn’t “more Kubernetes.” It’s smarter simplicity. Better defaults. Tools that let small teams ship without drowning in YAML. Even AI copilots lean toward simplifying not multiplying the amount of infrastructure you touch.
If anything, the next wave of great engineering culture looks a lot like the old wave: build the simplest thing that works, watch how real people use it, and scale only when the system asks you to. No more infrastructure cosplay. No more diagrams built for future problems that might never arrive.
I’ll leave you with this:
Most apps don’t fail because they couldn’t scale.
They fail because they never got far enough to need to.
So keep it boring. Keep it clear. Keep it human.
And if you’ve got a wild over-engineering story, drop it in the comments I promise we’ll all laugh with you, not at you.
Helpful resources
- Netflix Tech Blog: https://netflixtechblog.com
- Majestic Monolith (DHH): https://world.hey.com/dhh/the-majestic-monolith-73d4c2ca
- Heroku 12-factor app principles: https://12factor.net
- Litestream + SQLite: https://litestream.io
- Shopify and the Monolith https://shopify.engineering
So keep it boring. Keep it clear. Keep it human.
And if you’ve got a wild over-engineering story, drop it in the comments — I promise we’ll all laugh with you, not at you.
Top comments (0)