Skip to main content

Cognitive Load in Code Reviews: How to Give Feedback That Improves Quality

June 2, 2026

</>

Code review is one of the most impactful practices in software engineering — and one of the most poorly executed. Done well, it catches bugs before they reach production, spreads architectural knowledge across the team, and raises the collective quality bar. Done poorly, it creates resentment, slows delivery, and teaches people to avoid submitting changes for review.

The difference between effective and ineffective code review is rarely about the technical depth of the feedback. It is almost always about cognitive load: how much mental energy the reviewer demands from the author, and how easy it is for the author to understand and act on the feedback.


The Problem with Most Code Reviews

Most code review feedback falls into one of these failure modes:

Failure Mode 1: Ambiguous Criticism

 "This looks wrong."
 "Why did you do it this way?"
 "This is not how we usually do things."

These are not feedback — they are expressions of discomfort. The author cannot act on them without asking a follow-up question, which adds latency and creates social friction.

Failure Mode 2: Nitpicking at the Wrong Level

Spending review time on formatting, variable naming, and minor style preferences when the architectural approach is questionable. Review the right things at the right abstraction level.

Failure Mode 3: Missing the Forest for the Trees

Approving a PR with perfect code style that implements the wrong thing, or solves a problem in a way that will create maintenance headaches in six months.

Failure Mode 4: Feedback Delivered as Judgment

 "This is inefficient."
 "You should have known better than to use a linear scan here."
 "This is a mess."

Feedback about the code becomes confused with feedback about the person. This is the fastest way to make engineers stop asking for review.


The Structured Feedback Framework

Good code review feedback has four components:

WhatWhyHowSeverity

Template:

**[Severity]** [What I observed]: [Why it matters]  [Suggested alternative]

Examples:

**[Blocker]** This endpoint fetches user data without first verifying the requester's
identity. An unauthenticated user could call `/api/users/{id}` with any userId and
retrieve private account data. 
→ Add `const session = await getServerSession(authOptions)` before the query, 
and return 401 if `!session || session.user.id !== params.userId`.

**[Suggestion]** The `getUserById` and `getUserOrders` calls on lines 45–46 run 
sequentially. Since they're independent, running them in parallel would roughly 
halve the response time.
 Consider: `const [user, orders] = await Promise.all([getUserById(id), getUserOrders(id)])`

**[Nit]** Minor: the variable name `d` on line 89 is hard to read in context.
 `dateOfBirth` or `dob` would be clearer. (Feel free to ignore  this is very minor.)

Severity Labels

Using explicit severity labels removes ambiguity about how urgently feedback needs to be addressed:

LabelMeaningExpected Action
BlockerSecurity vulnerability, data loss risk, or critical bugMust be fixed before merge
RequiredSignificant design concern, performance problem, or standards violationMust be addressed before merge
SuggestionAlternative approach that would be meaningfully betterShould be discussed; author decides
NitMinor style preference, optional improvementAuthor can ignore; reviewer won't block
QuestionSeeking understanding, not proposing a changeAuthor explains reasoning; no action required

When you label everything as equally important, authors don't know where to focus. When you label precisely, authors can triage their response.


Questions vs. Suggestions

Use questions when you want to understand the author's reasoning without implying they are wrong:

// ❌ Implies the choice was wrong:
"Why are you using a Map here instead of a plain object?"

// ✅ Genuine question:
"[Question] I'm curious about the Map choice here — is there a performance reason, 
or was it for the guaranteed insertion order? Both are valid; I just want to understand."

Use suggestions when you have a concrete alternative:

// ❌ Vague:
"There might be a better way to do this."

// ✅ Concrete alternative:
"[Suggestion] This nested ternary on line 67 is a bit hard to follow. 
A guard clause would make the happy path more obvious:
→ if (!isAuthenticated) return <LoginPrompt />;
→ if (isLoading) return <Spinner />;
→ return <Dashboard dat={data} />;
"

Giving Feedback on Architecture, Not Just Code

The highest-value code review observations are architectural — things that are technically correct but will cause problems at scale or under change:

**[Required]** This works for the current feature, but I'm concerned about the 
long-term coupling. The `UserService` is directly importing from `PaymentService`, 
which means payment logic will be executed in any context that renders user data 
(including admin views and public profiles). 

→ The boundary should probably be: UserService holds user state; billing 
  information is fetched separately only when needed for payment flows.
→ Could we move the billing fetch to the checkout flow specifically?

I could be wrong about the intended architecture here — happy to discuss.

Note the closing hedge: "I could be wrong... happy to discuss." This acknowledges that the reviewer doesn't have full context. It invites dialogue rather than demanding compliance.


Receiving Code Review Feedback

The author's role is equally important:

Respond to every comment, even if just with ✅ or "Addressed in 3a8f2b1". Radio silence is frustrating for reviewers.

Distinguish what you changed from what you disagree with:

// ✅ Clear response:
"Changed. I switched to `Promise.all` as suggested — makes sense."

"Keeping as-is. The sequential calls are intentional here: `getUser()` establishes 
the authorization context needed by `getPermissions()`. Running them in parallel 
would require restructuring the auth middleware significantly. Happy to discuss if 
you see a clean parallel approach."

Never take feedback personally on code you wrote yesterday. You are not your code.


The Author's Responsibility: PR Size

The single most effective way to get better code reviews is to submit smaller PRs. A 50-line PR gets thorough, detailed review. A 500-line PR gets rubber-stamped or delayed.

Guidelines:

  • Ideal PR size: 50–200 lines changed.
  • Maximum before splitting: 400 lines.
  • One logical change per PR — not "feature + refactor + bugfix."

Breaking work into smaller PRs requires planning, but the quality of review you receive improves dramatically and the time to merge decreases.


Code Review Checklist

When reviewing, work through these in order (most important first):

Correctness (Blockers)

  • Does the code do what the PR description says it does?
  • Are there authentication and authorization checks on sensitive operations?
  • Are there data validation checks on all user inputs?
  • Could this break existing functionality? (Check for missing edge cases.)

Design (Required)

  • Does this fit the existing architectural patterns?
  • Are there hidden coupling problems that will cause trouble later?
  • Is error handling complete and meaningful?
  • Are tests included for new behavior? Are edge cases tested?

Performance (Suggestion)

  • Are there N+1 query patterns? (Fetching in a loop)
  • Could independent operations run in parallel?

Readability (Nit)

  • Are variable and function names clear without needing to read the body?
  • Is there anything that would confuse a new team member in 6 months?

Conclusion

Code review is a communication skill as much as it is a technical skill. The structural improvements — explicit severity labels, the what-why-how template, distinguishing questions from suggestions — reduce the cognitive load on authors and make feedback actionable rather than frustrating. The cultural improvements — never directing feedback at the person, always offering a concrete alternative, responding to every comment — make the review process a positive experience that people actively want to participate in. Better code reviews produce better code and better teams.

Recommended Posts