I once spent weeks parallelizing a cron job so it could hit all of our databases concurrently instead of one at a time. We cut its runtime by two hours. But every time someone needs to modify that code, they lose four hours getting up to speed—and because we now run the job every hour instead of every three, we pay that complexity tax three times as often. I’d saved the machine two hours and cost my team far more. That’s when I realized I’d been optimizing the wrong thing.
Introduction
I used to feel a certain satisfaction from writing clever code. Not because I believed clever code was good code, but because solving a complex problem correctly, especially one that requires understanding recursive CTEs, expression trees, or concurrent patterns, is genuinely difficult. When you finally get it right, there’s a real sense of accomplishment. You’ve bent your brain around something hard, understood the implications, and made it work.
That pride, though, never accounted for what happened next.
Over the past couple of years, particularly while doing performance work, I started seeing a pattern. I’d be debugging some production issue or trying to understand why a system was slower than it should be, and I’d find it: a piece of clever code that was the root cause. Not because the code was wrong, but because even for the person who wrote it, the side effects and full implications weren’t fully understood. The complexity had hidden the problems.
That’s when I had to confront something uncomfortable: the pride I felt for writing clever code had never been weighed against the cost of maintaining it, debugging it, or discovering months later that it was causing performance problems nobody anticipated.
And here’s the shift that seniority forced on me: solving a complex problem is worth celebrating. But solving it in a way that’s maintainable, understandable, and measurable—where the performance gain actually justifies the complexity cost—that’s what deserves real pride. One requires intelligence. The other requires judgment. And the second one matters more.
The Parallelization Decision
A few years back, our company was consolidating teams across departments, and I wanted to show what engineering could contribute. At the time, we had a data sync system that unified account information, user details, and metadata across six acquired products, funneling everything into Zendesk. Our customer support reps needed this data to work effectively, and the system was the backbone of their operations.
The setup was straightforward: a cron job that connected to databases from each product sequentially, pulled the data, and wrote it to a staging database. Then a series of asynchronous lambdas would handle the rest. Originally, it ran once a day. That was slow. When the system failed, support would be behind by days. A customer who’d just been purchased could take a week to get their account set up in Zendesk, which meant support couldn’t do their job on day one.
In 2019, we’d improved it to run every three to four hours. That helped. But one of our products had horizontally sharded databases with significantly more data than the others. The parallelization bottleneck was hitting that product sequentially, one shard at a time. The sync was taking three-plus hours, and the lag was still hurting support’s ability to onboard new customers quickly.
So I proposed we parallelize it. Instead of hitting each database sequentially, we’d hit them all concurrently. In theory, we’d cut processing time dramatically. My manager agreed. We invested the time, built it out using concurrent collections and async/await patterns in C#, and shipped it.
It worked. We went from three-plus hours down to about eighty-two minutes. That was real. That was measurable. That felt like a win.
At the time, this felt like the cost of writing highly performant code. If you want speed, you need complexity. You need to understand concurrent state, thread safety, and potential race conditions. That was just the trade-off. And for a system syncing data to Zendesk, it felt justified.
Because we could now sync every hour, we synced every hour—my suggestion, too. I thought faster data meant better support, and technically, it did. What I didn’t account for was how much harder every feature request in that area would become.
Concurrent collections and async/await patterns aren’t trivial. Developers had to reason through thread safety, race conditions, and shared state. Every so often, the code still hit a concurrency issue and failed to pull some data. Every time someone touched it, they lost hours getting up to speed. Code reviews got longer. Debugging got harder. Simple changes took days instead of hours.
Everyone knew complexity was the price you paid for speed, right?
It took me years to realize I’d gotten that equation completely backwards.
The Pattern
The parallelization story isn’t unique. It’s a pattern I’ve seen repeatedly, and honestly, a pattern I’ve contributed to.
I’ve encountered massive stored procedures in data jobs, thousands of lines long, where one developer tried to solve everything in SQL. I’ve written recursive CTEs with pivot tables into our applications, trying to handle complex data transformations in a single query. In both cases, the thinking was the same: if I solve it here, in one place, it’ll be more efficient.
The reality was the same too. Every time someone needed to modify one of those queries, they faced a wall of complexity. Understanding the execution plan took hours. Making a change safely took days. Nobody wanted to touch it, so bugs would sit in the code for months because the cost of fixing them was higher than living with them.
And then there were the Expression Trees: LINQ predicate building by hand, deeply nested OR statements constructed manually, when the ORM could have handled it in one line.
In each case, I didn’t think “I’m doing something wrong.” I thought “I’m solving this efficiently.” It wasn’t until years later, when I kept running into these patterns over and over, that I realized: the efficiency wasn’t in the machines running the code. The inefficiency was in the humans trying to understand it.
Why Smart Developers Write Clever Code
I didn’t propose the parallelization to look smart. I proposed it because I genuinely wanted to help our support team. But the way I thought about the problem, and the solution I chose, reveals something about how engineers approach complexity.
Here’s the thing: writing clever code isn’t stupidity. It’s usually a combination of genuine problem-solving instinct and imperfect information. You see a hard problem, you solve it, and the satisfaction of solving something difficult is real. Concurrent code, recursive queries, expression trees—these things require you to think deeply and get the details right. When you do, there’s legitimate accomplishment there.
But without measurement, without actually calculating ROI, it’s impossible to know whether the accomplishment was worth it. And that’s where organizations fail. We don’t measure the benefit of the optimization. We don’t measure the cost of maintaining it. So everything becomes a gut feeling.
I’ve noticed a pattern in how developers relate to complexity over their careers. Juniors are focused on making it work. They’re learning the language, learning the patterns, trying to solve the problem. Complexity happens, but they’re not necessarily aware of it. They’re just trying to make something functional.
Mids become aware of complexity. They know recursive queries are harder to understand than simple ones. They know concurrent code is trickier than sequential code. And that awareness often becomes something they take pride in. It becomes a way to demonstrate their chops, to show they can handle sophisticated solutions. That’s when the ego piece starts to show up.
Seniors and staff start to understand that simplicity and optimization aren’t opposites—they’re aligned. The truly optimal solution, long-term, is the one that’s simple enough to modify when requirements change. The one that doesn’t require four hours just to come up to speed. The one that doesn’t require a PhD in concurrent programming to touch.
I was a mid when I proposed the parallelization. I saw a complex problem and wanted to demonstrate that I could solve it at that level. And I did. The code worked. The optimization was real. But I’d optimized for the wrong thing, and nobody, not my manager, not the business, not me, had the data to understand what that actually meant.
The Hidden Cost: Iteration Velocity
The problem with optimization is that the benefit is always visible, and the cost is always invisible.
When we parallelized that sync system, everyone saw the result: 82 minutes instead of 180. That’s measurable. That’s real. That goes in the status update.
What nobody measured was what happened next.
Feature requests started coming in for that system. Bug fixes. Small tweaks. Each one required someone to dive into concurrent code, think through thread safety, understand shared state. Code reviews on those changes didn’t get faster. They got slower. But nobody was tracking that.
When the concurrency bugs started showing up, they were subtle. A sync would fail to pull a customer’s metadata, which meant their data wouldn’t be in Zendesk until the next run. No alarms. Just silent failures that might get noticed, or might not. Each bug required someone to understand why threads were accessing state in an unexpected order.
There was no dashboard tracking “hours spent understanding this code per change.” There was no metric on “code review time for concurrent systems versus simple systems.” We never calculated how many developer hours it took, in aggregate, to maintain this optimization.
That’s how invisible costs hide. They accumulate slowly. Nobody feels any single one of them. You just notice, eventually, that people are avoiding the code. That estimates are higher than they should be. That features you could implement in a day take a week.
And by then, the code is so embedded in the system that removing it feels like a bigger problem than just living with it.
The real equation is simple:
Optimization value = Machine time saved - (Human time cost × number of iterations)
If you iterate frequently, human time cost dominates. And most teams iterate far more than they realize.
When I Realized: The Inflection Point
I spent the last two years doing performance work. A lot of it. And something became undeniable: almost every performance problem I actually solved was solved simply.
An endpoint making ten database calls when it could make one. That’s architectural. Fix it, and suddenly you’ve got a 10x improvement.
Lazy loading kicking in when you didn’t expect it. That’s architectural. Load what you need upfront, and the problem disappears.
Data that never changes but you’re fetching it every request. That’s architectural. Cache it, and you’ve eliminated the bottleneck.
None of these required complex code. None of them required me to understand concurrent state or advanced LINQ patterns or recursive CTEs. They required stepping back and asking: “Are we doing something we don’t need to do?”
And that’s when I realized what I’d actually done with the parallelization. I hadn’t solved a performance problem. I’d brute-forced around an architectural one. The real issue was that the system was forced into full syncs every run. We were syncing everything, all six products, all their data, every single time. That was never going to be fast, no matter how cleverly we parallelized it.
The simple answer would have been: change the architecture to sync only what changed. But that was harder. That required rethinking how the system worked. Parallelizing the full sync was the easy way out. It gave the appearance of solving the problem without actually addressing the root cause.
Looking back now, that’s the pattern I see everywhere. We see a performance problem. We assume it requires complexity. We reach for advanced features, concurrent code, clever algorithms. And we miss the architectural issue that could be fixed in a fraction of the time with a fraction of the code.
The real inflection point wasn’t a single moment. It was the accumulation of two years of performance work where the answer was almost always simpler than I expected. And understanding that meant rethinking everything I thought I knew about optimization.
The Senior Lesson: Optimize for the Right Metric
Here’s what changed in how I think about optimization.
Early in my career, I thought performance meant CPU usage, memory consumption, query execution time. Throughput. Machine-level metrics. Those are real, and they matter. But they’re not where the bottleneck actually is.
The slowest part of your system is the human brain trying to understand the code.
Most companies don’t operate at the scale where extreme performance optimization is necessary. We’re not optimizing for milliseconds because we’re serving billions of requests. We’re writing business software. And in business software, the cost of maintaining code dominates the cost of running it.
Maintenance is harder than writing, which means understandability matters more than cleverness. SOLID principles matter more than advanced language features. Simplifying the call stack, making fewer roundtrips, batching operations, letting your database and APIs do what they’re designed to do—that matters more than any clever optimization you could write.
But here’s the thing that really compounds the problem: most of the code in our applications is old. A decade old, sometimes more. Written without tests. Without automation. Which means when someone needs to modify something, the testing effort is astronomical. They’re terrified to touch it because there’s no safety net. They can’t easily verify they didn’t break something.
So what happens? They avoid the code. And if the code is complex on top of that? They avoid it even harder.
That’s why I started optimizing for iteration velocity, not machine velocity.
So when I see a performance problem now, I don’t reach for complexity. I ask: Are we making unnecessary calls? Can we batch this? Can we simplify the call stack? Often, the answer is yes. And the fix is straightforward. And the code is simpler. And somehow, mysteriously, it’s also faster.
BuildContainsExpression: A Concrete Example
If you read my December post on hunting an 8-year-old stack overflow bug, you know about BuildContainsExpression. It’s a perfect case study of the pattern I’m describing here.
The parallelization and BuildContainsExpression share the same DNA:
Assumed complexity equals performance. We looked at the code and thought: “This must be optimized somehow. Why else would someone use Expression Trees to build deeply nested OR statements?” The complexity had to mean something.
No measurement of the benefit. We never benchmarked it. We never asked: “Is this actually faster than what the ORM can do?” We just assumed.
Hidden until it breaks. Like the parallelization code, it was tucked away in a utility class, doing its job invisibly for years. Until scale happened and it caused stack overflows in production.
The cost of fixing it was trivial. The cost of understanding it was enormous. The actual fix took twenty minutes. The investigation took days. And for years before that, anyone who touched the code in that area was working in the dark.
That’s the universal pattern. We see a complex problem. We assume it requires complex code to solve. We reach for advanced features. And we never measure whether the benefit justifies what we’ve created.
The difference between the parallelization and BuildContainsExpression isn’t that one was right and one was wrong. It’s that BuildContainsExpression was so obviously a mistake that when it failed, the failure was dramatic. The parallelization was subtle enough that the failure took years to recognize.
Both were optimizations that weren’t worth their cost.
Permission: You Don’t Have to Be Clever
Here’s what I wish someone had told me earlier: you don’t need to be clever to be good.
The biggest mistakes I’ve made came from not prioritizing readability, simplicity, and performance over complexity. And not considering the return on investment. I thought complex code was the sign of a good engineer. I was wrong.
Writing simple code is harder than writing clever code. Not because clever is harder to write—but because simplicity forces you to be intentional about clarity. You have to write it so any developer, looking at the method name and code structure, can understand what it does without hours of reasoning. That requires thought. It requires design patterns. It requires discipline.
I see junior developers trying to make things work. They’re not trying to be clever; they’re just trying to get something running. But “working” and “working well” are different things, and the gap between them is where you learn the real skill.
Here’s the permission I’m giving you: boring code is good code. Senior engineers write boring code on purpose. Not because we can’t write complex code. We write boring code because we understand what it costs.
When you’re writing code, ask yourself:
- Will I understand this in 2 years?
- Will my team understand this?
- Can someone modify this without fear of breaking something?
If you can’t answer yes to those questions, simplify it. Not later. Now.
Don’t reach for advanced language features because they feel impressive. Use them when they solve a real problem and you’ve measured the benefit. Don’t optimize early. Don’t assume complexity means performance. Don’t try to prove your intelligence through code—prove it through your judgment.
The smartest thing you can do is write code that doesn’t need to be clever.
The Framework for Future Decisions
Before you propose an optimization, before you reach for that advanced feature or complex pattern, ask yourself these questions in order.
Measure first. Do we actually have a problem? Don’t assume. Profile it. See where the real bottleneck is. Most of the time, you’ll find the performance issue isn’t where you thought it was. You don’t need a rigorous benchmark. You just need to know: is this actually slow?
Measure the right thing. Most companies don’t measure performance well anyway. But even without perfect measurement, you can estimate: how long does a feature take to implement now? How long would it take if we simplified the code? That comparison matters more than CPU milliseconds. And you can always ask: “Is this a machine problem or a human problem?”
Understand the cost. How will this optimization affect iteration speed? Will developers be able to modify it? Will code reviews be straightforward? Will new team members understand it? Be honest about the answer.
Is the math worth it? Even if you can measure the benefit, does it justify the cost? If your optimization saves two hours of machine time per run but costs four hours of human effort every time someone touches the code—and you touch it often—the math doesn’t work. Don’t assume it does.
Document why. If you do optimize, write it down. Explain the justification. Not for other people. For your future self. So two years from now, when you’re trying to modify it, you understand why it was built that way.
Here’s the rule I use now: If you can’t measure the benefit, don’t do the optimization. If the human cost is higher than the machine benefit, don’t do the optimization. If the next engineer won’t understand it, simplify it.
Simple as that.
What Changed in My Thinking
When I started, my primary question was simple: “How can I make this faster?” Not because I was obsessed with performance. Because delays in the sync meant escalations from support about missing data. The faster the system ran, the fewer tickets I’d receive. That was the equation.
I wasn’t balancing it well with other considerations. I just saw speed as the solution to support pain.
The appeal of advanced features was learning. I’d never written concurrent code before. I wanted to become a more senior engineer, to tackle larger problems. Concurrent patterns felt like proof that I’d leveled up.
Over time, that changed. The question shifted when I started getting pulled into larger problems and realized something: we needed resolution, not complexity. We needed things to work. I could be clever, or I could be effective. Those weren’t the same thing.
Now, my primary question is: “How can I make this understandable?” Because understandable code is debuggable code. It’s maintainable code. It’s resilient code.
What counts as the simplest solution depends on context. But I’m asking different questions now. Will my team understand this? Will I understand it in two years? Can we modify it safely?
Boring code looks like code we’ve already seen. It’s not fluffy or interesting or using the latest technology. It’s the tried-and-true path to implement the desired functionality. People find that boring. But boring is performant. And performant means I spend less time troubleshooting.
There’s still a tug. I still feel the pull toward solving complex problems in complex ways. But I’ve been pulled more toward wanting products that work. I don’t need clever code to get there. I need resilient code, performant code, error-free code.
I used to try to prove my intelligence through code. Now I’m trying to prove my judgment. Picking the right tradeoff—understanding what actually matters—is harder than just being clever. It’s also more valuable.
Call to Action
The next time you’re reviewing code, or writing code, or sitting with a piece of your codebase that makes your team’s eyes glaze over, ask yourself: Is this complexity justified by the benefit?
Look for the places where code is harder to understand than it should be. The recursive queries, the concurrent patterns, the advanced language features that nobody on the team really understands. Ask: What value are we getting from this complexity? And what are we paying for it?
Measure the human cost, not just the machine cost. How long does a feature request in this area actually take? How many hours do we lose in code reviews trying to understand what’s happening? How many times does someone say “I’m not touching that code” and work around it instead?
If the answer is “the complexity isn’t worth it,” simplify it. Not eventually. Now. And when you write new code, ask the same questions upfront. Don’t optimize early. Don’t assume complexity means performance. Don’t use advanced features to prove you’re smart.
Write boring code on purpose. The kind of code that any developer, when they look at it, understands immediately. The kind of code that’s easy to modify, easy to debug, easy to live with.
You don’t need to be clever to be good. You just need good judgment.
Next month, I’ll write about Entity Framework queries, and the patterns that make them either a joy or a nightmare to maintain. Same lesson, different context.