From Engineer to Lead: The Hardest Mental Model Shifts
From Engineer to Lead: The Hardest Mental Model Shifts
Not a soft-skills post — specifically about how your relationship with code, quality, and velocity has to change when your output is other people's output
The Moment Everything Changes
You were promoted because you were an excellent engineer. You shipped fast. Your code was clean. You made good architectural decisions. You were the person others came to when they were stuck.
Now you're a lead. And every instinct that made you successful is about to betray you.
The hardest part isn't learning to manage people—there are books for that. The hardest part is rewiring your brain to accept that your job is no longer to write great code. Your job is to maximize the output of your team. And those two things are often in direct conflict.
This post is about the specific mental model shifts that nobody tells you about—the ones that feel wrong until they suddenly click.
Mental Model Shift #1: Your Hands Are Now a Liability
The Old Model
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE IC MENTAL MODEL │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Problem appears │
│ │ │
│ ▼ │
│ You solve it │
│ │ │
│ ▼ │
│ Problem solved │
│ │ │
│ ▼ │
│ You feel good │
│ │
│ Time: Fast │
│ Quality: High (your standard) │
│ Learning: You learned │
│ Scalability: 1x (only you) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The New Model
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE LEAD MENTAL MODEL │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Problem appears │
│ │ │
│ ▼ │
│ You assign it (with context) │
│ │ │
│ ▼ │
│ Someone struggles (this is fine) │
│ │ │
│ ▼ │
│ You guide, don't solve │
│ │ │
│ ▼ │
│ They solve it │
│ │ │
│ ▼ │
│ They feel good + they learned + they can do it next time │
│ │
│ Time: Slower (this time) │
│ Quality: "Good enough" (maybe not your standard) │
│ Learning: THEY learned │
│ Scalability: Nx (they can now do this and teach others) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Why This Is Hard
// Your brain as an IC:
function solveProblems(problems: Problem[]): Solution[] {
return problems.map(p => solve(p)); // Direct, satisfying
}
// Your brain as a lead:
function solveProblems(problems: Problem[], team: Engineer[]): Solution[] {
return problems.map(p => {
const assignee = findBestLearningOpportunity(p, team);
const solution = delegate(p, assignee); // Painful
// This is the hard part:
// solution.quality < whatYouWouldHaveDone
// BUT
// team.capability += learningGained
// AND
// you.availability += timeNotSpentCoding
return solution;
});
}
The Counter-Intuitive Math
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE LEVERAGE CALCULATION │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Scenario: Complex bug needs fixing. You could fix it in 2 hours. │
│ Junior dev will take 8 hours with your guidance (1 hour). │
│ │
│ Option A: You fix it │
│ ├── Your time: 2 hours │
│ ├── Output: 1 bug fixed │
│ ├── Learning: None (you already knew how) │
│ └── Next time: Still depends on you │
│ │
│ Option B: You guide, they fix │
│ ├── Your time: 1 hour │
│ ├── Their time: 8 hours │
│ ├── Total time: 9 hours (seems worse!) │
│ ├── Learning: They can now fix similar bugs │
│ └── Next time: They handle it alone │
│ │
│ Over 6 months with 10 similar bugs: │
│ │
│ Option A: You fix all │
│ └── Your time: 20 hours │
│ │
│ Option B: You guide first 2, they fix rest │
│ └── Your time: 2 hours (guidance) + 0 (they handle the rest) │
│ │
│ The "slower" approach is 10x faster at scale. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Practical Application
// When to still code yourself:
const shouldICodeThis = (task: Task): boolean => {
// Exploratory work that will inform architecture
if (task.type === 'spike' && task.willInfluenceArchitecture) return true;
// Nobody else can do it AND it's blocking AND deadline is real
if (task.isBlocking && task.deadline.isReal && !task.canBeDelegated) return true;
// You need to stay sharp in a critical area
if (task.area === 'your-area-of-expertise' && thisQuarter.codingHours < minimum) return true;
// Everything else: probably not
return false;
};
// How to delegate effectively:
interface DelegationContext {
whatToDoUrl: string; // Clear specification or ticket
whyItMatters: string; // Context they need
howToApproach: string[]; // Suggested starting points
whatGoodLooksLike: string; // Definition of done
whereToGetHelp: string; // You, docs, or another person
deadline: Date | 'flexible'; // Real deadline or learning opportunity
}
function delegate(task: Task, to: Engineer, context: DelegationContext): void {
// The context is the leverage. Without it, delegation becomes abdication.
// With it, delegation becomes multiplication.
}
Mental Model Shift #2: "Good Enough" Is Actually Optimal
The Quality Paradox
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE QUALITY CURVE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Value │
│ │ │
│ │ ┌──── Diminishing returns zone │
│ │ ╱│ (where ICs often live) │
│ │ ●──────────────● │ │
│ │ ╱ │ │
│ │ ╱ "Good enough"──┤ │
│ │ ╱ │ │
│ │ ╱ │ │
│ │ ╱ │ │
│ │ ● │ │
│ │ ╱ │ │
│ │ ╱ │ │
│ │╱ │ │
│ └──────────────────────────┼──────────────────────────────► Effort │
│ │ │
│ │ │
│ IC instinct: "Go to the right" │
│ Lead wisdom: "Stop at good enough, do more things" │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
What "Good Enough" Actually Means
// This is NOT lowering your standards.
// This is raising your awareness of what matters.
interface QualityDimensions {
correctness: QualityLevel; // Does it work?
performance: QualityLevel; // Is it fast enough?
maintainability: QualityLevel; // Can we change it later?
security: QualityLevel; // Is it safe?
testCoverage: QualityLevel; // Can we verify it works?
codeStyle: QualityLevel; // Is it readable?
documentation: QualityLevel; // Can others understand it?
}
type QualityLevel = 'minimal' | 'adequate' | 'good' | 'excellent' | 'perfect';
// As an IC, you might have optimized everything to 'excellent'
// As a lead, you need to ask: "What level is required for THIS code?"
function determineRequiredQuality(code: Code): QualityDimensions {
// Core payment processing
if (code.area === 'payments') {
return {
correctness: 'perfect', // Money cannot be wrong
performance: 'good', // Fast enough
maintainability: 'excellent', // We'll change this often
security: 'perfect', // Non-negotiable
testCoverage: 'excellent', // Must verify everything
codeStyle: 'good', // Readable
documentation: 'excellent' // Others must understand
};
}
// Internal admin tool used by 3 people
if (code.area === 'internal-admin' && code.users < 10) {
return {
correctness: 'good', // Should work, bugs are fixable
performance: 'adequate', // Nobody cares if it takes 2 seconds
maintainability: 'adequate', // Low change frequency
security: 'good', // Still important, but internal
testCoverage: 'minimal', // Happy path is fine
codeStyle: 'adequate', // Readable enough
documentation: 'minimal' // The 3 users can ask
};
}
// One-time data migration script
if (code.type === 'migration' && code.runOnce) {
return {
correctness: 'excellent', // Must work, can't re-run easily
performance: 'adequate', // Run overnight if needed
maintainability: 'minimal', // Will be deleted
security: 'good', // Still matters
testCoverage: 'good', // Test on staging
codeStyle: 'minimal', // Nobody will read this again
documentation: 'minimal' // Just document how to run it
};
}
}
The Hard Conversation
┌─────────────────────────────────────────────────────────────────────────────┐
│ WHEN TO PUSH FOR BETTER VS. ACCEPT │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Push for better when: │
│ ───────────────────── │
│ • It's in a hot path (performance matters) │
│ • It's in a security-critical area │
│ • It will be read/modified by many people │
│ • It establishes a pattern others will copy │
│ • The tech debt will compound quickly │
│ • It's the kind of thing that causes incidents │
│ │
│ Accept "good enough" when: │
│ ────────────────────────── │
│ • The code is isolated (limited blast radius) │
│ • It's a learning experience for a junior engineer │
│ • Perfecting it won't change user outcomes │
│ • The refactor can happen later with no additional cost │
│ • You're trading style preferences, not substance │
│ • The deadline is real and the core is solid │
│ │
│ The skill is knowing the difference. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mental Model Shift #3: Your Opinion Is Now Weighted
The Danger of Speaking First
┌─────────────────────────────────────────────────────────────────────────────┐
│ THE ANCHORING PROBLEM │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Before you were lead: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ You: "I think we should use PostgreSQL" │ │
│ │ Senior: "Actually, DynamoDB makes more sense because..." │ │
│ │ You: "Good point, let's go with that" │ │
│ │ Outcome: Best idea wins │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ After you became lead: │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ You: "I think we should use PostgreSQL" │ │
│ │ Team: "..." (silence) │ │
│ │ Team: "Yeah, PostgreSQL sounds good" │ │
│ │ (That senior who would have suggested DynamoDB says nothing) │ │
│ │ Outcome: Your idea wins by default │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ What happened: Your title changed the dynamics. │
│ People self-censor around authority. │
│ Your "opinion" became the "answer" before discussion started. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The New Discipline
// As a lead, you need to change how you participate in technical discussions
// OLD WAY (as IC):
function technicalDiscussion(problem: Problem): Solution {
const yourIdea = thinkAboutProblem(problem);
share(yourIdea);
const discussion = debateWithPeers([yourIdea, ...othersIdeas]);
return bestIdea(discussion);
}
// NEW WAY (as lead):
function technicalDiscussion(problem: Problem): Solution {
// Step 1: Frame the problem, not the solution
const framing = {
problem: problem.description,
constraints: problem.constraints,
nonGoals: problem.whatWeAreNotSolving,
evaluationCriteria: howWeWillJudgeSolutions()
};
share(framing);
// Step 2: SHUT UP AND LISTEN
const ideas = await collectIdeasFromTeam(); // You speak last
// Step 3: If you have input, frame it as questions
// NOT: "I think we should use PostgreSQL"
// BUT: "What would be the tradeoffs of PostgreSQL vs DynamoDB here?"
// Step 4: Only share your opinion if:
// - Asked directly
// - The team is stuck
// - There's a critical factor they're missing
// - You need to break a tie
// Step 5: When you do share, be explicit about weight
share({
opinion: yourIdea,
weight: 'just-my-opinion' | 'strong-preference' | 'this-is-decided'
// Most should be 'just-my-opinion'
});
}
Explicit Weight Labeling
// Create explicit language for opinion weight
enum OpinionWeight {
// "Here's one idea among many, I'm genuinely curious what you think"
JUST_THINKING_OUT_LOUD = 'just_thinking',
// "I have a preference, but I want to hear alternatives"
LEANING_TOWARD = 'leaning',
// "I've thought about this a lot, convince me otherwise"
STRONG_OPINION = 'strong_opinion',
// "This is decided, here's why"
DECISION = 'decision'
}
// Use these explicitly:
// "I'm just thinking out loud here, but what if we..."
// "I'm leaning toward X, but I want to hear what you all think first"
// "I have a strong opinion on this—let me share it and then hear pushback"
// "I've decided we're going with X, here's the reasoning..."
Mental Model Shift #4: Consistency Beats Brilliance
The Pattern Library Problem
┌─────────────────────────────────────────────────────────────────────────────┐
│ CONSISTENCY VS. BRILLIANCE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Codebase A (Brilliant but Inconsistent): │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ service-user/ # Clean architecture, DDD │ │
│ │ service-orders/ # Hexagonal architecture │ │
│ │ service-payments/ # Simple MVC │ │
│ │ service-inventory/ # Event sourcing + CQRS │ │
│ │ service-shipping/ # Whatever the intern did │ │
│ │ │ │
│ │ Each service: Locally optimal, "the best pattern for the problem" │ │
│ │ Result: Engineers can't move between services │ │
│ │ Every PR requires learning a new architecture │ │
│ │ Onboarding takes months │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Codebase B (Consistent but "Boring"): │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ service-user/ # Standard layered architecture │ │
│ │ service-orders/ # Standard layered architecture │ │
│ │ service-payments/ # Standard layered architecture │ │
│ │ service-inventory/ # Standard layered architecture │ │
│ │ service-shipping/ # Standard layered architecture │ │
│ │ │ │
│ │ Each service: Maybe not locally optimal │ │
│ │ Result: Engineers move freely between services │ │
│ │ PRs are quick to review │ │
│ │ Onboarding takes weeks │ │
│ │ Team velocity: 3x faster │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ As an IC, you optimize for local perfection. │
│ As a lead, you optimize for global throughput. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The "Best Tool for the Job" Trap
// IC thinking:
function chooseTechnology(problem: Problem): Technology {
const allOptions = getAllPossibleTechnologies();
const bestFit = allOptions.find(t => t.fitScore(problem) === max);
return bestFit; // Optimal for this problem!
}
// Lead thinking:
function chooseTechnology(problem: Problem, context: TeamContext): Technology {
const existingStack = context.currentTechnologies;
const teamExpertise = context.teamSkills;
const hiringMarket = context.abilityToHire;
const candidates = existingStack.filter(t => t.canSolve(problem));
if (candidates.length > 0) {
// Use something we already have
return candidates.sort(byTeamFamiliarity)[0];
}
// Only introduce new technology if:
// 1. Existing tech genuinely can't solve the problem
// 2. The problem is important enough to justify the cost
// 3. We have capacity to become experts in it
// 4. We can hire people who know it
const newTechCost = {
learningCurve: hoursToProductivity * teamSize,
maintenanceBurden: ongoingOverhead,
hiringImpact: narrowsPoolBy,
cognitiveLoad: oneMoreThingToRemember
};
return newTechCost.totalCost < problem.valueOfOptimalSolution
? evaluateNewOptions(problem)
: existingStack.findClosestFit(problem);
}
When to Break Consistency
┌─────────────────────────────────────────────────────────────────────────────┐
│ WHEN TO DEVIATE FROM THE STANDARD │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Deviate when: │
│ ───────────── │
│ • The standard genuinely cannot solve the problem │
│ (not "it's harder", but "it's impossible") │
│ • The domain is fundamentally different │
│ (ML pipeline vs CRUD API are different enough) │
│ • You're establishing a NEW standard │
│ (intentional evolution, not one-off exception) │
│ • The cost of conforming is greater than the cost of diverging │
│ (rare, do the math honestly) │
│ │
│ Don't deviate when: │
│ ────────────────── │
│ • "This new framework is better" (maybe, but switching cost is real) │
│ • "I'm more productive in X" (but the team isn't) │
│ • "It's the right tool for the job" (right tool != right choice) │
│ • "Let's experiment" (experiments need explicit scope and cleanup) │
│ │
│ If you deviate, document WHY extensively. │
│ Future engineers need to understand if this is: │
│ a) The new standard to follow │
│ b) An exception that shouldn't be repeated │
│ c) Legacy that should be migrated │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mental Model Shift #5: Velocity Is Not Your Typing Speed
The New Definition of Fast
┌─────────────────────────────────────────────────────────────────────────────┐
│ REDEFINING VELOCITY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ IC Velocity (what you optimized for): │
│ ────────────────────────────────────── │
│ • Time from starting task to PR merged │
│ • Lines of code shipped │
│ • Tickets closed │
│ • Features completed │
│ │
│ Lead Velocity (what you now optimize for): │
│ ─────────────────────────────────────── │
│ • Team throughput (total tickets closed by team) │
│ • Time from idea to production (including reviews, QA, deploy) │
│ • Cycle time reduction │
│ • Unblocking time (how fast can the team get unstuck) │
│ • Rework rate (how often do we redo things) │
│ • Lead time for new engineers to become productive │
│ │
│ You might be "slow" (writing less code) while the team is "fast" │
│ (shipping more than ever). That's success. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
What Actually Slows Teams Down
// Things you now spend time on (that feel like "not working"):
interface TeamMultipliers {
// These feel slow but have massive ROI
writingDocumentation: {
feels: 'slow',
reality: 'Saves 100 hours of questions'
};
creatingTemplates: {
feels: 'slow',
reality: 'Every new service starts 80% done'
};
improvingCICD: {
feels: 'slow',
reality: 'Every engineer saves 30 min/day'
};
reviewingPRsCarefully: {
feels: 'slow',
reality: 'Prevents tech debt, grows engineers'
};
writingRFCs: {
feels: 'slow',
reality: 'Prevents building wrong thing'
};
mentoringJuniors: {
feels: 'slow',
reality: 'In 6 months, they mentor others'
};
attendingPlanningMeetings: {
feels: 'slow',
reality: 'Engineers work on right things'
};
}
// Things that feel productive but aren't
interface ProductivityIllusions {
rewritingJuniorCode: {
feels: 'fast',
reality: 'They learned nothing, will repeat mistake'
};
skippingCodeReview: {
feels: 'fast',
reality: 'Tech debt and bugs multiply'
};
solvingProblemsYourself: {
feels: 'fast',
reality: 'Team stays dependent on you'
};
avoidingMeetings: {
feels: 'productive',
reality: 'Misalignment causes rework'
};
workingWeekends: {
feels: 'dedicated',
reality: 'Burnout and normalizing crunch'
};
}
The Force Multipliers
┌─────────────────────────────────────────────────────────────────────────────┐
│ LEAD FORCE MULTIPLIERS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Unblocking (Highest ROI) │
│ ────────────────────────── │
│ Engineer blocked = $X00/hour wasted │
│ Your time to unblock = usually < 30 minutes │
│ Always prioritize unblocking over your own work │
│ │
│ 2. Context Distribution │
│ ─────────────────────── │
│ You have context others don't (roadmap, cross-team, business) │
│ Sharing context = engineers make better decisions autonomously │
│ Write it down, don't just tell individuals │
│ │
│ 3. Decision Velocity │
│ ──────────────────── │
│ Teams often wait for permission they don't need │
│ Clarify decision rights: "You can decide X without asking" │
│ Make reversible decisions quickly, irreversible ones carefully │
│ │
│ 4. Removing Obstacles │
│ ──────────────────── │
│ Cross-team dependencies, unclear requirements, process friction │
│ Shield the team from organizational chaos │
│ Escalate and resolve blockers they can't │
│ │
│ 5. Hiring and Growing │
│ ──────────────────── │
│ One great hire = 2x team output (or more) │
│ One engineer's growth = another hire you didn't need │
│ This is slow work with the highest leverage │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Mental Model Shift #6: Your Failures Are Now Public
The Visibility Asymmetry
┌─────────────────────────────────────────────────────────────────────────────┐
│ FAILURE VISIBILITY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ As an IC: │
│ ─────────── │
│ You debug for 4 hours → Nobody knows │
│ You make a wrong decision → You fix it quietly │
│ You have a bad day → Your code doesn't ship, you catch up tomorrow │
│ You're unsure → You research until you're confident │
│ │
│ As a lead: │
│ ──────────── │
│ You debug for 4 hours → Team wonders where you are │
│ You make a wrong decision → Team followed your advice, now fixing │
│ You have a bad day → Team notices, morale affected │
│ You're unsure → Team needs an answer, you can't always research first │
│ │
│ The transition: │
│ ──────────────── │
│ Learning to be wrong publicly is a skill. │
│ Learning to say "I don't know, let me find out" is a skill. │
│ Learning to change your mind publicly is a skill. │
│ Learning to give direction with incomplete information is a skill. │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
How to Be Wrong Well
// Being wrong is inevitable. Being wrong well is a skill.
interface BeingWrongWell {
// When you realize you were wrong:
acknowledge: {
do: 'Own it clearly: "I was wrong about X"',
dont: 'Minimize: "Well, the situation changed"'
};
// Share what you learned:
explain: {
do: '"Here\'s what I learned and how I\'ll think about it differently"',
dont: 'Over-explain or make excuses'
};
// Make it safe for others:
normalize: {
do: '"This is why I encourage everyone to push back on my ideas"',
dont: 'Act infallible, team will stop challenging you'
};
// Fix forward:
action: {
do: '"Here\'s the plan to correct course"',
dont: 'Dwell on the mistake'
};
}
// Specific phrases that work:
const usefulPhrases = [
"I was wrong about that. Here's what I've learned...",
"I changed my mind. Here's why...",
"I gave you bad advice. Let's fix it together.",
"I don't know yet. I'll find out by [time].",
"I was operating on incomplete information. Now that I know X...",
"That was a bad call on my part. What should we do now?",
];
Mental Model Shift #7: The Code Review is the Product
From Verification to Mentorship
┌─────────────────────────────────────────────────────────────────────────────┐
│ CODE REVIEW MENTAL MODEL SHIFT │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ IC Code Review: │
│ ─────────────── │
│ Purpose: Verify correctness │
│ Duration: As fast as possible │
│ Output: LGTM or changes requested │
│ Value: Bugs caught │
│ │
│ Lead Code Review: │
│ ───────────────── │
│ Purpose: Grow engineers + ensure quality │
│ Duration: As long as valuable │
│ Output: Learning opportunity + shipped code │
│ Value: Engineer skill growth + patterns established │
│ │
│ What changes: │
│ ───────────── │
│ • You're not just catching bugs, you're teaching │
│ • You're not just reviewing code, you're establishing patterns │
│ • Your comments will be referenced by others ("Sarah said to do it X way") │
│ • How you review affects team culture more than any document │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Code Review as Teaching
// Transform your code review comments
// Instead of just fixing, teach:
// BAD (just corrects):
// "Use const instead of let here"
// GOOD (teaches):
// "Use const instead of let here. The rule I follow: start with const,
// only use let when you genuinely need reassignment. Makes it easier
// to reason about variable changes in a function."
// BAD (just rejects):
// "This will have N+1 query issues"
// GOOD (teaches):
// "This will have N+1 query issues. For each user in the loop, we're
// making a separate DB call. Instead, batch them: get all the IDs first,
// make one query for all orders, then join them in code. Want me to
// show you a pattern for this?"
// BAD (dictates):
// "Rename this to getUserOrders"
// GOOD (explains principle):
// "Consider renaming to getUserOrders. I try to follow the pattern:
// verbNoun for functions (getUser, createOrder), nounPhrase for
// variables (userOrders, activeUsers). Makes it easier to predict
// names when reading code."
// TIERING comments by importance:
// 🔴 Blocker: "This will cause data loss, must fix"
// 🟡 Important: "This will be hard to maintain, strongly suggest changing"
// 🟢 Suggestion: "Consider X, but fine either way"
// 💭 Nitpick: "Take or leave: I'd personally do X"
// Be explicit about which type each comment is
When to Let It Go
// As a lead, you need to pick your battles
interface ShouldICommentDecision {
comment(issue: CodeIssue): boolean {
// Always comment:
if (issue.causesBugs) return true;
if (issue.isSecurityVulnerability) return true;
if (issue.willCauseIncidents) return true;
if (issue.violatesExplicitTeamStandard) return true;
if (issue.teachesImportantLesson && engineer.isJunior) return true;
// Consider not commenting:
if (issue.isStylePreference) return maybe(); // Only if egregious
if (issue.youWouldDoItDifferently) return no();
if (issue.isMinorInefficiency) return maybe();
if (issue.isInCodeYoullNeverSeeAgain) return probably_no();
// Ask yourself:
// "Is this hill worth dying on?"
// "Is commenting here going to help this engineer grow?"
// "Am I optimizing for my taste or team success?"
}
}
// The paradox: having high standards doesn't mean commenting on everything.
// It means knowing which things matter and being extremely consistent on those.
The Transition Cheat Sheet
┌─────────────────────────────────────────────────────────────────────────────┐
│ LEAD MENTAL MODEL CHEAT SHEET │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ FROM TO │
│ ────────────────────────────────────────────────────────────────────── │
│ I solve problems → I ensure problems get solved │
│ My code is my output → Team's code is my output │
│ I have the best ideas → I surface the best ideas │
│ I optimize for perfect → I optimize for shipped │
│ I speak first → I speak last │
│ I decide fast → I help the team decide │
│ I work on hard problems → I make problems easier for others │
│ I know the answer → I know who knows the answer │
│ I fix the bug → I grow the engineer who'll fix future bugs │
│ My velocity matters → Team's velocity matters │
│ I stay heads-down → I create heads-down time for others │
│ I avoid meetings → I make meetings unnecessary │
│ │
│ What stays the same: │
│ ───────────────────── │
│ • High standards (just applied differently) │
│ • Technical judgment (now for guidance, not implementation) │
│ • Curiosity (about people and systems, not just code) │
│ • Ownership (of outcomes, not just code) │
│ • Craft (of leadership, not just engineering) │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
The Uncomfortable Questions
Answer these honestly:
1. When was the last time someone on your team pushed back on your technical opinion?
□ This week → Good, they feel safe
□ This month → Concerning
□ Can't remember → Your ideas aren't being challenged
2. When you review code, what's your comment-to-praise ratio?
□ Mostly praise with targeted critiques → Healthy
□ Mostly critiques → You might be demoralizing people
□ I don't give praise → You're definitely demoralizing people
3. How often do you write code vs. unblock others writing code?
□ Mostly unblocking → Probably right for a lead
□ 50/50 → Context dependent, might be fine
□ Mostly coding → Either wrong priorities or team is too senior
4. When a junior engineer does something "the wrong way," what do you do?
□ Explain why and guide them to fix it → Teaching
□ Fix it for them → Faster, but they learned nothing
□ Let it go → Sometimes right, but consistently means avoiding growth
5. When you don't know the answer, what do you say?
□ "I don't know, let me find out" → Healthy
□ "Let me think about that" (then never follow up) → Trust erosion
□ I always have an answer → Either genius or lying
Closing Thoughts
The transition from engineer to lead isn't a promotion—it's a career change that happens to use some of the same skills. Your relationship with code changes fundamentally:
-
Code is no longer your craft. It's one of many tools in your toolkit. Your craft is now making teams effective.
-
Quality is now about trade-offs. You're not pursuing local perfection; you're pursuing global optimization.
-
Speed is now about leverage. An hour spent unblocking three engineers is worth more than an hour of your own coding.
-
Your opinion is now expensive. Use it sparingly, or watch your team stop thinking for themselves.
The engineers who struggle most in this transition are the ones who were the best individual contributors. Their instincts are perfectly honed for the wrong job.
The ones who thrive are the ones who can genuinely take joy in someone else's code shipping, someone else's architecture winning the debate, someone else getting the credit. If you can't find satisfaction in that, you might be happier staying on the IC track—and there's no shame in that.
But if you can make that shift, you'll have more impact than you ever could have had alone. You'll see engineers you mentored become leads themselves, patterns you established become company standards, and teams you built ship things you never could have built by yourself.
That's the job. It's not about your code anymore. It's about everyone else's.
The best leads don't build great software. They build great teams that build great software.
What did you think?