On this path · Communication 4. Technical Discussions and Reviews
- Meetings: Contributing and Steering
- Presentations and Public Speaking
- Storytelling for Engineers
- Technical Discussions and Reviews
- Giving and Receiving Feedback
- Difficult Conversations
- Negotiation Language
- Interviews: Speaking About Yourself
- Networking and Small Talk
- Executive and Persuasive Communication
Technical Discussions and Reviews
Disagree precisely and keep code and design reviews constructive.
Learning outcomes
A code review is the place where your English is tested hardest, because two things ride on every comment at once. You are trying to make the code better, and you are trying to keep working with the person who wrote it tomorrow. Write “you overcomplicated this” and you may win the technical point and lose the colleague; write nothing because you fear the friction and the bug ships. The whole skill is saying the hard thing in a way that improves the work and the relationship together, and most of that skill is word choice you can learn.
After studying this page, you can:
- Aim a critique at the code rather than the person, and rewrite a comment that has slipped into blame.
- Choose the ask-versus-tell move, framing a change as a question or suggestion instead of a bare order, and know when a plain command is correct.
- Label severity so a blocking concern, a suggestion, and a nit are never confused.
- Disagree precisely on the idea while staying easy to receive, using calibrated stance.
- Defend a design under question by naming the alternatives, costs, failure modes, and your decision rule.
- Soften async review tone so a written comment does not read colder than you meant, receive critique without defensiveness, and praise good work, not only flag problems.
Before we dive in
Picture the comment that started a bad week. A reviewer wrote, on a pull request, “Why did you do it this way? This is way too complicated and it’s going to be a maintenance nightmare.” The author had spent two days on that code. They read the comment at 9 p.m., with no tone of voice to soften it, and heard an attack. They replied defensively. The reviewer doubled down. The thread grew to forty comments, two more people joined, and by Friday a change that should have merged in an hour had soured a working relationship that took months to repair. The code, by the way, did need to be simpler. The reviewer was right.
That is the problem this page exists to solve. The reviewer had a correct technical point and delivered it in a way that made it almost impossible to act on. Critique is unavoidable in engineering: you cannot review code, run a design review, or argue an architecture without telling someone their work has a flaw. Telling someone their work has a flaw is, in the language of politeness, face, and indirectness, a face-threatening act, and a review is dozens of them in a row. So the job is never to avoid the critique. It is to perform it so the work gets better and the person stays your collaborator. Everything below is the machinery for doing that, comment by comment.
A note on scope. This page is about the language of the review itself: the comment, the disagreement, the defense. The broader cycle of giving and taking feedback over a career, including praise, growth, and the feedback relationship, is the subject of giving and receiving feedback; this page is the technical-review slice of it.
Critique the code, not the person
Start with the single move that prevents most review damage. Aim every comment at the code, the design, or the choice, never at the person who made it. The difference sounds small and is enormous, because one version describes a thing that can be changed and the other describes a person who must defend themselves.
Watch what changes between two comments that carry the identical technical content. “You overcomplicated this” makes the person the subject and “overcomplicated” their verdict; it is a charge they will answer. “This function could be simpler” makes the function the subject and points at a path forward; it is an observation they can act on. Nothing technical was lost. What changed is that the second version leaves the author’s competence untouched, so they can fix the code without first defending themselves.
The reliable test is grammatical: look at the subject of your sentence. If the subject is “you”, you are probably critiquing the person; if the subject is “this function”, “the loop”, “this approach”, “the query”, you are critiquing the work. The table below runs four real review comments through that test.
Four review comments rewritten from person-aimed to code-aimed. The technical content is identical; only the target of the sentence moves.
| Aimed at the person | Aimed at the code | What the fix does |
|---|---|---|
| You overcomplicated this. | This function could be simpler; the nested conditionals are doing the work a guard clause would. | Subject becomes the function, and the comment names the concrete simplification. |
| You didn't handle the empty case. | This will throw on empty input; we'd need a guard before the loop. | States the failure and the location instead of assigning blame. |
| Why would you name it like that? | This name reads as a noun but it's a verb; could we rename it to match what it does? | Turns an accusation into a question about the name itself. |
| You always forget the error path. | The error path isn't covered here; what should happen when the call times out? | Drops the character judgement and asks about the specific gap. |
Read down the right column and notice none of the comments went soft. “This will throw on empty input” is a blunt technical claim. It is just aimed at the input and the throw, not at the author’s care or skill, so there is nothing for the author to take personally and everything for them to fix. That is the whole move: maximum directness on the code, zero directness on the person.
Ask instead of tell
The second move builds straight on face. A review comment is a request to change someone’s work, and a request can be made as a bare command (“pull this into a helper”) or as a question or suggestion (“what do you think about pulling this into a helper?”). The question form is not weaker. It does something the command cannot: it leaves the author room to push back, to explain a constraint you cannot see, or to agree, which protects their negative face, their freedom to decide, in exactly the way politeness, face, and indirectness describes.
The reason this matters in a review and not just in a hallway request is that you are often wrong, and you do not know it yet. The author may have a reason the helper does not work, a reason that lives in a ticket you never read. A bare command “extract this into a helper” assumes you have full information. A question “could we extract this into a helper, or is there a reason it lives here?” invites the missing context instead of steamrolling it. You end up right more often, and the author feels consulted rather than commanded.
The flow shows where the ask-versus-tell line actually sits. Default to the question, because most review comments are about taste, structure, or a change where the author might know something you do not. Switch to a plain tell only when two conditions both hold: it is a genuine defect, a bug or a security or correctness problem, and you have full context, so there is no hidden constraint to discover. Even then the command stays aimed at the code, never the person. “We need to redact the token before logging” is a tell that is still about the token, not about the author who logged it.
Severity labels: nit, suggestion, blocking
Here is a problem the previous two moves do not solve. You have made your comments code-aimed and you have phrased them as questions, but the author still cannot tell which ones matter. A typo in a comment and a race condition that will corrupt data both arrived as polite questions, and the author has no way to know that one is optional and the other must be fixed before merge. They might block on the typo and ship the race condition.
The fix is a tiny convention that the whole industry has converged on: label the severity of each comment with a one-word prefix. The three that carry most of the load are nit: for a minor, optional point you would not block over, suggestion: for a real improvement that is still the author’s call, and blocking: for something that must change before the code merges. The prefix does in one word what a paragraph of hedging cannot: it tells the author exactly how much weight to put on the comment, so they spend their effort where it counts.
The common review severity labels, what each one means, and a sample comment. The label sets how much the author must act on it.
| Label | What it means | Sample comment |
|---|---|---|
| nit: | Minor and optional; take it or leave it. Never a reason to block. | nit: extra blank line here. |
| suggestion: | A real improvement, but the author's call to accept or not. | suggestion: this reads cleaner as an early return. |
| question: | You want to understand before judging; not yet a request. | question: is the retry here intentional, or left over? |
| blocking: | Must change before merge; a correctness, security, or contract issue. | blocking: this logs the raw token; it has to be redacted. |
| praise: | Calls out something done well, on purpose. | praise: nice, this error message tells the user exactly what to do. |
The labels do two jobs at once. They tell the author where to spend effort, and they hold you honest about your own comments. The discipline of typing blocking: makes you ask whether a thing is truly worth blocking on, and most of the time it is not, which is the cure for the most common reviewer fault: blocking a merge over a preference. If you cannot in good conscience write blocking:, then it is a nit: or a suggestion:, and the author is free to move on.
The fastest way to make people dread your reviews is to hold up a merge over something that does not matter: a variable name you would have chosen differently, a blank line, a style preference the linter does not enforce. It reads as a power move even when you meant none, because you are spending the author’s time on your taste. Label it honestly. If it is a nit, mark it nit: and approve the change anyway; let the author decide. Save blocking: for things that are actually wrong, and your blocking comments will carry real weight when you need them to.
Disagreeing precisely without harshness
Sometimes you do not just want a small change; you think the whole approach is wrong, or the author has pushed back on your comment and you still disagree. This is the highest-stakes moment in a review, and it is where calibrated stance, the subject of hedging and stance, earns its keep. You need to be unmistakably direct on the idea, so nobody misses that you are pushing back, while staying easy enough to receive that the conversation continues.
English has a set of ready-made phrases that are built for exactly this, and they are worth keeping live in your vocabulary. Instead of “this is wrong” or “that’s a bad idea”, reach for “I’d push back on this”, “the risk I see is”, “I’m not convinced this handles the retry case”, “I don’t think that holds when the queue backs up”, “I see it differently”. Each of these is plainly a disagreement, so your position is clear, but each frames it as your reading of the problem meeting theirs, not a verdict on their competence. “I’m not convinced this handles the retry case” puts the burden on the design to convince you and names the exact case you doubt; it gives the author something concrete to answer instead of a wall to defend.
The pattern under every good disagreement is to keep the technical point sharp while softening only the social edge. “I don’t think this handles a duplicate message; what happens if the broker redelivers?” is sharp on the idea, it names the exact failure and asks for the answer, and gentle on the person, because it is your concern and an open question, not their mistake. Aim your confidence at your own conviction (“I’m fairly sure the duplicate case breaks this”) and your hedges at the interpersonal frame (“I might be missing context here, but”). Firm on the substance, soft on the relationship: that split is what confident disagreement is made of.
Two failure modes sit on either side of this. Under-soften and you get “this is just wrong”, which is sharp on the person and starts a fight. Over-soften and you get “I just wonder if maybe there could possibly be a tiny concern here, perhaps?”, which is so buried the author cannot even tell you disagree, so nothing changes. The calibrated middle, “I’d push back on this; the risk I see is that a redelivered message double-charges the customer”, is the one that moves the review forward, because it is direct enough to register and kind enough to answer.
Defending a design under question
The same skill runs in reverse when it is your design under question. In a design review or an architecture discussion, someone senior asks “why did you go with a queue here instead of just calling the service directly?” A junior engineer hears an attack and gets defensive, or caves and abandons a good decision. The senior move is neither. It is to treat the question as a request for your reasoning and to lay that reasoning out cleanly, because a design decision you cannot explain is one you cannot defend, and one you explain well usually survives.
The reasoning has a shape, and it is the same shape a good design document uses, which is why this connects directly to writing RFCs and design docs. You name four things: the alternatives you considered, the cost of each, the failure modes you were trying to avoid, and the decision rule that picked the winner. That structure does something a flat “because it’s better” cannot: it shows you saw the other options and rejected them for stated reasons, which is exactly what turns a defensive moment into a display of judgment.
The flow ends on a branch that separates a good defense from stubbornness. After you lay out the reasoning, you check whether the questioner brought information you did not have. If they did not, your decision stands and is now explained rather than merely asserted, which is a stronger position than where you started. If they did, you change your mind in the open: “good point, I hadn’t weighed the on-call cost; that does shift the tradeoff.” Defending a design well is not refusing to move. It is showing your reasoning so clearly that moving, or not moving, is obviously justified.
Async tone: warmth a comment cannot say out loud
Every move so far gets harder in writing, and a review is almost always in writing. A spoken critique carries your tone, your smile, the pause where you soften; a written comment carries none of that. The reader supplies the missing tone themselves, and under stress they supply the harshest reading available. “Why is this here?” said aloud with a curious lilt is a genuine question; the same three words typed into a pull request at 9 p.m. read as “why on earth did you do this?” This is the central fact of async review, and it is developed fully in tone in writing: written words land colder than you meant, so you have to add back the warmth your voice would have carried.
The fix is concrete. Add a few words of warmth and context that the silence strips out. “Why is this here?” becomes “Could you help me understand why this lives here? I might be missing the context.” A flat “this is wrong” becomes “I think there’s a bug here; want me to walk through what I’m seeing?” The added words are not padding; they are the carrier wave for the tone your voice would have supplied in person. A reviewer who writes warm async comments is not being soft. They are compensating, correctly, for a medium that deletes warmth by default.
Reread every review comment once before you post it, but read it in the worst voice you can imagine: tired, defensive, having a bad day. That is the voice the author may read it in, because the text gives them nothing else to go on. “Did you test this?” can read as a neutral check or as an accusation that you were careless, and you do not control which. If a comment could be read as an attack, it will be by someone, so add the words that close that door: a “could you”, a “I might be wrong”, a “nice work overall, one thing”. Tone you intend but do not type does not reach the reader.
Receiving critique without defensiveness
Half of review is being reviewed, and the language skill there is mostly internal: managing your own response so your reply does not start a fight. When a comment on your code stings, the instinct is to defend, to explain why you are right, to read an attack into a neutral note. The senior habit is the opposite, and it rests on four moves you can practice.
First, assume good intent: read the comment as the most reasonable version of itself, the way you would want yours read. “Why did you do it this way?” almost always means “help me understand”, not “you idiot”, so answer the charitable version. Second, ask for specifics instead of arguing the general: “this feels off” is hard to act on, so reply “which part feels off, and what would you expect instead?”, which turns a vague jab into usable feedback. Third, separate your ego from the code: the comment is about the work, not your worth, and the code is not you, so a flaw in it is not a flaw in you. Fourth, thank the reviewer, genuinely, because someone spent their time making your work better, and “good catch, thanks, I’ll fix it” closes a thread that defensiveness would have stretched to forty comments.
Receiving a hard review comment: the defensive reply that escalates, and the open reply that resolves it.
| The comment you got | Defensive reply (escalates) | Open reply (resolves) |
|---|---|---|
| This logic is hard to follow. | It's not that complicated if you read it carefully. | Fair, it grew over a few commits. Which part lost you? I'll add a comment or split it. |
| Why not just use the existing helper? | Because the existing helper doesn't do what I need, obviously. | Good question. The helper doesn't handle the async case, but maybe I should extend it instead. Let me check. |
| blocking: this can deadlock under load. | I've never seen it deadlock in testing. | Good catch, I hadn't considered the contended path. Can you point me at the lock order you're worried about? |
Read the two reply columns side by side. The defensive replies all do the same thing: they treat the comment as an attack and counter-attack, which forces the reviewer to escalate to be heard. The open replies all do the same thing too: they grant what is true, ask for the specific, and move toward a fix, which ends the thread. Notice that the open replies are not surrender. “Maybe I should extend it instead, let me check” is still thinking; it just thinks out loud and with the reviewer rather than against them.
Praise is half the job
A reviewer who only ever flags problems trains people to dread their name in the reviewer list, and trains themselves to read code looking only for faults. Both are losses. Calling out good work is not soft or optional; it is half of what a review is for, because it tells the author what to keep doing, it makes the blocking comments land easier when they come, and it is simply true that the code did something well.
The trick is to make praise specific, the same way you make criticism specific. “Looks good!” is noise; it could be pasted onto any change and carries no information. “praise: this error message tells the user exactly what to do next, that’s a real improvement over the old one” tells the author precisely what worked and why, so they do it again. Aim praise at the work with the same precision you aim critique at the work. A review that names two real strengths and three real problems reads as a fair assessment by someone who was actually paying attention; a review that names only the three problems reads as a hunt.
Mental Model: a review is a shared search, not a verdict
Here is the reframe that ties the page together, and it dissolves most review friction at once. The wrong model, the one that produces harsh comments and defensive replies in equal measure, is that a review is a verdict: the reviewer is a judge ruling on the author’s work, passing or failing it, and each comment is a charge to be defended or conceded. Under that model every comment is adversarial by design, because a judge and a defendant are on opposite sides.
The better model is that a review is a shared search for the best version of the code, with the reviewer and the author on the same side of the problem, both looking at the work, not at each other. A comment is not a charge; it is a thing one searcher noticed that the other might want to consider. “I’m not convinced this handles retries” is not an accusation under this model; it is two people turning the code over together and one of them spotting a case worth checking. The author is not on trial, so they have nothing to defend; they are a co-searcher who can say “good catch” or “actually, here’s why it’s fine” with equal ease.
This model fixes both sides of the page at once. It tells the reviewer to aim at the code, because you do not attack your teammate in a shared search, and to ask rather than tell, because a fellow searcher might see what you cannot. It tells the author to receive without defensiveness, because there is no verdict to fear, only a case to consider. Once both people believe the review is a search and not a trial, the harsh comment and the defensive reply both stop making sense, and the forty-comment thread from the opening of this page never starts.
You and the author are on the same side of the code, searching together for its best version, not facing each other across a verdict. A comment is something you noticed, offered for them to consider, not a charge they must answer. Aim at the work, ask before you tell, label how much each comment matters, and praise what is good. When you are the author, assume the reviewer is searching with you, not judging you, so a flaw found is a flaw fixed together, not a loss.
When to move the thread to a call
One operational rule closes the loop, because not every disagreement belongs in writing. When a review thread keeps growing, when the same point bounces back and forth three or four times without converging, when the tone is starting to sharpen, or when the disagreement is really about a design and not a line of code, stop typing and move it to a call or a quick in-person chat. The reason is everything this page has said about async: written disagreement loses tone, accumulates harshness, and is slow, so a knot that ten messages cannot untie often dissolves in two minutes of talking.
The move itself is short and a relief to both sides: “I think we’re talking past each other in text; got five minutes to hash this out on a call?” Then bring the decision back to the thread in one comment, so the people who were not on the call can follow what was decided. Moving to a call is not avoiding the disagreement; it is choosing the medium that will actually resolve it, and recognizing that a written review is a wonderful tool for most comments and the wrong tool for a stuck one.
Common mistakes
The first mistake is personal or harsh critique: making the person the subject (“you overcomplicated this”, “you always forget the error path”) instead of the code. The fix is the grammatical test from earlier, change the subject of the sentence to the function, the query, the approach, and the same technical point lands without a target on the author.
The second is vague comments. “This is bad”, “this feels off”, “I don’t like this” give the author nothing to act on; they only communicate disapproval. Every comment should name the specific thing and ideally the path forward: not “this is bad” but “this re-reads the file on every call; could we read it once and cache it?” Vague praise has the same flaw, “looks good” carries no information, so make both your praise and your criticism specific enough to act on.
The third is blocking on nits. Holding up a merge over a preference, a name you would have chosen differently, a blank line, reads as a power move and teaches people to dread your reviews. Label honestly: if it is optional, mark it nit: and approve anyway, and reserve blocking: for what is genuinely wrong.
The fourth is defensiveness when receiving. Reading a neutral comment as an attack and counter-attacking forces the reviewer to escalate and turns a one-line fix into a fight. Assume good intent, ask for specifics, separate your ego from the code, and thank the reviewer.
The fifth is async tone landing as cold. Posting a comment that is technically fine but reads harsh because the medium stripped your tone. Reread every comment in the worst voice the author might use, and add back the warmth, the “could you”, the “I might be wrong”, the “nice work, one thing”, that your voice would have carried in person. None of these mistakes is about your English grammar; all of them are about aiming the same correct technical point in a way the reader can actually use.
Mastery Questions
Question
A teammate's review comment reads 'you overcomplicated this and it's going to be a maintenance nightmare.' Why does it fail, and how do you rewrite it to make the same point?
Answer
It fails because it is aimed at the person, not the code: 'you overcomplicated' makes the author the subject and assigns them a verdict they will defend, so the technically correct point becomes a fight. The grammatical test is to check the subject of the sentence; if it is 'you', you are critiquing the person. Rewrite it so the subject is the code and the comment names a concrete path forward: 'This function could be simpler; the nested conditionals are doing the work a guard clause would.' Nothing technical is lost, the comment is just as direct about the code, but the author's competence is left untouched, so they can fix it without first defending themselves. Maximum directness on the code, zero directness on the person.
Sources & evidence7 claims · 3 cited
Claims cover the eight taught moves plus the shared-search mental model and the move-to-a-call rule. Face-saving and softening claims are grounded in Brown and Levinson; the caring-and-challenging stance in Scott; review conventions are internal-reasoning over common practice, with no invented numbers.
- Aiming a review comment at the code rather than the person (making the function, not you, the grammatical subject) delivers the identical technical content while leaving the author's competence untouched, so they can act on the comment instead of defending themselves.internal reasoning
- Framing a requested change as a question or suggestion (what do you think about pulling this into a helper?) rather than a bare command protects the author's negative face and invites context the reviewer may be missing; a plain command is warranted only for a genuine defect held with full context.stable common knowledge
- Severity labels (nit, suggestion, blocking) prefix a comment with how much the author must act on it, separating an optional preference from a merge-blocking defect, and the discipline of writing blocking cures the common fault of blocking a merge over a preference.internal reasoning
- Precise disagreement keeps the technical point sharp while softening only the social edge, using ready-made stance phrases (I'd push back on this, the risk I see is, I'm not convinced this handles the retry case) that mark a clear position without delivering a verdict on the person; both under-softening and over-softening fail.internal reasoning
- A design under question is best defended by naming the alternatives considered, the cost of each, the failure modes avoided, and the decision rule that chose the winner, then updating openly only if the questioner brings information the author lacked.internal reasoning
- A written review comment reads colder than intended because the medium strips the tone a voice would carry, so reviewers must add warmth back in words; this is the async-tone correction.internal reasoning
- Treating a review as a shared search for the best code rather than a verdict, pairing genuine care for the person with direct challenge of the work, dissolves both harsh critique and defensive reception; the author receives well by assuming good intent, asking for specifics, separating ego from code, and thanking the reviewer.stable common knowledge
Cited sources
- Politeness: Some Universals in Language Usage · Penelope Brown and Stephen C. Levinson
- Principles of Pragmatics · Geoffrey Leech
- Radical Candor · Kim Scott