From Code Review Friction to Psychological Safety: How I Learned to Give Feedback That Actually Improves Engineering
How shifting from "being right" to "helping others improve" transformed my code reviews, accelerated team shipping, and fundamentally changed my leadership approach.
From Code Review Friction to Psychological Safety: How I Learned to Give Feedback That Actually Improves Engineering
I spent the first four years of my engineering career being right. Not just technically correct—I was right in a way that made it clear everyone else was wrong.
My code reviews were surgical. I'd spot a potential race condition three levels deep in async logic, or catch a missing null check, and I'd comment with surgical precision. The feedback was accurate. The tone, though? It landed like criticism from someone who'd already decided the code was suboptimal before reading it.
I didn't realize I was doing this until a senior engineer pulled me aside after I'd left a particularly brusque comment on a junior's pull request. He didn't lecture me about kindness or empathy. He just asked: "What do you think that person will do the next time they're uncertain about async patterns?"
The answer was obvious. They'd avoid asking for code review. They'd ship less, review less, and eventually become more cautious—not more careful, but more defensive.
That question broke something open in me. I realized I'd been optimizing for the wrong metric: the quality of individual comments instead of the health of our feedback loop.
The Before: When Code Review Became a Gauntlet
Let me ground this with a specific example. A mid-level engineer named Marcus submitted a PR that refactored our event publishing system. The logic was sound, but he'd used a pattern I didn't prefer: storing event metadata in a closure instead of passing it through function arguments.
My comment:
"Why are you using closure state here? This couples the event metadata to the handler scope. We should be passing this as an argument. See the pattern we use in
lib/events.ts—much cleaner."
Technically? Correct. The closure approach was less explicit, harder to test in isolation, and less consistent with our codebase conventions.
Marcus's response was defensive: "I tested it. It works." He didn't update the code. He didn't ask why the pattern mattered. He just pushed back, and I pushed back harder with links to architectural docs.
The PR sat for three days. We went back and forth. It was adversarial. Eventually, he capitulated and changed it, but I could feel the resentment. More importantly, I'd taught him that code review was a place where he'd be corrected, not guided.
Three months later, when Marcus had a question about testing strategy, he asked another engineer instead of me. When he found a subtle bug in his own code, he fixed it quietly without flagging it for discussion. He was optimizing his behavior around avoiding my feedback, not around learning.
The Inflection Point: Realizing the Problem Wasn't the Code
The turning point came when I reviewed a pull request from our newest hire, Priya. She'd written a database query that was inefficient—it would do N+1 queries in certain conditions.
My instinct was to correct it immediately. But I paused and asked myself: What is the actual problem I'm solving here?
The N+1 query would cause issues, yes. But Priya had written clear, readable code. She'd included tests. The bug would catch in staging or load testing. And—crucially—she had written something that worked, which is harder than it looks.
Instead of my usual comment, I tried something different:
"I'm noticing this query pattern might create N+1 issues if
usersis large. I hit a similar problem last quarter with the notifications table. Have you load-tested this with a realistic data size? Happy to pair on optimizing if you want to explore it."
The difference was subtle but fundamental: I'd moved from correcting to noticing together. I'd implied that (1) I had context for why this mattered, (2) this was a solvable problem, not a failure, and (3) I was available to help, not just to judge.
Priya's response: "Oh, good catch. I wasn't sure about the scale. Let me run some tests and see if it's actually a problem."
She came back with data. The query was actually fine for our dataset. We discussed potential future scaling issues, and she left the code as-is with everyone aligned on why. More importantly, she'd learned something about thinking through scale without feeling defensive.
The Pattern That Changed Everything
Once I started noticing what worked, I realized there were three distinct patterns in how I was giving feedback:
Pattern 1: The Correction (What I used to do)
- "This is wrong/inefficient/not our style"
- Implies: You missed something obvious
- Response: Defensiveness or compliance
Pattern 2: The Question (What I started trying)
- "What happens if X?" or "Have you considered Y?"
- Implies: There's something to explore here together
- Response: Curiosity and ownership
Pattern 3: The Observation + Context (What actually scaled)
- "I'm noticing [specific thing]. I hit something similar with [past example]. Here's why it mattered: [consequence]. Want to explore together?"
- Implies: This is a pattern, I have context, you're capable, we're a team
- Response: Engagement and learning
The third pattern worked because it did three things simultaneously:
-
It was specific without being accusatory. "I'm noticing" is observational. It's not "you did this wrong."
-
It provided context. By sharing my own past mistake, I normalized the problem as a category of thing that happens, not a personal failure.
-
It offered partnership. "Want to explore together?" is fundamentally different from "you should change this."
Where This Mattered Most: The Async Refactor
The real test came during a major refactor. Our codebase had accumulated async patterns from different eras—some using callbacks, some using Promises, some using async/await. A talented engineer named James was tasked with standardizing this.
James's first pass was... rough. He'd converted everything to async/await, but he'd missed some edge cases around error handling. In a few places, errors would silently fail instead of being caught.
Old me would have responded with a detailed comment pointing out each issue, maybe with a hint of "you need to think more carefully about error semantics."
Instead, I did something different. I left a high-level comment:
"I can see where you're heading with this—async/await is the right direction. I'm noticing some error cases that might slip through silently (lines 34, 67, 112). This is tricky because different parts of the system have different error contracts. Before you fix these individually, want to pair for 30 minutes? I want to make sure we're consistent about how errors propagate across the whole refactor. I've got some patterns from the payment system that might help."
What I did here:
- Acknowledged the direction and effort ("I can see where you're heading")
- Named the specific problem without judgment ("I'm noticing")
- Explained why it mattered ("different parts have different contracts")
- Offered partnership and context ("want to pair? I've got patterns")
James responded immediately: "Yeah, that would help. I wasn't sure how strict to be with error handling."
We paired for 45 minutes. I didn't tell him what to do. I asked him questions: "What happens in the payment system if this errors? What about the event system?" We developed a consistent pattern together. He finished the refactor with clarity instead of uncertainty.
More importantly: when James hit similar problems in future PRs, he didn't wait for me to catch them. He'd ask proactively because he understood the principle, not just the specific fix.
How This Changed Our Shipping Speed
Here's the part that surprised me: this shift in feedback style made us faster, not slower.
When code review was adversarial, PRs took longer because:
- Engineers would be defensive and less receptive to feedback
- They'd make minimal changes instead of understanding the principle
- Reviewers (like me) would need to review multiple times because fixes were surface-level
- People would avoid certain kinds of changes because they knew review would be painful
When code review became collaborative:
- Engineers understood why feedback mattered, so they fixed root causes
- People asked questions before writing code, not after
- Review cycles shortened because code was better-thought-through
- More people felt comfortable taking on ambitious refactors
We didn't measure this formally, but the proxy was clear: our average PR review cycle dropped from 2-3 days to same-day or next-day. Not because people were rushing, but because the feedback was clearer and people weren't bracing for criticism.
The Language Shifts That Mattered Most
Looking back, a few specific language patterns made the biggest difference:
Instead of: "This pattern is inefficient"
Try: "I'm noticing this could have scaling issues with [specific scenario]. Have you load-tested with realistic data?"
Instead of: "You need to handle this error case"
Try: "What happens if this fails? I'm thinking about [consequence]—is that something we need to guard against?"
Instead of: "This doesn't match our style"
Try: "I see you went with [approach]. We've been standardizing on [other approach] because [reason]. Want to align with that?"
Instead of: "Why did you do it this way?"
Try: "I'm curious about your thinking here—what made [approach] feel right for this problem?"
The shift isn't about being softer. It's about being more specific about what you observed and why it matters, while assuming the person has good judgment and just needs context.
The Moment It Became Real Leadership
About six months into this shift, something happened that made me realize this wasn't just about being nicer. It was about a fundamental change in what I thought my job was.
A junior engineer submitted a PR with a subtle concurrency bug. In the old days, I would have caught it and commented. But this time, I realized: should I catch it, or should I help her learn to catch it?
I