CODE-REVIEW · TEAM · PROCESS · EMBEDDED

How I Run Code Reviews for Firmware Teams (And Why Most Reviews Waste Everyone's Time)

2026-06-29 · Davide Carrese

I spent three years on a project where every pull request went through exactly two reviewers, a mandatory 24-hour waiting period, and a checklist with forty-seven items. The code was still full of bugs. The safety-critical module had a race condition that survived fourteen reviews. The junior developer spent more time waiting for approvals than writing code. And the team hated the process so much that people started batch-committing at the end of sprints just to avoid the overhead.

That experience taught me something uncomfortable: most code review processes are designed to make managers feel safe, not to actually improve code. The rituals — mandatory reviewers, minimum comment counts, approval gates — create the illusion of quality while consuming enormous amounts of engineering time.

Over the following years, I stripped the process down, rebuilt it from first principles, and tested it across four different firmware teams. Here is what survived.

The First Principle: Reviews Catch Different Bugs Than Testing

The single biggest mistake I see in firmware teams is treating code review as a substitute for testing, or testing as a substitute for review. They catch different things:

What testing findsWhat review finds
Logic errors, timing violations, edge cases in executionMisunderstood APIs, wrong assumptions about hardware behaviour
Incorrect register values and bitfield masksRegister configuration that works but is not what the datasheet recommends for the use case
DMA descriptor chain corruption under loadDMA buffer layout that will be a maintenance nightmare in six months
Stack overflow from deep call chainsMissing volatile qualifiers on shared variables accessed by ISR and main loop

I have seen teams invest in elaborate hardware-in-the-loop test rigs while their code review consisted of a single "LGTM" comment from the most junior person on the team. And I have seen teams with mandatory two-person reviews on every commit to an internal-only test harness, while the bootloader that goes on ten thousand devices got a rubber stamp because "it was already reviewed in the prototype phase."

The fix is simple: match the review depth to the risk profile of the code, and never confuse process completion with quality assurance.

The Three-Tier Review Model

I use three tiers of review, applied based on the code's criticality:

TierWhenWhoDepth
Tier 1 — GlanceInternal tools, test harnesses, build scripts, documentationOne person, any seniority (can be the author self-merging after 2 hours)Check for obvious mistakes and naming. No style comments.
Tier 2 — StandardApplication logic, protocol implementations, new peripheral driversOne person with domain knowledge, within 24 hoursCorrectness, API design, concurrency assumptions, register-level sanity. Style only if it affects readability.
Tier 3 — DeepBootloaders, safety-critical modules, power management sequences, watchdog configurationsTwo people: one domain expert, one architect/senior who did not work on the moduleLine-by-line if needed. Walk through every state transition, every error path, every timing assumption. Document the review outcome in the commit message.

The key insight: most firmware code is Tier 2. Treating all code as Tier 3 burns out reviewers and slows the team to a crawl. Treating bootloader code as Tier 1 is how devices brick in the field.

I communicate this tiers model explicitly at the start of every engagement — in the contract or the project kick-off. The client pays for Tier 3 reviews on critical modules and Tier 2 on the rest. Everyone knows where the line is drawn, and nobody argues about "why this change needs two reviewers."

The Anti-Patterns I Ban on Day One

These four patterns destroy the value of code reviews in firmware teams. I call them out in the first team meeting:

1. Style comments disguised as correctness concerns.
"Please rename i2c_transfer() to i2c_transfer_data() because it also transfers the address byte." — This is a style preference dressed up as pedantry. If the function works, is documented, and has a clear contract, let it go. Style debates in reviews are the single biggest time sink, and they disproportionately frustrate junior developers. Use a formatter (clang-format), set it in CI, and move on.

2. The "I would have done it differently" comment.
If the approach is correct, performant enough, and safe, the alternative implementation preference is not actionable. The reviewer should ask "does this approach have a correctness issue?" not "would I have written it this way?"

3. Mandatory N + 1 reviewers.
The most common failure mode: a project requires two reviewers, so every PR waits for two people. Meanwhile, the most knowledgeable reviewer has already approved, and the second reviewer is a formality who skim-reads and clicks Approve. The review adds zero value but doubles the latency. The solution: one reviewer with the right domain knowledge is worth more than two random reviewers.

4. Reviewing generated or configuration code.
STM32CubeMX-generated initialisation code, linker scripts pasted from the SDK example, CMSIS config headers — why are humans reviewing these? If the CubeMX output compiles and the hardware boots, the review is theatre. Focus reviewer time on the logic the engineer actually wrote.

The Embedded-Specific Review Checklist

Beyond the general code review principles, firmware code has failure modes that pure software reviews never encounter. These are the specific things I check on every Tier 3 review:

The Infrastructure That Makes Reviews Fast

No amount of process optimisation will fix a review workflow where the reviewer has to check out the branch, compile it, flash it, and run it manually to understand the change. The infrastructure layer is where most teams should invest before tweaking their review policy:

What Has Worked for Me

Four things made the biggest difference in the teams I have led:

Practical Checklist

Sources

📬 Comments / discussion

Prefer email: comments@carrese.eu — include the article URL so I can follow up. For corrections or deeper questions, I typically reply within 48 hours.