Land the role & grow to tech-lead
The final capstone: a structured job-search plan, the first-90-days playbook, and the map from engineer → senior → tech-lead — turning everything you built into a career. Runnable planning tools.
Learning objectives
- Run a structured job search, not a scattershot one.
- Execute a strong first 90 days.
- Understand the engineer→senior→lead progression.
- Own your growth deliberately.
code/proj-cr-launch/ — run them against your own resume, stories, and offers.1 · A structured job search essential
Applying randomly wastes effort. Run it like a pipeline: target roles, tailor materials (CR1), track applications, prep per-company (CR2–CR5). Volume × quality, measured.
job_tracker.pyfrom collections import Counter
applications = [
{"company": "A", "stage": "offer"},
{"company": "B", "stage": "onsite"},
{"company": "C", "stage": "rejected"},
{"company": "D", "stage": "phone screen"},
{"company": "E", "stage": "applied"},
{"company": "F", "stage": "onsite"},
]
funnel = Counter(a["stage"] for a in applications)
total = len(applications)
print("pipeline:", dict(funnel))
advanced = sum(1 for a in applications if a["stage"] not in ("applied", "rejected"))
print(f"response rate: {round(100*advanced/total)}% ({advanced}/{total} advancing)")
# healthy: keep enough in early stages to backfill as some drop
pipeline: {'offer': 1, 'onsite': 2, 'rejected': 1, 'phone screen': 1, 'applied': 1}
response rate: 83% (5/6 advancing)
A job search works best run like a pipeline: many applications, each moving through stages (applied → phone screen → onsite → offer). This little tracker counts how many of your applications are at each stage and works out your response rate, so you can see the search's health at a glance.
from collections import Counterbrings in a handy tool that tallies how often each value appears in a list.applicationsis a list of small dictionaries, one per company, each recording its currentstage. This is your search represented as data.funnel = Counter(a["stage"] for a in applications)counts how many applications sit in each stage.total = len(applications)is the overall count.advanced = sum(1 for a in applications if a["stage"] not in ("applied", "rejected"))counts the ones that got past the first step and weren't rejected — i.e. still alive. The last print turns that into a percentage response rate.
What the output means: pipeline: {...} shows the count at each stage, and response rate: 83% (5/6 advancing) means 5 of 6 applications moved forward. A low rate is a signal to improve your resume or targeting (CR1); a high rate means keep the volume up.
Try this: Replace the sample applications with your own list and update each stage as things progress. Re-run whenever something changes to watch your real funnel.
2 · The first 90 days intermediate
Land well in a new role
- Weeks 1-2: set up, read the code, get the app running (DF/CD skills), meet the team, ask questions.
- Weeks 3-6: ship small, safe changes (a bug fix, a test) to learn the pipeline and build trust.
- Weeks 7-12: take a real feature end-to-end; understand the domain and the users.
- Throughout: write things down, over-communicate, and find the person who explains the 'why'.
3 · Advanced — engineer → senior advanced
The jump to senior is about scope and autonomy: you own features/systems end to end, make sound tradeoffs without hand-holding, and your code raises the team's bar. It's less "can you code" and more "can you own outcomes."
4 · Professional — the impact ladder professional
| Level | Scope of impact |
|---|---|
| Junior | completes well-defined tasks |
| Mid | owns features with some guidance |
| Senior | owns systems; makes tradeoffs; mentors |
| Staff/Lead | drives cross-team technical direction & multiplies others |
5 · Tech-lead — multiply, don't just build tech-lead
The tech-lead shift is from your output to the team's: you set technical direction, unblock people, uphold quality standards (the DF/TQ/CD/SD tech-lead tiers you learned), mentor, and translate between business and engineering. Your success is measured by what the team ships, not just you.
growth.pydef growth_stage(signals):
"""signals: dict of bool — what you're consistently doing."""
score = sum(signals.values())
if signals.get("multiplies_team") and signals.get("sets_direction"):
return "tech-lead track"
if signals.get("owns_systems") and signals.get("mentors"):
return "senior"
if signals.get("owns_features"):
return "mid"
return "growing toward mid"
me = {
"owns_features": True, "owns_systems": True, "mentors": True,
"sets_direction": False, "multiplies_team": False,
}
print("current stage:", growth_stage(me))
print("next: build 'sets_direction' + 'multiplies_team' -> tech-lead")
current stage: senior
next: build 'sets_direction' + 'multiplies_team' -> tech-lead
This is a self-assessment: you tick off which senior/lead behaviours you're consistently doing, and it tells you roughly what career stage that puts you at — and what to build next to reach the following rung.
signalsis a dictionary of True/False facts about your habits — do you own features, own systems, mentor, set technical direction, multiply the team?- The function checks them from the top down, strongest first: if you both
multiplies_teamandsets_direction, you're on the "tech-lead track"; else if youowns_systemsandmentors, you're "senior"; and so on. The first matching rule wins, so it reports your highest solid level. mefills in the honest current picture — here owning features and systems and mentoring, but not yet setting direction or multiplying the team.- The two print lines report the stage and spell out the exact two signals to develop next to move up.
What the output means: current stage: senior — the True signals matched the "senior" rule but not the tech-lead one. The next line names the gap: build sets_direction and multiplies_team to reach the tech-lead track.
Try this: Set the flags to your own honest yes/no. Whatever two are False just below your current stage are your concrete goals for the next 6–12 months.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Run the job search like a funnel you can measure, not a black box you guess at. The response rate tells you where to intervene — a weak resume, or interviews you're not converting.
Your task: Build job_tracker.py that counts applications by stage and computes a response rate.
Requirements:
- Count applications at each funnel stage (applied → screen → onsite → offer)
- Compute a response rate = anything past 'applied' over the total
- Handle an empty pipeline without dividing by zero
- Return the per-stage counts alongside the rate
- Runs offline over a list of furthest-stage-reached values
💡 Hint: A Counter over the furthest stage each application reached gives the funnel; a low response rate says fix targeting, not interview prep.
Show solution
Treat the search as a measurable pipeline — the response rate tells you where to intervene:
from collections import Counter
STAGES = ["applied", "screen", "onsite", "offer"]
def funnel(applications):
# applications: list of the furthest stage each reached
c = Counter(applications)
counts = {s: c.get(s, 0) for s in STAGES}
total = sum(counts.values())
responded = total - counts["applied"] # anything past 'applied'
return {"counts": counts, "total": total,
"response_rate": round(responded/total, 2) if total else 0.0}
apps = ["applied","applied","screen","onsite","applied","offer"]
print(funnel(apps))
# response_rate ~0.5 -> half your applications got a reply
A low response rate means fix the resume/targeting; strong responses but no offers means fix interview prep. The funnel tells you which, so effort goes where it moves the needle.
Context: A new role needs a plan, not vibes. Staged goals turn 'make a good impression' into concrete milestones — buying trust with small safe wins before taking on risk.
Your task: Build plan_90_days() returning the goal for a given week band.
Requirements:
- Weeks 1–2: setup (env, access, read the codebase, meet the team)
- Weeks 3–6: ship small, safe changes to build trust
- Weeks 7–12: own a feature end-to-end
- Beyond ramp: higher-scope, more ambiguous work
- Runs offline for any week number
💡 Hint: Map week ranges to goals; by week 12 you should own something end-to-end, which is the evidence a review looks for.
Show solution
Staged goals turn "make a good impression" into concrete, checkable milestones:
def plan_90_days(week):
if 1 <= week <= 2:
return "setup: env, access, read the codebase, meet the team"
if 3 <= week <= 6:
return "ship small safe changes: build trust with low-risk PRs"
if 7 <= week <= 12:
return "own a feature end-to-end: design, build, ship, measure"
return "beyond ramp: take on ambiguous, higher-scope work"
for w in (1, 4, 9, 14):
print(f"week {w:>2}: {plan_90_days(w)}")
Early wins are small and safe by design — you're buying trust and context before taking on risk. By week 12 you should own something end-to-end, which is the evidence a review looks for.
Context: Promotion is about scope of ownership, not effort. Effort is invisible; owned scope is not — a feature, then a system, then a direction.
Your task: Build impact_tier(signals) that classifies work as junior/mid/senior/staff-lead from what the person owns.
Requirements:
- Classify from ownership signals, not hours worked
- Owning features → mid
- Owning systems and mentoring → senior
- Setting direction and multiplying the team → staff/tech-lead
- Runs offline over a signals dict
💡 Hint: Check the highest-scope signals first so the tier reflects the largest surface the person demonstrably owns.
Show solution
The ladder is defined by scope of ownership — naming your tier honestly shows you what to reach for next:
def impact_tier(signals):
# signals: booleans about what you demonstrably own
if signals.get("sets_direction") and signals.get("multiplies_team"):
return "staff/tech-lead"
if signals.get("owns_systems") and signals.get("mentors"):
return "senior"
if signals.get("owns_features"):
return "mid"
return "junior"
print(impact_tier({"owns_features": True})) # mid
print(impact_tier({"owns_systems": True, "mentors": True})) # senior
print(impact_tier({"sets_direction": True, "multiplies_team": True})) # staff
Effort is invisible; owned scope is not. Moving up means owning larger, more ambiguous surface area — a feature, then a system, then a direction — and having the artifacts to prove it.
Context: Growth stalls when you don't know what to build next. The useful output isn't your current tier — it's the next two concrete signals to develop, the shortest path up.
Your task: Build growth.py that reads current signals and returns the two nearest unmet signals to develop.
Requirements:
- An ordered ladder of signals so gaps surface in a sensible sequence
- Return the person's current strengths
- Return the next two unmet signals to develop
- Two concrete targets beat a vague 'grow more'
- Runs offline over a signals dict
💡 Hint: Order the ladder (feature ownership and mentoring before system ownership and direction) and take the first two signals not yet met.
Show solution
The useful output isn't your current tier — it's the next two things to work on:
LADDER = ["owns_features", "mentors", "owns_systems",
"sets_direction", "multiplies_team"]
def next_signals(signals, n=2):
unmet = [s for s in LADDER if not signals.get(s)]
return unmet[:n]
def assess(signals):
have = [s for s in LADDER if signals.get(s)]
return {"strengths": have, "develop_next": next_signals(signals)}
print(assess({"owns_features": True, "mentors": True}))
# develop_next: ['owns_systems', 'sets_direction']
Ordering the ladder means the gaps come out in a sensible sequence: master feature ownership and mentoring before reaching for system ownership and direction-setting. Two concrete targets beat a vague "grow more".
Context: Interviews score you on dimensions, and averages hide the gap that sinks you. Scoring yourself against the same rubric surfaces the one dimension to fix before applying widely.
Your task: Build a rubric scorer over portfolio, resume evidence, search execution, interview readiness, and first-90-days plan, flagging any dimension below bar.
Requirements:
- Score all five dimensions
- Flag every dimension below a bar (e.g. 3 of 5)
- Report the average and a ready/not-ready verdict
- Name the specific below-bar dimensions, not just the average
- Runs offline over a scores dict
💡 Hint: Return the below-bar map, not just the mean — a strong portfolio can't rescue a search you're not running consistently.
Show solution
Self-scoring against the same rubric interviewers use surfaces your weakest dimension before they do:
DIMENSIONS = ["portfolio", "resume_evidence", "search_execution",
"interview_readiness", "first_90_plan"]
BAR = 3 # out of 5
def score_readiness(scores):
weak = {d: s for d, s in scores.items() if s < BAR}
avg = sum(scores.values()) / len(scores)
return {"avg": round(avg, 1), "below_bar": weak,
"ready": len(weak) == 0}
s = {"portfolio": 4, "resume_evidence": 3, "search_execution": 2,
"interview_readiness": 4, "first_90_plan": 3}
print(score_readiness(s))
# below_bar: {'search_execution': 2} -> fix this before applying widely
Averages hide the gap that sinks you; the below_bar map names the one dimension to fix first. A strong portfolio can't rescue a search you're not running consistently.
Context: An offer is more than base salary. A weighted comparator stops the biggest base number from dominating a decision that growth and risk should shape.
Your task: Build an offer comparator scoring comp, growth, and risk so you decide on total value, not just the headline number.
Requirements:
- Comp combines base, equity, and bonus (normalized)
- Growth captures scope and mentorship
- Risk penalizes short runway and unclear role
- A weighted score combines all three; weights are tunable to your priorities
- Compare two contrasting offers offline (e.g. startup vs big-co)
💡 Hint: Normalize comp to a comparable scale and subtract a risk penalty; let the score check your gut, not replace it — tune the weights to what you value.
Show solution
A weighted comparator stops the biggest base number from dominating a decision it shouldn't:
def offer_value(o, weights=None):
w = weights or {"comp": 0.5, "growth": 0.3, "risk": 0.2}
comp = (o["base"] + o["equity_annual"] + o["bonus"]) / 100_000 # normalize
growth = o["scope"] * 0.5 + o["mentorship"] * 0.5 # 0-5 each
risk_penalty = (5 - o["runway_years"]) + (5 - o["role_clarity"])
score = w["comp"]*comp + w["growth"]*growth - w["risk"]*risk_penalty
return round(score, 2)
startup = {"base":150_000,"equity_annual":60_000,"bonus":0,"scope":5,
"mentorship":2,"runway_years":2,"role_clarity":3}
bigco = {"base":200_000,"equity_annual":40_000,"bonus":30_000,"scope":3,
"mentorship":4,"runway_years":5,"role_clarity":5}
print("startup:", offer_value(startup), " bigco:", offer_value(bigco))
Comp is half the story: growth (will you own real scope, get mentored?) and risk (runway, role clarity) can flip the decision. Tune the weights to your own priorities and let the score check your gut, not replace it.
✓ Checkpoint — you can move on when you can…
- Run a structured, measured job search.
- Execute a strong first 90 days.
- Explain the engineer→senior→lead progression.
- Assess your stage and plan the next rung deliberately.
| Dimension | Meets the bar | Above the bar (staff) |
|---|---|---|
| Portfolio quality | You have 1–2 shipped, runnable projects with a README that states the problem, the approach, and how it was verified. | Projects are deployed/live with evidence (metrics, before/after, tests) that a stranger can evaluate in five minutes without you present. |
| Resume evidence | Bullets are outcome-oriented (impact + number), not task lists; each claim maps to something you can demo or discuss. | Every bullet is defensible under drill-down: you can explain the tradeoff, the alternative, and what you'd do differently. |
| Search execution | The search is run as a tracked pipeline (targets, applications, stages) with a measured response rate, not random applying. | You keep enough volume in early stages to backfill drop-off, tailor materials per target, and iterate the funnel when the rate is low. |
| Interview readiness | You have STAR stories prepared (CR2) and can pass the coding/system-design bars (CR3/CR4) for your target level. | Stories are calibrated to the level you're targeting, and you can turn any resume line into a 2-minute structured answer on demand. |
| First-90-days plan | You have a concrete ramp: set up + read code (weeks 1–2), ship small safe changes (3–6), own a feature (7–12). | The plan names how you'll earn trust with a well-tested, cleanly-deployed early win and how you'll find the person who explains the 'why'. |
| Growth self-awareness | You can place yourself honestly on the impact ladder (junior→staff) and name the two signals to build next. | You have a deliberate 6–12 month plan to develop the next rung's signals (owns systems, mentors, sets direction, multiplies the team). |
Score each dimension 0 (missing), 1 (meets bar), or 2 (above bar). 0–4: keep building. 5–8: a solid, defensible submission. 9–12: staff-level — you could hand this to a reviewer and defend every call. Any dimension at 0 blocks shipping regardless of the total.
Knowledge check check yourself
Why does the project frame the job search as a measured pipeline (tracking stages and response rate) instead of applying widely?
Show answer
According to the impact ladder, what actually distinguishes the tech-lead shift from strong senior work?
Show answer
sets_direction + multiplies_team, not just owning systems.