← Back to Blog

Why Most Technical Debates Don't Matter

Tabs vs spaces. REST vs GraphQL. Monolith vs microservices. Some technical debates matter, but most don't. Here's how I tell the difference after a decade.

Last Tuesday, a PR comment thread on our team's repo hit 47 messages. The topic: whether a new utility class should use a static factory method or a public constructor. Forty-seven messages. The class had three fields and one method. It was used in exactly two places.

I contributed six of those messages. I'm not proud of that.

By message 30, someone had linked a Martin Fowler article. By message 40, someone else had pulled up the JDK source code to show how Optional.of() uses a static factory. By message 47, the original author just picked one, pushed the commit, and wrote "going with constructor, let's move on." The PR was approved in four minutes after that.

I've been writing software for over a decade. I've been on both sides of these arguments more times than I can count. I've been the person writing the essay-length comment about why my approach is better, and I've been the person quietly closing the laptop and going for a walk because the thread is making me tired. And somewhere along the way, I started noticing a pattern: the intensity of a technical debate is almost always inversely proportional to how much it actually matters.

The Debates That Feel Important But Aren't

Tabs vs Spaces

I can't believe we're still talking about this in 2026. Formatters exist. Prettier exists. EditorConfig exists. Every major IDE can auto-format on save.

Pick one. Put it in your .editorconfig. Set up a pre-commit hook. Never think about it again. The fact that this debate has survived for decades is proof that neither option has a meaningful advantage. If one were clearly better, we'd have converged by now.

I use spaces. I don't have a good reason. It's what I started with. If you use tabs, that's fine. Your users will never know.

REST vs GraphQL

I've built production APIs with both. REST is simpler to set up, simpler to cache, and simpler to debug. For 90% of the APIs I've worked on, REST was the right call.

GraphQL is great when you have multiple clients with very different data needs. A mobile app that needs a thin payload and a web dashboard that needs everything. That's a real problem, and GraphQL solves it well.

But most teams don't have that problem. Most teams have one or two clients, and the data shapes are pretty similar across them. If you're building a GraphQL API because it's "the modern way," you're adding complexity for no reason. If you're building a REST API for a system with fifteen different client types all needing different slices of the same data, you're going to end up with a mess of custom endpoints.

The answer is almost always "it depends on your situation." Which is boring. But correct.

Monolith vs Microservices

I have a strong opinion here, and I'll share it: most teams should start with a monolith. I've watched three different startups adopt microservices before they had ten engineers, and all three spent more time debugging inter-service communication than building features.

Microservices solve an organizational problem, not a technical one. When you have 200 engineers and they can't all work on the same codebase without stepping on each other, microservices let teams move independently. That's valuable. But if your whole backend team fits in one Slack channel, you probably don't need Kubernetes and a service mesh.

The worst microservices architecture I ever saw was at a company with eight developers and twenty-three services. They had more services than engineers. Deploying a feature that touched three services required coordinating three PRs, three CI pipelines, and a deployment order. A monolith would have been one PR and one deploy.

That said, I've also worked on monoliths that were painful. A million-line Java application where a change in the billing module could break the notification system because of some shared mutable state buried four layers deep. At that point, splitting things apart makes sense.

Context matters. Team size matters. How fast you're growing matters. "Monolith vs microservices" isn't a technical question. It's an organizational one.

Functional vs OOP

This debate was more interesting fifteen years ago. Today, every mainstream language borrows from both models. Kotlin has data classes and higher-order functions. Swift has structs, protocols, and map/filter/reduce. Even Java has lambdas and streams now.

The "pure functional" vs "pure OOP" argument is mostly academic. I've never worked on a production codebase that was purely one or the other. Real code is pragmatic. You use objects when modeling stateful things makes sense. You use functions when transforming data makes sense. Sometimes you do both in the same file.

If someone tells you that functional programming is always better, ask them to model a complex UI with mutable state using only pure functions. If someone tells you OOP is always better, ask them to write a data transformation pipeline using only class hierarchies. Each of those exercises produces something awkward.

ORM vs Raw SQL

I use both. In the same project. Sometimes in the same service class.

For simple CRUD operations, the ORM saves time and reduces boilerplate. Spring Data JPA with a findByUserIdAndStatus method is hard to beat for basic queries. The generated SQL is fine. You don't need to hand-optimize a query that runs once per user request and hits an indexed column.

For complex reporting queries with multiple joins, subqueries, and window functions? Write the SQL. ORMs generate terrible SQL for complex queries, and you'll spend more time fighting the ORM than you'd spend writing the query yourself.

The dogmatic positions here, "never use an ORM" or "always use an ORM," both ignore how actual projects work. You'll have 50 simple queries where the ORM is perfect and 5 complex queries where you need raw SQL. Use both. It's fine.

Why We Argue Anyway

If these debates don't matter much, why do they generate so much heat? I've thought about this a lot, partly because I keep catching myself doing it.

Technical preferences become identity

"I'm a Vim user" isn't really about text editing. It's a flag you plant. It says something about how you see yourself as an engineer. Same with "I prefer functional programming" or "I use Arch Linux." These choices become part of how we define ourselves in a field where it's hard to differentiate based on actual skill.

When someone challenges your technical preference, it doesn't feel like they're disagreeing about a tool. It feels like they're disagreeing about who you are. That's why a conversation about text editors can get heated in ways that a conversation about database indexing strategies rarely does.

Easy debates feel productive

Debating tabs vs spaces is easy. You can have a strong opinion, defend it with examples, and feel like you contributed. It scratches the same itch as doing real work without requiring you to actually solve a hard problem.

