TL;DR: On a small team, code review is not a style check and it is not a second round of testing. It is the one moment where someone other than the author asks "what happens when this goes wrong, and who finds out?" When I review as a CTO, I read the description before the diff, look at what the change touches before how it is written, and spend most of my attention on four things: data that cannot be undone, contracts other code depends on, failure paths, and anything that ships to a store or a phone line where rollback is slow. Formatting, naming and taste go to a linter or get one comment, not ten. Keep pull requests small, review within a day, and treat AI-written code exactly like human-written code: the author owns it.
I have reviewed code as CTO for RAQTS (Unity, React Native and Next.js), Nystag Healthcare XR (Unity on the Vive Focus 3) and Bettershop (a React, TypeScript and GraphQL dashboard for Amazon sellers), and I review work across the team at my studio. Small teams are different from big ones. There is no platform group, no dedicated QA, often no second reviewer, and the person merging is sometimes also the person deploying at 11pm. That changes what review is for.
What code review is for on a small team
Big companies use review for many things at once: knowledge sharing, consistency across hundreds of engineers, compliance. On a team of three to nine, most of those still matter a little, but one matters far more than the rest: catching the mistakes that are expensive to fix after they ship.
That framing decides where the time goes. A badly named variable costs almost nothing after merge. A migration that drops a column, a changed API response that an older app version still depends on, or a webhook handler that silently swallows errors can cost days, customers or data. Review time is finite, so I spend it in proportion to the cost of being wrong, not in proportion to the number of lines changed.
My review order
I do not start at the first line of the diff. I go in this order, and I often stop before the end because an earlier step already answered the question.
1. Read the description first
What is this change supposed to do, and why now? If the pull request has no description, that is my first comment. A reviewer who does not know the intent can only check that the code is internally consistent, which is the least useful kind of review. One or two sentences is enough: the problem, the approach, and how the author tested it.
2. Look at what it touches, before how
Before reading any logic, I scan the file list. A change that touches a database schema, an API contract, authentication, payments, a build or release config, or anything that talks to a third party gets a slower review than a change to a single UI component. This takes thirty seconds and decides whether the next twenty minutes are a skim or a careful read.
3. Read the risky parts properly
This is where most of the time goes, and the specifics are below. UI tweaks, copy changes and well-tested refactors get a lighter pass afterwards.
The four things I actually look for
Data that cannot be undone
Anything that writes, deletes or migrates data gets the most attention. Questions I ask: does this migration run safely on the real data, not just on an empty dev database? Can it be rolled back, and if not, is there a backup step? Does a delete actually need to be a hard delete? Is a write idempotent if the same request arrives twice?
That last one comes up constantly in automation work. A webhook from a voice agent platform or a CRM can and will be delivered more than once, and a handler that creates a new record every time turns one phone call into three duplicate leads. I wrote more about that class of bug in why voice agent post-call automations fail.
Contracts other code depends on
Every system I have led had more than one client reading the same data. At RAQTS, a score had to mean the same thing in the Unity layer, the mobile app and the web portal. At Bettershop, the GraphQL schema is the contract between the dashboard and everything behind it. A renamed field, a nullable field that becomes required, or a changed enum value can break a consumer that is not in the diff at all.
So for any change to an API, schema or shared type, I ask who else reads this, and whether old clients still work. On mobile this is especially sharp, because you cannot force everyone to update. A field you remove today is still being requested by an app version installed months ago. The full argument is in designing for old app versions, and it is one of the most common things I flag in review.
Failure paths
Most code is written and tested on the happy path. Review is where someone asks about the other paths. What happens when the network call times out? When the third-party API returns a 500, or a 200 with an error in the body? When the list is empty? When the user is offline, which on a phone or a headset is a normal state, not an edge case?
The pattern I look for hardest is a catch block that logs nothing and returns a default. It makes the feature look like it works while hiding the exact information you need when it does not. I would rather see an ugly error that reaches someone than a clean screen built on a silent failure.
Things that ship somewhere slow to roll back
A web deploy can be reverted in minutes. An App Store or Play Store release cannot: a fix means a new build and another review, and users on the broken version stay there until they update. A Unity build that goes onto clinical headsets, a config change to a live phone line, or a prompt change on a production voice agent are in the same category. Callers are hitting it in real time.
For these, I want to see how the author tested on the real target, not just in the editor or simulator, and whether there is a way to turn the change off without shipping again: a remote config value, a feature switch, or at minimum a clear revert plan. For voice agents specifically, a prompt edit is a code change and gets a regression test run before it goes live.
What I deliberately do not spend review time on
Formatting, import order and most naming questions belong to tools. If the team argues about them in review, the fix is to add a linter or formatter rule once, not to leave the same comment forever. On TypeScript projects that means a shared lint config and type checks in CI. In Unity projects, the things that matter, like build settings and platform defines, belong in the build pipeline, not in a reviewer's memory.
Taste is the other trap. If I would have structured something differently but the author's version is clear, tested and easy to change later, I either say nothing or leave one comment marked clearly as optional. A reviewer who blocks every pull request over preference teaches the team to open fewer, bigger pull requests, which makes every review worse.
Keep pull requests small and reviews fast
The single biggest improvement to review quality on a small team is smaller changes. A 150-line pull request gets read. A 2,000-line pull request gets scrolled, approved and hoped for. I ask people to split refactors from behavior changes, because a diff that moves code and changes logic at the same time is almost impossible to review honestly.
Speed matters just as much. A pull request that waits three days for review goes stale, collects merge conflicts and encourages people to batch more work into the next one. My target is a first response within one working day. Across time zones this takes some planning: I review at the start of my day so feedback is waiting when US and UK teammates come online, which is part of how I keep remote work across time zones from slowing a project down.
Reviewing AI-written code
Most of the code I see in review now had an AI tool involved somewhere, including my own. I use Claude Code on client projects every day. My rule in review is simple: the person who opened the pull request owns every line in it, regardless of who or what typed it.
In practice, AI-assisted pull requests have a recognizable failure profile, so I look a bit harder in a few places:
- Invented APIs. Especially in native iOS and Android code, Unity platform calls and less common libraries, where the tool produces a confident method that does not exist or behaves differently than described.
- Too much code. Extra abstraction layers, defensive checks for cases that cannot happen, and helpers nobody asked for. More code is more to review and maintain.
- Tests that test the mock. Generated tests that pass because they assert what the mock returns, not what the real system does.
- Unexplained changes outside scope. A one-line fix that also reformatted three files and changed a dependency version.
None of these are reasons to ban the tools. They are reasons to keep pull requests small and to ask the author, "did you run this on the real target?"
When there is only one reviewer, or none
On many small teams, I am the only senior reviewer, or the founder is the only other developer. Two things help.
First, a short checklist in the pull request template: how was this tested, does it touch data or a shared contract, does it need a migration, how do we turn it off. It costs the author a minute and catches the things a tired reviewer misses.
Second, self-review before asking for review. Opening your own diff in the review interface and reading it as if someone else wrote it catches a surprising share of problems, including debug logs, leftover test values and commented-out code. When I am the sole developer on something, that is the minimum I do before merging.
If you are inheriting a project where nobody has reviewed anything for months, review is not where to start. Get a picture of the whole system first, which is what I do in the first week with an inherited codebase, then put review in place for new changes.
The short version
Read the intent before the code. Spend review time in proportion to the cost of being wrong: data, contracts, failure paths, and anything that is slow to roll back. Let tools handle style. Keep changes small and respond within a day. Hold AI-written code to the same standard as everything else, because the author owns it either way.
I lead engineering as a hands-on fractional CTO for startups in the US, UK and Europe, across web, mobile, Unity and AR/XR, and AI voice agents. If your team is shipping fast and wants review and release habits that keep up, more about how I work as a fractional CTO is here, or book a call.