DEV Community

張旭豐
張旭豐

Posted on

A 30-Second Pricing Decision Tree for Freelance Devs

TOTAL_LEN: 8446

A 30-Second Pricing Decision Tree for Freelance Devs

You have a project inquiry. You don't know what to charge. You don't know whether to go fixed price or hourly.

Most freelancers freeze here. Then they lowball themselves.

This decision tree exists to end that freeze. Five questions. Thirty seconds. One pricing strategy you can use right now.


The Tree

Q1: Is the client's goal clear?

  • No → Go to Q2
  • Yes → Go to Q3

Q2: Is there a deadline pressure?

  • No → Sell paid discovery first ($150-300/hr, 2-4hr minimum)
  • Yes → Quote a premium fixed price (1.5-2x your normal rate for rush premium)

Q3: Do they have a budget range stated?

  • No → Anchor with 3 options (good/better/best)
  • Yes → Go to Q4

Q4: Is the scope likely to expand?

  • Yes → Charge hourly with a not-to-exceed cap
  • No → Quote fixed price (but add a revision clause)

Q5: Are there competitors also bidding?

  • Yes → Qualify harder first — cheapest loses. Focus on timeline, process, track record.
  • No → You have leverage. Use it.

The 5 Output Strategies

Strategy When to Use It
Paid discovery first Goal unclear, high complexity
Premium fixed price Rush deadline + unclear scope
Anchor with 3 options Budget known, goal clear
Hourly with cap Scope will creep, client flexible
Fixed price + revision clause Scope stable, want certainty

A Hypothetical Before/After Case

Hypothetical scenario: A client asks for a "simple" WordPress plugin customization. You quote $500 fixed.

What the decision tree says: Q1 (goal clear?) → No. Q2 (deadline?) → 2 weeks. Q3 (budget?) → Not stated. Q4 (scope expand?) → Likely yes.

What you should do: Sell paid discovery first. Scope properly. Then quote fixed.

What often actually happens: You quote $500. Scope triples. You spend 22 hours on a $500 project. Effective rate: $22/hour.

The recovery path: When the client asks for phase 2, you use the anchor-with-3-options approach. Target quote: $800 (basic) / $1,400 (full) / $2,100 (full + ongoing). A well-prepared client picks $1,400. Effective rate on phase 2: $100/hour.

Lesson: The first quote is rarely the real decision point. Phase 2 is where you recover.

Pricing Readiness Checklist