You know what's hard? Figuring out why your checkout flow has a 40% drop-off rate. Diagnosing a memory leak that only shows up under sustained load. Deciding whether to invest three months in paying down tech debt or building the feature your biggest client is threatening to leave over.

Those problems are messy and ambiguous and don't have clean answers. Tabs vs spaces has a clean answer: pick one. The clean answer is less satisfying, so we keep debating.

Ego

Nobody likes being told their approach is wrong. I don't. You don't. The senior engineer with 20 years of experience doesn't. When someone says "actually, constructor injection is better than field injection," what you hear, if you've been using field injection for years, is "you've been doing it wrong." That stings, even if they're right.

Most technical debates aren't really about the technology. They're about whose judgment is better. And that's a much more personal thing to argue about.

We generalize from one experience

I once spent a full weekend debugging an issue that turned out to be caused by an ORM generating an inefficient query. It created a full table scan on a table with eight million rows. After that experience, I had a visceral distrust of ORMs for about two years.

That wasn't rational. The ORM didn't fail because ORMs are bad. It failed because I used it for a query it wasn't designed to handle. But the pain of that weekend was real, and it colored how I thought about every ORM-related decision for a long time. We all do this. One bad experience with a technology becomes a general rule about that technology.

The Debates That Actually Matter

Not every technical debate is a waste of time. Some are worth having carefully.

"Should we add this dependency or build it ourselves?"

This one has real consequences. Every dependency is a bet on someone else's maintenance schedule, security practices, and API stability. I've seen projects go down because a transitive dependency introduced a breaking change in a minor version. I've also seen teams waste months rebuilding something that a well-maintained library does better.

There's no universal answer, but the stakes are real. A bad dependency choice can cost you weeks of debugging a year from now. Building something yourself when a library exists can cost you weeks of development right now.

"Do we need this feature at all?"

This is the most expensive question to get wrong. Building the wrong thing costs more than building the right thing badly. A buggy feature that users need will get fixed. A polished feature that nobody uses is pure waste.

I've been part of teams that spent months building features that never got used. Not because the code was bad, but because nobody stopped to ask whether the feature solved a real problem. The engineering was fine. The decision to build it was the mistake.

"Are we solving the right problem?"

This is the debate nobody wants to have because it means admitting you might be heading in the wrong direction. It's uncomfortable. But catching a wrong-direction decision early saves orders of magnitude more time than any code-level optimization.

Last year, my team spent two weeks designing a caching layer to speed up a slow API endpoint. Then someone asked: "Why is this endpoint slow in the first place?" Turns out the query was doing a sequential scan because an index was missing. One migration, one index, problem solved. We almost built a whole caching system to work around a missing index.

"How do we handle this data migration without downtime?"

This is a real technical constraint with real user impact. Get it wrong and your users see errors, or worse, lose data. Dual-write strategies, backfill scripts, feature flags, rollback plans: these are the trade-offs that matter. Decisions here have direct consequences users will feel.

Debates that matter are about user impact, money, or long-term maintenance cost. If a decision affects none of those, it's a preference, not a technical choice.

How I Tell the Difference

After years of getting pulled into debates that didn't matter, I've developed a simple set of questions I ask myself before jumping into a technical argument.

Can this be resolved with a linter config or a formatter? If yes, it doesn't matter. Configure the tool, enforce it in CI, stop talking about it. Tabs vs spaces, bracket placement, import ordering, trailing commas: about 80% of code style discussions fall here.

Is this about syntax, style, or tooling preference? If yes, it probably doesn't matter. These are the debates where both sides can produce working, maintainable software. The choice between Kotlin's when expression and a chain of if/else blocks doesn't affect your users.

Is this about architecture, user impact, or long-term maintenance cost? If yes, it might matter. Slow down and think. These are the decisions that are expensive to reverse. Choosing a database, deciding on a deployment strategy, picking your authentication approach. Give these the time they deserve.

Has this debate been going on for 30+ years with no winner? If yes, the answer is probably "both are fine." Emacs vs Vim, SQL vs NoSQL, static vs dynamic typing. Each persists because both options work. If one were clearly superior, the other would have died out.

Here's a personal example. A few months ago, I caught myself writing a 400-word Slack message about why we should use sealed classes instead of enum classes for a particular state machine in our Kotlin codebase. I was three paragraphs in, with code examples, when I stopped and asked myself: does this affect users? No. Does it affect long-term maintenance? Maybe slightly, but both approaches would work. Am I writing this because it matters, or because I have a preference and I want to be right?

I deleted the message and replied with: "Either works. Your call. I have a slight preference for sealed classes but it's not a hill I'll die on." The PR was merged ten minutes later. The feature works fine.

Strong Opinions, Loosely Held

The best engineers I've worked with share a specific trait. They have strong opinions, and they'll argue for their approach. But they listen to the counterargument. They update their thinking when presented with new information. And when the debate isn't going anywhere, they pick whichever option ships faster and move on.

They don't confuse preferences with principles. They know the difference between "I like this approach better" and "this approach will cause real problems." And they spend their energy on the second category.

I'm still working on this myself. I still catch myself getting pulled into debates that don't matter. But I'm getting better at noticing it earlier, at asking "does this actually matter?" before I write the comment. Sometimes the answer is yes, and the debate is worth having. Most of the time, the answer is no.

The code doesn't care about your preferences. The users don't care about your preferences. Ship the thing. You can always refactor later if you were wrong. And honestly, you probably won't need to.

Share
X LinkedIn HN
UI

Umur Inan

Principal Software Engineer

Backend engineer focused on JVM systems, distributed architecture, and the failure modes that only show up in production. I write about what I learn building and breaking things at scale.

👁 0 10 min read

Comments (0)