For the first few years I thought code review was about catching mistakes. You read someone's diff, you find the bugs, you point them out, they fix them. A quality gate. Useful, a little adversarial, basically a spellcheck for logic.
I don't think that anymore. Somewhere across a few years of reviewing other people's work, mentoring engineers earlier in their careers & sitting on the other side of the table evaluating interns, review stopped being about finding bugs and became something closer to the main way a team thinks together. The bugs are almost a side effect. The real thing being built in a review is shared understanding — and that turns out to be a completely different skill from writing code well.
The question that reframed it for me
The shift happened when I stopped asking "is this correct?" as I read a diff and started asking "will the next person understand this in six months?" — and often the next person is the author, who won't remember why they did any of it.
Those two questions pull in different directions more often than you'd expect. Code can be perfectly correct and still be a small time bomb: a clever one-liner that takes ten minutes to decode, a name that means something specific to the author and nothing to anyone else, a piece of logic whose why lives entirely in the author's head and nowhere in the file. None of that fails a test. All of it costs the team later. Once I started reviewing for the six-month question, I caught a whole class of problems that "is it correct" sails right past.
Why the best comments are questions
Early on, my review comments were commands. "Rename this." "Extract this into a function." "This should be memoized." Technically fine & they aged badly.
Two things were wrong with the command style. First, sometimes I was the one missing context — there was a real reason the code was the way it was & my confident instruction just made me look like I hadn't read carefully. Second, a command shuts down the conversation. The author either complies or argues; either way nobody learns anything.
A question does more work. "What happens here if this array is empty?" lands better than "handle the empty case," and it lands better for a reason: if there's a real bug, the author finds it themselves, which teaches far more than being handed the answer. If there isn't — if they'd already handled it upstream — I learn something instead of having planted a wrong instruction in the record. The question respects that the author might know something I don't & it turns the review into a conversation between two people figuring out the code together rather than one person grading another.
Separating what blocks from what I'd merely prefer
Here's a distinction that, left unmade, quietly poisons a team's relationship with review: the difference between "this is broken" and "I'd have written it differently."
A comment that says the auth check is missing and a comment that says you'd have named the variable differently are not the same species & treating them as equal is corrosive. When every note carries the same weight, the author can't tell the load-bearing feedback from your personal taste, so either they treat everything as blocking and drown, or they treat everything as optional and miss the one that mattered.
I got a lot more useful to work with once I started labeling my own comments. A blocking issue is stated as blocking. A preference is stated as a preference and explicitly marked as the author's call — "non-blocking, take it or leave it." That one habit does more for review culture than any tooling. It tells the author exactly where the real line is & it forces me to be honest with myself about how many of my comments were genuinely important versus just how I happen to like things.
What reviewing junior code showed me about myself
Reviewing code from engineers earlier in their careers is a strange mirror. You watch someone do something the long, explicit, slightly clumsy way — and about a third of the time you realize the long way is actually clearer than the clever shortcut you'd have reached for on autopilot.
Junior code asks "why" out loud. It doesn't yet know which conventions are load-bearing and which are just habit, so it questions all of them by accident. A lot of my own "best practices" turned out, under that innocent scrutiny, to be things I did because I'd always done them, not because they held up when someone made me explain them. Reviewing people who hadn't absorbed my habits yet was one of the better ways I found to notice which of those habits were real.
How it leaks back into your own code
The strangest effect is that reviewing enough code quietly rewrites how you write it. You start drafting a function and hear the review in your head before you've finished the first line. Someone's going to ask what this handles when the input's empty. Someone's going to ask why this is here. That name won't mean anything to the person who reads it next. So you answer those questions in the code before anyone has to ask them.
It's the most efficient feedback loop I know of, precisely because it eventually stops needing the reviewer. The comments you've received and the ones you've written enough times become the voice you edit against on the first draft.
Where review stops
I don't want to oversell it, because a team that over-trusts review makes a specific mistake. Review is a narrow-window tool. It sees a diff — this change, against this moment of the codebase — and it's excellent at that scale and blind past it. It won't tell you that the whole approach is wrong, that the feature shouldn't be built this way, that the architecture the change fits neatly into is itself the problem. By the time something reaches review, most of the important decisions have already been made.
Those decisions belong to conversations that happen before anyone writes code — a whiteboard, a design doc, five minutes at someone's desk. The teams I've enjoyed working on most treated review as one link in that chain, not the whole chain. Review catches what slips through. It was never meant to be the only net & asking it to be is how good code gets shipped in service of a bad idea.