{"slug": "reviewing-code-is-a-skill", "title": "Reviewing code is a skill", "summary": "Code review serves multiple purposes beyond finding defects, including education, maintaining norms, gatekeeping, and accident prevention, according to research on Google developers and a 2013 study. The author argues that reviewing code is a skill that can be taught and improved, and that it remains valuable even as LLMs are claimed to be better at finding bugs.", "body_md": "# Reviewing code is a skill\n\n**Intended audience:** Software developers interested in getting better at developing software. Initially, I meant this post more for junior developers, but some parts are more applicable for tech leads etc. So if the post appears a bit muddled, please accept this apology from my inner Pascal, “Sorry, I did not have the time to break it up into two posts.”\n\nIn developer communities, there has been a lot of discussion\non code review, especially over the course of 2025-2026.\nFor example, you may have seen claims like:Not necessarily all from the same people.\n\n- “Code review is the bottleneck”\n- “Mandatory pre-merge code review is for low-trust environments; you should push directly to main”\n- “Code review doesn’t find bugs”\n- “Code review is not for X, it’s for Y”\n- “LLMs are better than humans at reviewing code”\n- “LLM code reviews are much better than humans at finding edge cases/bugs”\n- “You should stop looking at the code; you should be doing XYZ instead”\n\nand so on.\n\nOut of this, zooming in on “Code review is not for X, it’s for Y” for a moment, the research has the following to say:\n\nBy coding our interview data, we identified four key themes for what Google developers expect from code reviews: education, maintaining norms, gatekeeping, and accident prevention. Education regards either teaching or learning from a code review and is in line with the initial reasons for introducing code review; norms refer to an organization preference for a discretionary choice (e.g., formatting or API usage patterns); gatekeeping concerns the establishment and maintenance of boundaries around source code, design choices or another artifact; and accidents refer to the introduction of bugs, defects or other quality related issues.\n\nSimilarly, [Expectations, outcomes, and challenges of modern code review](https://d1wqtxts1xzle7.cloudfront.net/123087856/icse2013-libre.pdf?1748877378=&response-content-disposition=inline%3B+filename%3DExpectations_outcomes_and_challenges_of.pdf&Expires=1786290544&Signature=R5O-pbRUD~ZBlCXj7MKPrQovHnP94AzTDdRaHH7OMLzSz-rElLhqbwYRJrN76q2ooGx6cRtl5laCF1fMf-rp3~CuR9vSOY2zWA20TuZCrQueownjjFMsMwMk16RgdsSgAPxtA~TXCOD06HqAo~wYeETeJW-l9gbRgbaFI2caM7pxbZM3eY2TvPQi54EAeymv3OyIylije4xUtlaoa9b7bwSfxSHwvxkaS2alYSqVu3HO1iZMTTQs9djOzEV3J8yGoJ26-PFhhwSUG1HIawGy6iGgOPur5Ue-EMWoLa85l5zGzh0jBdyJcf7VAaDIbU9wC4K~yXh9GzMocHpXV0YHDw__&Key-Pair-Id=APKAJLOHF5GGSLRBV4ZA) (2013) points out:\n\nOur study reveals that while finding defects remains the main motivation for review, reviews are less about defects than expected and instead provide additional benefits such as knowledge transfer, increased team awareness, and creation of alternative solutions to problems. Moreover, we find that code and change understanding is the key aspect of code reviewing and that developers employ a wide range of mechanisms to meet their understanding needs, most of which are not met by current tools\n\nSo at least, hopefully we can agree that code review serves many purposes.\n\nI will get to the other points in a bit.\n\nBut before that, I want to articulate a framing which I haven’t seen much elsewhere.\nNamely, that *reviewing code is a skill*. Specifically, I posit that:\n\n- It’s possible to get better at reviewing code. By “better”, I mean across all of the purposes above: catching bugs, catching design issues, increasing one’s awareness of what’s going on, and understanding the code.\n- It’s possible to teach someone to get better at reviewing code.\n- By virtue of it being a fairly modern skill, we don’t quite know where the human skill ceiling lies (e.g. what is the Pareto frontier in terms of speed vs quality?).\n- If you’re a software developer, and if you believe that people\nwill continue to be involved in the development and maintenance\nof programs for the foreseeable future, then it’s\n*valuable*to get better at reviewing code.\n\nFirst, I will give three small examples taken from the past few weeks of work of me finding bugs when reviewing code. I’ve specifically chosen bugs for discussion because they’re relatively unambiguous.\n\nNext, I will provide some of my own historical background related to code review, and some arguments in favor of the core thesis.\n\nAfter that, I discuss some ideas for experimenting with and improving code review.\n\nFinally, I discuss the aforementioned oft-repeated memes around code review and how they hold up to scrutiny with the thesis in mind.\n\nLet’s get started.\n\n## A tale of three almost-introduced bugs\n\nA bunch of details have been elided in the course of explaining the different examples below, in an attempt to make them digestible. It may be valuable to keep an eye on stray thoughts of the form, “well, that seems like a code smell, no wonder you almost had that bug” or “duh, this could’ve been avoided with XYZ”.\n\nIn two out of the three cases, the person who wrote the PR had experience with the surrounding code.\n\nThe other thing to note is that LLM reviews with a mixture of high-end coding models (around Jun 2026) were run for all of the PRs described below. They did not catch the issues that I caught.\n\nIt may be helpful\nto consult this handy table from Lorin Hochstein’s\n[Traditional versus resilience engineering views](https://surfingcomplexity.blog/2026/08/02/traditional-versus-resilience-engineering-views/):The post is short and recommended reading. The table below is a subset of the one in the original post.\n\n| Traditional view focuses on | Resilience engineering view focuses on |\n|---|---|\n| objectives | production pressure |\n| reducing complexity | navigating complexity |\n| root cause | interaction of multiple factors |\n| human variability as liability | human variability as asset |\n\nOne way to read the cases below is to try to think about both sides of the table as you read them.\n\n### Writing some git configuration\n\nWe use our own devboxesDisposable VMs used for software development.\n\nat `$WORK`\n\nwhich run on EC2 instances.\nThe logic for starting things has two subprocesses:\n\nOne background process, to initialize state which isn’t needed right away. This process may or may not have finished by the time the user starts using the devbox.\n\nOne foreground process, which needs some extra data from the user’s laptop, and blocks the user until it’s done. Only after this process finishes can the user start using the devbox.\n\nTo reduce latency, we’ve been working on\nmoving more operations to the background process.\nIn this spirit,\none of my colleagues created a PR to move some of\nthe modifications to the global `~/.gitconfig`\n\nIn the interest of performance and consistency, we want to manage some aspects of people’s Git configurations. Yes, I know Nix exists, I use it for one of my own servers. No, we don’t use Nix at work. You’re keeping a check on your intrusive thoughts, right?\n\nfrom the foreground process to the background process.\n\nSo when a `git config`\n\ncommand needs to modify `~/.gitconfig`\n\n,\nit first acquires an exclusive file lock on `~/.gitconfig.lock`\n\n.\nThis prevents concurrent modifications from other (cooperating)\nprocesses, such as other `git config`\n\ncalls.\n\nWhen I saw the PR, I remembered we’d hit non-determinism in the devbox setup, where git’s behavior of fast-failure on lock acquisition failure led to flakiness during startup due to a similar problem of concurrent writes.\n\nJust retrying on top would’ve still led to non-determinism, so my colleague added a cross-process dependency edge using an existing mechanism, where the write would only happen after the background process finished.\n\nThen I pointed out that we’d actually tried that earlier,\nbut we’d gotten rid of that almost immediately,\ndue to the increase in end-to-end latency\n(because now a subpart of the foreground process\nwould have to wait for the *whole* background process\nto finish).\n\nIn the end, since we also had some file modifications which\ncouldn’t quite go through `git config`\n\ndirectly,Due to needing to reconcile `# DO NOT EDIT`\n\nblocks.\n\nwe converged on a solution which had a separate `flock`\n\noperation\n(with its own `.lock`\n\nfile). This allowed for (1) having retries\nwith backoff (2) doing multiple modifications under the same `flock`\n\nwithout other intervening writes\n(3) doing direct writes without worrying about concurrent writers.\n\n### To display progress or not to display progress\n\nThere’s a periodic CI job which does some processing\nand uploads a tarball to an AWS S3 bucket.\nTurns out, the `aws`\n\nCLI displays progress by default.\nPresumably, this is meant to help with debugging issues,\nas well as provide reassurance about progress\nwhen using the CLI directly in the terminal.\n\nWhen this colleague changed the logic to upload to\n4 buckets from 1 bucket to speed up downloads from other\nregions,AWS buckets belong to a specific region.\n\nthe CI job started failing,\ndue to the log file exceeding the limit of 10MB.\n\nTurns out, the `aws`\n\nCLI logs a line for every 256 KB uploaded.\nThis corresponds to tens of thousands of log lines when the upload is 10GB+.\nMultiplying the log lines by 4 tipped the job over the 10MB limit.\n\nThe colleague submitted a PR to change the invocations to\nuse `--no-progress`\n\nto unbork the CI job.\nAt the time, my first thought was, “Hmm, is this the only option?\nMaybe there’s a way to show fewer progress updates?\nIf the job fails mid-upload, it’d be nice to have some\nprogress information to make it clearer *when* the job\nfailed relative to the start of the upload.”\n\nSo I looked up the docs for the `aws`\n\nCLI, and saw a flag\n`--progress-seconds <INT>`\n\nwhich allowed tuning the frequency\nof progress updates. I asked the PR author if we could use\nthat instead.\n\nAt the back of my mind, I remembered a mini-incident\nwe’d had earlier, where a commonly used script had introduced\nan `aws`\n\nCLI flag, but not all environments the script ran in\nhad a sufficiently new CLI version which supported it,\nso the script broke for a bunch of people.\n\nSince the PR author had also seen that happen,\nI assumed that they would do the due diligence to check\nwhat version of the CLI was running in the CI job and\ncheck whether that CLI version supported `--progress-seconds`\n\n.\n\nThe PR author got back to me quickly and updated the PR to\nremove `--no-progress`\n\nand use `--progress-seconds`\n\n.\n\nI was a bit surprised at the speed, so I realized they might\nnot have thought of the same risk. First, I tried looking\nat the aws CLI changelog to see if it mentioned when the flag\nwas introduced (since the flag’s documentation didn’t have this\ninformation). It did not. Then I had LLM chase down\nwhat CLI version the CI job was using,\nas well as which commit introduced that flag in the `aws`\n\nCLI,\nand the first published release with that commit.\nThis uncovered the fact that the job would break\nif `--progress-seconds`\n\nwas used,\nbecause the existing CLI version in the job was too old.\n\nI did some spot checks on the commits and versions, and then commented on the PR with a brief apology for not stating my assumptions earlier, and asked if they could cross-check the findings/find an alternate solution.\n\nThe PR was later fixed by installing a sufficiently new\n`aws`\n\nCLI version that was already being packaged\nin another place in the repo.\n\n### The extra SHA that could’ve caused an outage\n\nRemember the CI job from the previous example?\nOne of the things I’ve been working on lately is decoupling\nthe release process of that job group from the overall\nCI release process.The CI system is complicated enough\nthat different jobs can have their own release processes.\n\nOne day, I hit a merge conflict while rebasing my WIP\nchanges on top of the `master`\n\nbranch. Turns out,\nsomeone had added new variations of the existing job.\n\nSo the job group is responsible for uploading certain tarballs. An engineer needed some more tarballs to be uploaded slightly differently. They’d created a separate job for that, instead of updating an existing job in the group.\n\nI was like, “hmm, why is this a different job, shouldn’t it involve adding one more element to a list somewhere?”\n\nTurns out, the reviewer for that job-addition PR had\nrecommended separating out the new job from the old one\nto start with to reduce risk with disrupting the existing job’s\nperformance, reliability etc. Additionally, one of\nthe “slight” differences was that the new job was uploading\nsidecar files with a checksum.If you’re reading this and going “but doesn’t S3 already natively support checksums”, I actually didn’t know about that until after all of this was done-and-dusted, and I’m guessing the original PR author didn’t either.\n\nI was confused looking at that. “If this job needs checksums, do the existing jobs need checksums? If so, why didn’t they make the checksum logic uniformly applicable to all jobs? Do we need to update the existing readers to check the integrity of the old tarballs? Are these new tarballs crossing some kind of trust boundary whereas the old tarballs were not?”\n\nThinking a bit more, I realized there was a bug.\nAbout a year back, I’d watched a presentation by\nProf. Hannes Mühleisen called\n[DuckLake - The SQL-Powered Lakehouse Format for the Rest of Us](https://youtu.be/YQEUkFWa69o?si=9om_lryDl57PBIno&t=919).\nIt was talking about Apache Iceberg and its use\nof files, which seemed very complicated.\n\nAt the time, my understanding was that some of this complexity was due to essentially not having a way to do multi-object transactions. And thus, one of the key design decisions for DuckLake was to have a SQL database (with ACID transactions) for maintaining metadata.\n\nComing back to the CI job, it was uploading the tarball\nto a fixed object name in a fixed bucket. This was uploaded\nbefore the checksum. So while S3 guarantees all-or-nothing\nsemantics for a write to an object,\nif the job was canceled or crashed before uploading the checksum,\nthen readers checking the integrity of the tarball\nagainst the old checksum in a fail-closed mannerThe usual way to use checksums.\n\nwould start failing, which would lead to an ~outage\nfor that particular feature until the tarball\nand checksums were back in sync.\n\nI pointed out this failure mode to the person who created the PR, as well as in the PR introducing the read path. In the end, the read path check for the sidecar file was not introduced.\n\n## The case for code review being a skill\n\nLet’s rewind the clock back a bit. To 2018.\n\nI was working as a graduate student in physics. The research I was doing involved various kinds of simulations. We used Jupyter notebooks for much of the work. The simulations would work one day, they’d stop working the next day, and the third day, they’d continue not working despite undoing the work from the second day.\n\nI was struggling with how to manage the code, how to make it efficient, and how to prevent mistakes from coming up, and how to use version control. At the time, I started learning about automated testing and code review. I recall being quite amazed, “Wait, what? People actually review all of the code, not just the outputs?! Wow, it must be nice to work as a software developer.”\n\nEventually, I got increasingly more interested in the process of writing software better, and more disenchanted with the physics I was supposed to be studying. I ended up dropping out of the PhD program in 2019, and started working as a software engineer.\n\nSince then until now, over the course of my career so far, there have been multiple times where someone have commented that they’ve found my code reviews more helpful than the average review they’re used to.\n\nAs one data point, before 2026, when reviewing a PR, depending on the author’s familiarity with how I review, the underlying code, prior discussions etc., my comment density on PRs has been in the range of 30-100 SLOC/comment. Not all of them were about bugs. Most were about more “boring” things like asking clarifying questions, naming, layering etc.\n\nSo why am I telling you all of this? Well, my point is that I learnt how to review code along the way. I was not magically born with some innate ability to do code review, or to catch bugs in code review.\n\nAre there aspects somewhat specific to me which help me in spotting bugs? Probably, yes. Some things I can think of:\n\n- When I discover a bug that I introduced earlier, I tend to take it more personally than other developers I’ve met.\n- I tend to think of programs in terms of\ninvariants and\n[little proofs](https://blog.get-nerve.com/to-be-a-better-programmer-write-little-proofs-in-your-head/). - I like reading tech blogs and watching tech talks, especially those with debugging stories and performance investigations.\n\nOut of these, I think the second point in particular is very learnable and teachable.\n\nStepping back a bit, I think there’s a *lot* we don’t\nreally know about code review. If you look at the literature\non code reviews, it’s generally pretty anemic.\n\nApart from doing more structured studies – which would be difficult to get buy-in for in a typical corporate environment – I think there’s a lot of room for trying out new practices related to code review, including at small scales, to make it more useful for authors, reviewers and the surrounding team.\n\n## Putting on the mad scientist hat for a bit\n\nIn this section, I want to suggest some potential ideas to improve upon (or at least, try variations of) code review as a practice. The purpose of writing these down is twofold:\n\n- To give you some inspiration for coming up with ideas of your own.\n- To give you some sense of how existing practices are lacking in particular aspects\n\nThe point is not “you should go try these out” (maybe you shouldn’t) or “I am certain that these will work well” (I’m not, because I haven’t had the chance to put them into practice).\n\nThe ideas below assume a strong foundation of [psychological safety](https://rework.withgoogle.com/intl/en/guides/understand-team-effectiveness),\nbecause if you don’t have that,\nyou should almost certainly be fixing that first\nbefore trying out more weird things.\n\n### Randomized process-oriented Socratic dialogues\n\nSay you have a junior engineer who is requesting a review from a senior engineer with expertise relevant to the code being changed.\n\nWhen the PR is up, and the review is requested, say a bot randomly decides (based on some tunable frequency) to create a meeting.\n\nIn the meeting, the senior person, instead of pointing\nout things which don’t quite make sense,\nor could be done differently, instead\nfirst asks the junior person for their perspective\non why they did things a certain way,\nor what assumptions they were making.It’s key to avoid counterfactual questions for why something was *not* done a certain way, or why the person did *not* think of something. This might sound easy, but if you actually try this, you’ll notice it’s quite hard to stick to this!\n\nDuring this process, a junior with cognitive agility will recognize gaps in their understanding, and potentially identify improvements they could make.\n\nThe focus would be on the *thought process* for\ndifferent sub-skills like writing a good PR description\nor robust error handling, with the PR serving\nas a foil to ground the discussion.\n\nSee also: [6 principles of Effective feedback](https://www.shadowboxtraining.com/news/2026/01/20/6-principles-of-effective-feedback/), §1. Effective feedback is based on process, not outcome.\n\n### Lightweight near-miss post-mortems\n\nToday, it’s increasingly more common to have post-mortems\nfor incidents. It’s also common to organize work into\nsprints and have team retro meetings at the end\nof a sprint.Also a skill that can be learned, improved upon etc.!\n\nWhat if, every time a bug was caught in code review during\nthe sprint, the PR author was asked to record a short clip\nexplaining the context and what the caught issue was,\nand if the kind of issue matched a past issue,\nwhat the impact of the past issue was.The wording here is very deliberate. I’m explicitly not mentioning “what the potential impact could’ve been had the issue slipped through” because that goes into the territory of speculation/prediction.\n\nDuring the team retro, the team could watch the clips together, ask questions and increase shared knowledge. This way, everyone on the team would also be exposed to a lot more bugs that almost slipped through.\n\nTo avoid having differences in frequency, you could limit this to at most one clip per person per meeting.\n\nSee also: [You’re missing your near misses](https://surfingcomplexity.blog/2025/02/01/youre-missing-your-near-misses/).\n\n### Firewalled modeling\n\nIn [Finding bugs without running or even looking at code (video)](https://www.youtube.com/watch?v=FvNRlE4E9QQ) (StrangeLoop 2019), Jay Parlar\ndescribes an example of him trying to build a model\nin Alloy for an access control system –\nwhich spanned multiple codebases, and where he hadn’t\nlooked at the code – and in the process of modeling,\nhe found both bugs and design issues.\n\nIn my own limited experience, lightweight formal methods seem useful when trying to reason about process semantics (involving cancelation, potentially indefinite waits, file locking protocols) as well as access control.\n\nWhat if, as a baseline, when you started working on a system,\nespecially a complex one which interfaced with multiple\nexternal systems or carried high security or correctness\nrisks, two people played the roles of the modeler\nand the programmer.\nThe modeler would develop the model *without* looking at the code,\nand the programmer would work on the code.\nThey’d meet in the middle to create test cases.\n\nThen the model could serve as a much smaller reference to be able to review the thoroughness of the code, as well as to test out various unusual situations that would otherwise require a large amount of test scaffolding, especially in the early stages of the project.\n\n### Studying expertise\n\n(OK, this one doesn’t fit under “mad scientist” but whatever. It’s my blog.)\n\nIn many fields, there is a growing body of literature around expertise. This doesn’t really exist for code (and specifically code review) in quite the same way.\n\nWhat if you analyzed the code reviews happening in your team and tried to figure out if there are outlier (domain, person) pairs in terms of insightful review comments. Or maybe you get this information just from interviewing people.\n\nOnce you’ve found the outliers, you could try an easy-to-learn\napproach like [Applied Cognitive Task Analysis](https://commoncog.com/an-easier-method-for-extracting-tacit-knowledge/)\nfor extracting the tacit knowledge these people have.\nDoing this is easier for code review compared to many other tasks,\nbecause instead of simulating a difficult-to-simulate situation\n(such as firefighting), you can actually just have\nthe person review PRs live.\n\n## On the zeitgeist surrounding code review\n\nHopefully, by this point, I’ve convinced you somewhat that reviewing code is a skill, and that there’s a lot more room left for us to try out different practices. For example, in terms of training programs, we are not close to more well-studied areas like team sports (e.g. soccer) and two-player games (like Chess, Go etc.).\n\nAt this point, you might (reasonably) still be thinking:\n“So what? LLMs are going to get better at all of these things\nmuch faster than humans can get better at them,\nso it’s pointless to try to compete with LLMs\nat reviewing code.”A related objection might be that while you might care about code review, your management doesn’t. Sorry, I don’t have any advice related to convincing management.\n\nIf you’ll recall my phrasing from before:\n\nIf you’re a software developer, and if you believe that people will continue to be involved in the development and maintenance of programs for the foreseeable future, then it’s\n\nvaluableto get better at reviewing code.\n\nThe second part of this is conditional on the first. So we can disagree on what likelihood we assign to the premise itself (“if you believe … for the foreseeable future”) being true, but that’s a separate discussion from whether the implication (the “then”) itself holds.\n\nThe flipped version of the implication would be something like:\n\nIf you’re a software developer, and even if you believe that people will continue to be involved in the development and maintenance of programs for the foreseeable future, it’s\n\nnet negativeto get better at reviewing code.\n\nFor example, you could hold this position because you think\nthat there are other skills which you could learn that provide\nsignificantly higher leverage.I commonly see “system design” and “product thinking” or “developing taste” as being touted as more important to develop.\n\nOr you might think that reviewing code will later\nbe restricted to a small fraction of working programmers,\nlike how a relatively small fraction of programmers today\nlook at assembly code on a regular basis.\n\nTo this, I have the following responses.\n\nFirst, if you’re able to drop down a lower-level of abstraction compared to your peers, that’s almost always an advantage because you’re able to tackle a wider set of problems that they can. For example, today, if you’re able to understand SQL query plans more easily, or you understand things like memory allocation and assembly, you are at an advantage at designing more robust and high-performance code compared to engineers who don’t understand those topics.\n\nSecond, given that software development is still in its infancy compared to so many other fields, it’s quite possible that we’re not even close to seeing the ceiling on human skill and performance on various aspects, such as, but not limited to, code review. So it may very well be that the average skill you see around you may be far from the ceiling is.\n\nLastly, I recommend grounding your world view\nin experience reports, case studies, and your own observations,\nrather than “takes” on social media.\nCedric Chin has written about much better\nabout this than I could’ve in\n[How to make sense of AI](https://commoncog.com/how-to-make-sense-of-ai/)\nand [Letter to a young person worrying about AI](https://commoncog.com/letter-to-a-young-person-worrying-about-ai/).\n\nAt the end of the day, if you’re going to be responsible for the code you ship, and the code you ship has real consequences for real people, it’s worth asking how you can get better at what you do. To that end, I believe that investing in getting better at reviewing code is one of the best things you can do as a software developer.\n\nYaksha: What is swifter than the wind?\n\nYudhishtira: The mind.\n\nYaksha: More numerous than the blades of grass?\n\nYudhishtira: The thoughts in the mind.\n\nYaksha: What is the most praiseworthy thing of all?\n\nYudhishtira: Skill.\n\nYaksha: The most valuable possession?\n\nYudhishtira: Knowledge.", "url": "https://wpnews.pro/news/reviewing-code-is-a-skill", "canonical_source": "https://typesanitizer.com/blog/code-review.html", "published_at": "2026-08-11 05:37:40+00:00", "updated_at": "2026-08-11 06:46:50.996113+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Google"], "alternates": {"html": "https://wpnews.pro/news/reviewing-code-is-a-skill", "markdown": "https://wpnews.pro/news/reviewing-code-is-a-skill.md", "text": "https://wpnews.pro/news/reviewing-code-is-a-skill.txt", "jsonld": "https://wpnews.pro/news/reviewing-code-is-a-skill.jsonld"}}