Before you send any quote, run through this:

  • [ ] I know exactly what the client is asking for (goal is clear)
  • [ ] I've identified the top 3 ways this scope could expand
  • [ ] I've priced in a buffer for at least 2 of those expansions
  • [ ] The client has told me their budget range (or I've asked)
  • [ ] I know whether they're talking to other developers
  • [ ] I have a clear deliverable list (what does "done" mean?)
  • [ ] I know my walk-away price (lowest I'll go and still be happy)
  • [ ] My quote includes a revision clause or cap
  • [ ] I've specified payment terms (50% upfront is normal)

If you checked fewer than 6 boxes: use the decision tree above to figure out your next move before you quote.

The Copy-Paste Quote

Use this when the client says "give me a quote":

"Based on the uncertainty here, I'd suggest starting with a paid discovery session before I give a fixed project quote. That way we both know what we're building before we price it."

This sentence alone has saved me from three lowball situations this year.


What Each Output Looks Like in Practice

Paid discovery first:
"I'm happy to scope this out properly. I charge $200/hr for discovery — typically 2-4 hours for a project like this. That gives us a solid spec and a fixed quote afterward."

Premium fixed price:
"Because of the timeline, my rush rate is 1.8x my standard. For this scope, that puts us at $X. Is that workable?"

Anchor with 3 options:
"Here are three ways I could approach this:

  • Good: [basic scope] at $[price]
  • Better: [full scope] at $[price]
  • Best: [full scope + ongoing support] at $[price]"

Hourly with cap:
"I'll work hourly on this, with a not-to-exceed of $[cap]. That protects you from runaway bills and keeps me motivated to stay efficient."

Fixed price + revision clause:
"My fixed price for this is $[X]. That includes [Y revisions]. Additional revisions beyond that are billed at $150/hr."


Free Tool: Pricing Sanity Checker

Copy the HTML below into a file named pricing-checker.html and open it in your browser. Five inputs, instant price range — no server needed.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pricing Sanity Checker</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  body{font-family:system-ui,sans-serif;background:#0d1117;color:#e6edf3;min-height:100vh;padding:2rem}
  .container{max-width:640px;margin:0 auto}
  h1{font-size:1.4rem;color:#58a6ff;margin-bottom:0.5rem}
  .subtitle{color:#8b949e;font-size:0.9rem;margin-bottom:1.5rem}
  .card{background:#161b22;border:1px solid #30363d;border-radius:8px;padding:1.25rem;margin-bottom:1rem}
  label{display:block;font-size:0.8rem;color:#8b949e;margin-bottom:0.3rem}
  select,input{width:100%;padding:0.5rem;background:#0d1117;border:1px solid #30363d;border-radius:4px;color:#e6edf3;font-size:0.95rem;margin-bottom:0.8rem}
  select:focus,input:focus{outline:none;border-color:#58a6ff}
  .result{background:#1c2128;border:1px solid #30363d;border-radius:8px;padding:1.25rem;margin-top:1rem;display:none}
  .result h2{font-size:0.8rem;color:#8b949e;text-transform:uppercase;letter-spacing:0.05em;margin-bottom:0.75rem}
  .price{font-size:1.8rem;font-weight:700;color:#58a6ff;margin-bottom:0.5rem}
  .signal{margin-top:0.75rem;padding:0.6rem;border-radius:4px;font-size:0.85rem}
  .signal.warn{background:#3d1f1f;color:#f85149}
  .signal.ok{background:#1f3d1f;color:#3fb950}
  .cta{text-align:center;margin-top:1.5rem;padding-top:1rem;border-top:1px solid #30363d;font-size:0.8rem;color:#6e7681}
  .cta a{color:#58a6ff}
</style>
</head>
<body>
<div class="container">
  <h1>Pricing Sanity Checker</h1>
  <p class="subtitle">5 inputs, instant price range for freelance projects</p>
  <div class="card">
    <label>Project type</label>
    <select id="type">
      <option value="web">Web dev (frontend/backend/full-stack)</option>
      <option value="mobile">Mobile app</option>
      <option value="api">API / backend</option>
      <option value="design">UI/UX design</option>
      <option value="fix">Bug fix / patch</option>
      <option value="consult">Consulting / review</option>
    </select>
    <label>Estimated hours</label>
    <input type="number" id="hours" placeholder="e.g. 40" min="1">
    <label>Your hourly rate (USD)</label>
    <input type="number" id="rate" placeholder="e.g. 75" min="1">
    <label>Complexity</label>
    <select id="complexity">
      <option value="1.0">Simple</option>
      <option value="1.35">Medium — some integrations</option>
      <option value="1.75">Complex — auth, real-time</option>
      <option value="2.2">Enterprise — compliance, scaling</option>
    </select>
    <label>Client type</label>
    <select id="client">
      <option value="0.9">Startup / early-stage</option>
      <option value="1.0">Small business</option>
      <option value="1.1">Agency</option>
      <option value="1.25">Enterprise / corporate</option>
    </select>
  </div>
  <div class="result" id="result">
    <h2>Sanity Check</h2>
    <div class="price" id="range"></div>
    <div class="signal" id="signal"></div>
  </div>
</div>
<div class="container">
  <div class="cta">
    If this helped, you can buy me a coffee:<br>
    <a href="https://paypal.me/cheapuno" target="_blank">paypal.me/cheapuno</a>
  </div>
</div>
<script>
const market={consult:150,api:125,web:100,mobile:120,design:110,fix:90};
function calc(){
  const h=+document.getElementById('hours').value,r=+document.getElementById('rate').value,c=+document.getElementById('complexity').value,cl=+document.getElementById('client').value,t=document.getElementById('type').value;
  if(!h||!r)return;
  const m=market[t],low=Math.round(h*m*c*cl*0.8),high=Math.round(h*m*c*cl*1.2);
  document.getElementById('range').textContent='$'+low.toLocaleString()+' – $'+high.toLocaleString();
  const adj=Math.round(h*r*c*cl*(t==='consult'?1.4:t==='api'?1.2:1));
  const sig=document.getElementById('signal');
  const ratio=adj/(h*m*c*cl);
  if(ratio<0.85){sig.className='signal warn';sig.textContent='⚠️ You may be undercharging. Clients can perceive low price as low quality.'}
  else if(ratio<=1.15){sig.className='signal ok';sig.textContent='✅ Your rate is in the fair range.'}
  else{sig.className='signal warn';sig.textContent='📈 High rate — make sure client has budget and you have a clear scope document.'}
  document.getElementById('result').style.display='block';
}
['hours','rate','complexity','client','type'].forEach(id=>document.getElementById(id).addEventListener('input',calc));
</script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Save that as pricing-checker.html and open it in any browser. No account, no server, no tracking.


FAQ

Q: What if they push back on paid discovery?
Say: "Discovery is how I make sure we're both confident about the final price. I've had clients save 40% on project cost because discovery caught scope gaps early."

Q: What if I pick the wrong strategy?
You won't know until you try. That's fine. The tree is a starting point, not a guarantee.

Q: What if the client won't pay for discovery?
Then they don't have a serious project yet. Move on.


No more freezing. No more lowballing. Just a decision.



Need Help Making Your Next Pricing Decision?

If you're stuck on whether to take a project or how to respond to a lowball offer, I offer:

$10 Quick Decision Review

  • I'll review your specific situation in 24 hours
  • Give you a clear recommendation based on the framework above
  • Payment: https://paypal.me/cheapuno

No pitch. No follow-up emails. Just a direct answer.

Top comments (0)