Writing /
Competitive programming made me a worse engineer, then a better one
At 2:40 in the morning, in the middle of a Tokyo winter, my phone made the noise it only makes for one reason. Our rate limiter was rejecting traffic it should have been letting through. Not all of it. Maybe four percent, which is the worst possible amount, because four percent looks like weather until a customer notices it’s always the same four percent.
I’d written that rate limiter eleven weeks earlier. It was Rust, it was fast, and I was proud of it. It took me forty minutes at my kitchen table to work out what past-me had meant.
The function was called tk. The locals were n, k, w, hi, and lo. There was a loop I’d folded two other loops into, because folding them was cute and saved a pass, and the fold was wrong at exactly one boundary: when the bucket index rolled over, w briefly meant window width in one branch and window start in the other. I’d reused a variable for two meanings, four lines apart, and it had held up under load for two and a half months before finding the one clock alignment where it didn’t.
I want to be honest about the shape of that night. The bug wasn’t hard. Finding it was hard, and it was hard because of me.
The part where I say the credentials out loud, once
For context, and I’ll only do this once: I took contests seriously for years. LeetCode has me somewhere in the global top 700, five stars on CodeChef, six stars gold on HackerRank. Saturday nights in my twenties meant a 9pm AtCoder round with the heater on and a can of coffee going cold next to the keyboard. I loved it. I still do.
I bring it up because it’s the cause of the story, not the point of it. That rate limiter was contest code wearing a production badge, and I didn’t write it that way by accident. I wrote it that way because for years, writing that way had been the correct answer.
The worse phase
Contests optimize for one thing: correct output, fast, once. Nothing about that rewards a reader. Nobody maintains a contest submission. Nobody gets paged by it. There is no second reader, ever, so every habit that helps a second reader is dead weight during the round, and if you spend enough Saturdays training those habits out of yourself, you don’t get to keep them on Monday.
So my first production year looked like this.
One-letter variables everywhere, because in a contest the whole solution fits on one screen and the names are noise. Cleverness as a reflex rather than a decision: I’d reach for a bitmask where a match statement would have done, not because I’d weighed it but because bitmask was the shape my hands made. Optimizing before profiling, always. I hand-rolled a hash map early on because the standard one “felt slow,” and when I finally profiled the service months later, the entire map accounted for something like a third of a percent of wall time. The actual cost was in a JSON round trip I hadn’t looked at, because it was boring and I wasn’t interested in boring.
Code review was the worst of it. I treated review like a speed run. I was the fastest reviewer on the team and I was quietly proud of that too. Ninety seconds, approve, next. Contests train you to read a solution for whether the approach works, and that’s genuinely a useful read, but it is not the same read as “will this be true in six months when a different team doubles the tenant count.” I was answering a question nobody had asked me.
And underneath all of it, a taste problem. If a for loop and a dictionary solved something, I felt like I hadn’t done my job. I had spent years being rewarded for finding the non-obvious path. Production mostly wants the obvious one.
There’s a deeper mismatch I only understood later. A contest hands you a stated problem, with constraints printed right there: 1 <= n <= 100000. Production hands you a Slack message that says “checkout feels slow for some people?” Contest skill is solving stated problems at speed. The first half of the actual job is that nobody has stated the problem yet, and the constraints are somebody’s undocumented assumption from 2019.
The turn
Three weeks before that page, a senior engineer had left a comment on a different PR of mine. Not a nit, not a style complaint. She wrote: “I believe you that this works. I don’t believe I could fix it at 4am.”
I remember being annoyed. I remember thinking, mildly, that this was a skill issue on her end.
Then I sat at my kitchen table at 2:40am, unable to fix my own code, and the comment came back with full force. She hadn’t been talking about her. She’d been describing a property of the code, and the property was real, and the person it eventually cost was me.
That was it. That was the whole conversion event. No reading list, no framework. One code review comment I’d dismissed, cashed in at the worst hour of the night.
What contests actually left behind
Here’s the part I didn’t expect: once I stopped writing like a contestant, what remained turned out to be most of the value, and none of it was the syntax.
Complexity instincts fire on their own now, and they fire during review, which is where they’re worth something. Somebody opens a PR with a loop over services inside a loop over deploy targets, and it reads perfectly fine at the forty services we had when it was written. My eye snags before I’ve consciously done anything. It’s the same reflex that told me a contest solution would time out at n = 100000, except now n is a tenant count in someone’s growth deck. I’ve caught more quadratics in review than I ever wrote in a contest, and catching one before it ships is worth more than any submission I ever made.
Invariant thinking transferred almost intact. Contests make you state what must be true at the top of the loop, because that’s the only way to be confident without tests. That’s exactly the muscle you need for an optimistic check-and-commit retry, for SLO burn rate math, for reasoning about a reconcile loop that’s allowed to run twice. And it changed how I write postmortems. Not “the service went down.” Which invariant broke, who was relying on it, and where was it written down. Usually it wasn’t.
Clock-calm is the one I’d have laughed at if you’d predicted it. An incident is a contest where the input is your own infrastructure. Two hours in, six people in a channel, an executive asking for an ETA, and the thing I have is a hundred rounds of practice at being under a clock without panicking into a rewrite. The specific contest habit that saves me most: when you’re stuck, stop typing and reread the problem statement. The incident version is stop typing and reread the alert. The number of times the alert was already telling me the answer while I theorized past it is embarrassing.
Reading unfamiliar code fast came free with the hobby. Contests mean reading other people’s solutions constantly, in styles you’d never choose, with no comments. On call that becomes: open a service you have never touched, at 3am, and find the state and the loop and ignore everything else. I’m not smarter than my colleagues at this. I’ve just done more reps of it in hostile conditions.
What it never taught me
Naming things. Contests actively de-trained it, and I had to learn naming from scratch, in public, badly, in code reviews where people were kind about it.
Tolerance for boring code. This took longest. Boring code isn’t a compromise you make on the way to good code, it’s most of what a working system is made of. The dictionary and the for loop are usually correct and the reason they’re correct has nothing to do with performance.
Deleting code. Contest code is disposable, so you never practice removal, you just close the tab. Production deletion is a social act. You have to find who depends on the thing, prove the traffic is gone, wait a release, and argue for it in a meeting. Nothing about ten years of contests prepared me for the fact that the hardest part of removing a system is other people’s memory of it.
So: do it, then hide it
I’d still tell a junior engineer to grind contests. Genuinely. Nothing else I’ve found builds algorithmic intuition that cheaply, and the clock-calm alone has paid for itself several times over in incidents. Skipping it because “you’ll never write a segment tree at work” misses what you’re actually buying.
But then do the second half, which nobody tells you about: learn to hide it. The instincts are the asset, the fingerprints are the liability. Nobody on your team should be able to tell from your code that you were fast once. They should only notice that your reviews catch things, that you don’t spiral at 3am, and that your variables have names.
Mine are called window_start now. It took a 2:40am page to get there, and that seems like a lot of ceremony for one rename, but here we are.