Negotiating Compensation as a Software Engineer
A framework for negotiating engineering compensation: research, timing, competing offers, equity evaluation and the phrases that move a conversation.

Most software engineers leave money on the table during compensation negotiations — not because they lack skill, but because they lack a framework. Negotiation is not about being aggressive or making demands. It is a structured conversation where both sides work toward an agreement that reflects the value being exchanged. Companies expect negotiation. Recruiters have ranges. The initial offer is almost never the best offer.
The difference between accepting the first number and negotiating effectively can be tens of thousands of dollars per year, compounding over an entire career.
Understanding Total Compensation
Base salary is only one component. Total compensation (TC) at most tech companies includes base salary, equity (RSUs or options), signing bonus, annual bonus, and benefits. Comparing offers requires understanding all components.
interface CompensationPackage {
baseSalary: number;
equity: EquityComponent;
signingBonus: number;
annualBonus: number; // Often a percentage of base
benefits: string[];
}
interface EquityComponent {
type: 'rsu' | 'options';
totalValue: number; // Total grant value
vestingScheduleYears: number; // Typically 4 years
cliffYears: number; // Typically 1 year
annualVesting: number[]; // e.g., [25, 25, 25, 25] for uniform
}
function calculateAnnualTC(pkg: CompensationPackage, year: number): number {
const annualEquity = pkg.equity.annualVesting[year - 1]
? (pkg.equity.totalValue * pkg.equity.annualVesting[year - 1]) / 100
: 0;
const bonus = pkg.baseSalary * (pkg.annualBonus / 100);
const signing = year === 1 ? pkg.signingBonus : 0;
return pkg.baseSalary + annualEquity + bonus + signing;
}
// ❌ Comparing offers by base salary alone
// Offer A: $170k base
// Offer B: $160k base ← appears worse
// But Offer B has $200k RSU over 4 years + $30k signing
// ✅ Compare total first-year compensation
const offerA: CompensationPackage = {
baseSalary: 170_000,
equity: { type: 'rsu', totalValue: 80_000, vestingScheduleYears: 4, cliffYears: 1, annualVesting: [25, 25, 25, 25] },
signingBonus: 10_000,
annualBonus: 10,
benefits: ['health', '401k-match'],
};
const offerB: CompensationPackage = {
baseSalary: 160_000,
equity: { type: 'rsu', totalValue: 200_000, vestingScheduleYears: 4, cliffYears: 1, annualVesting: [25, 25, 25, 25] },
signingBonus: 30_000,
annualBonus: 15,
benefits: ['health', '401k-match', 'education-stipend'],
};
// Year 1 TC:
// Offer A: $170k + $20k equity + $17k bonus + $10k signing = $217k
// Offer B: $160k + $50k equity + $24k bonus + $30k signing = $264k
// Offer B wins by $47k despite lower baseResearch Before You Negotiate
Negotiation starts before the conversation. You need data — what the market pays for your experience level, what the specific company's range is, and what your alternatives are.
# Research sources for compensation data
primary_sources:
- name: "levels.fyi"
strength: "Verified TC data by company, level, and location"
use: "Find the range for your target company and level"
- name: "Glassdoor"
strength: "Large sample size, includes non-tech companies"
use: "Baseline for companies not well-represented on levels.fyi"
- name: "Blind"
strength: "Anonymous reports with detailed breakdowns"
use: "Recent offers and negotiation outcomes"
- name: "Competing offers"
strength: "Your strongest leverage — concrete alternative"
use: "Anchoring negotiation higher with real numbers"
# What to research
data_points:
- "Median TC for your level at the target company"
- "Range (25th to 75th percentile) for TC"
- "Typical equity grant size and vesting schedule"
- "Standard signing bonus range"
- "Whether annual refreshers are common"
- "Cost of living adjustment for your location"// ❌ Walking into negotiation without data
// "I think I deserve more" — subjective, easy to dismiss
// ✅ Walking in with specific, sourced numbers
interface NegotiationBrief {
targetRole: string;
marketData: MarketRange;
currentOffer: number;
counterTarget: number;
justification: string[];
}
interface MarketRange {
p25: number;
median: number;
p75: number;
source: string;
}
const brief: NegotiationBrief = {
targetRole: 'Senior Software Engineer',
marketData: {
p25: 210_000,
median: 245_000,
p75: 280_000,
source: 'levels.fyi, verified 2021 data, same metro area',
},
currentOffer: 220_000,
counterTarget: 260_000,
justification: [
'Current offer falls at the 25th percentile for this role and location',
'My 6 years of distributed systems experience aligns with senior expectations',
'I have a competing offer at $250k TC from a comparable company',
],
};The Negotiation Conversation
Timing and phrasing matter. Here is a framework for the actual conversation.
## Phase 1: Receive the offer (do NOT respond immediately)
Recruiter: "We'd like to offer you $170k base with $120k RSU over 4 years."
You: "Thank you — I'm excited about this opportunity. I'd like to
take a day to review the full package. Can you send the details
in writing so I can look at everything together?"
→ This is not stalling. It prevents anchoring bias and gives
you time to calculate total comp and prepare your response.
## Phase 2: Express enthusiasm, then present your counter
"I'm very enthusiastic about joining [Company]. After reviewing the
package and comparing it with my market research, I'd like to discuss
the compensation. Based on data from levels.fyi for this role and
location, the median total compensation is around $245k. Given my
experience with [specific relevant skill], I was hoping we could
get closer to $260k total. Is there flexibility in the base salary
or equity grant?"
→ Key elements:
- Lead with enthusiasm (you want the job)
- Reference specific data (not subjective feelings)
- Name a specific number (gives recruiter a target)
- Ask about flexibility (opens the door for creative solutions)
## Phase 3: Handle pushback
Recruiter: "The base salary is at the top of the band for this level."
You: "I understand base salary has constraints. Are there other
components we could adjust? For example, a larger equity grant
or a signing bonus to bridge the gap in the first year?"
→ If one lever is stuck, try others:
- Equity grant increase
- Signing bonus
- Annual bonus target
- Early review / promotion timeline
- Education budget
- Remote work flexibilityEquity Evaluation
Equity is the most commonly misunderstood compensation component. RSUs at a public company are straightforward. Options at a startup require careful analysis.
// RSU valuation (public company) — relatively straightforward
interface RSUGrant {
shares: number;
currentPrice: number;
vestingSchedule: number[]; // Percentage per year
}
function evaluateRSU(grant: RSUGrant): number[] {
return grant.vestingSchedule.map(
(pct) => (pct / 100) * grant.shares * grant.currentPrice
);
}
// Options valuation (startup) — much more uncertain
interface OptionsGrant {
shares: number;
strikePrice: number;
currentValuation: number;
totalSharesOutstanding: number;
preferenceStack: number; // Liquidation preferences above common stock
}
function evaluateOptions(grant: OptionsGrant): {
ownershipPercentage: number;
currentSpreadPerShare: number;
bestCase: number;
worstCase: number;
} {
const ownership = (grant.shares / grant.totalSharesOutstanding) * 100;
const spread = Math.max(0, grant.currentValuation / grant.totalSharesOutstanding - grant.strikePrice);
return {
ownershipPercentage: ownership,
currentSpreadPerShare: spread,
bestCase: grant.shares * spread, // If company sells at current valuation
worstCase: 0, // Options can be worth zero
};
}
// ❌ Treating startup options at face value
// "I have $400k in options" — at WHAT valuation? After preferences?
// ✅ Discount startup equity heavily
// Rule of thumb: value startup options at 10-20% of paper value
// Most startups fail, and liquidation preferences dilute common stockWhen to Walk Away
Not every negotiation succeeds. Know your walk-away number before you start. If the final offer does not meet your minimum, declining is the correct move — and it is not burning a bridge.
interface DecisionFramework {
minimumAcceptableTC: number;
idealTC: number;
nonCompFactors: NonCompFactor[];
alternativeOptions: string[];
}
interface NonCompFactor {
factor: string;
weight: 'high' | 'medium' | 'low';
assessment: string;
}
const decision: DecisionFramework = {
minimumAcceptableTC: 230_000,
idealTC: 260_000,
nonCompFactors: [
{ factor: 'Remote work', weight: 'high', assessment: 'Fully remote offered' },
{ factor: 'Team quality', weight: 'high', assessment: 'Strong — interviewed with team' },
{ factor: 'Growth trajectory', weight: 'medium', assessment: 'Clear promotion path to Staff' },
{ factor: 'Work-life balance', weight: 'high', assessment: 'On-call every 6 weeks, reasonable' },
{ factor: 'Tech stack', weight: 'low', assessment: 'Familiar stack, no learning curve' },
],
alternativeOptions: [
'Competing offer at $250k from Company B',
'Current role with pending promotion cycle',
],
};
// If final offer < minimumAcceptableTC AND non-comp factors don't compensate:
// Politely decline. "I appreciate the time and effort. Unfortunately, the
// compensation package doesn't align with my expectations for this move.
// I'd be happy to revisit if circumstances change."Key Takeaways
- Compare total compensation, not base salary — equity, bonuses, and signing bonuses often outweigh base salary differences
- Research market rates before negotiating — specific data from levels.fyi or competing offers is your strongest leverage
- Never accept immediately — take a day to review, calculate, and prepare your counter
- Name a specific number with justification — vague requests ("I want more") give the recruiter nothing to work with
- If one lever is stuck, try others — equity grants, signing bonuses, review timelines, and benefits are all negotiable
- Know your walk-away number before the conversation starts — it prevents emotional decisions under pressure


