Tech Lead NotesRohit’s interview study guide
Chapter 01

The Lead Engineer Role

What a senior or lead engineer is actually responsible for, how to own a workstream end to end, a plan for the first 90 days on a new team or contract, what to keep building yourself and what to hand over, and how to balance delivery, quality and people when they pull in different directions.

5 topics
Parts marked Advanced are extra depth. Skip them on a first read or a quick revision.

#What a senior or lead engineer is expected to do

Added

A senior engineer is trusted to deliver hard things well on their own. A lead engineer is trusted to make a team deliver hard things well, and is still an engineer while doing it. The job has four parts, and interviews for a lead role probe all four: technical direction (what we build and how: architecture, standards, the big technical decisions), delivery (that it actually ships: planning, sequencing, risks, unblocking), quality (that what ships is good and stays good: reviews, testing, operability, technical debt) and people (that the team grows: mentoring, delegation, feedback, a healthy way of working). A lead who only does the first is an architect; one who only does the second is a project manager; the role is the overlap.

The mental model: you're measured by the team's output, not your own. As a senior engineer, a great week is one where you shipped a hard feature. As a lead, a great week can be one where you wrote little code, but three engineers shipped because you unblocked the firmware question on Monday, caught the migration problem in review on Tuesday, and spent Thursday pairing with the newest engineer so they can own the next feature without you. Your leverage is in decisions, reviews, unblocking and teaching; your own commits are one input among many.

Will Larson's Staff Engineer (2021, staffeng.com) describes the Tech Lead as one of four common shapes of senior technical role, alongside Architect, Solver and Right Hand: the tech lead guides the approach and execution of one team (or a small cluster), partners closely with a single manager (sometimes two or three), scopes complex work, coordinates the team, unblocks it, keeps the essential cross-team relationships working and defines the team's technical vision, and by default delegates the team's most complex projects rather than building them personally. Titles vary a lot between companies ("Lead Engineer", "Tech Lead", "Senior Engineer, Team Lead", "Staff Engineer"), so in an interview it's worth asking what the role owns rather than assuming from the title.

the team ships your code is one input Technical direction architecture, standards, decisions and their records "how we build it" Delivery scope, plan, sequence, risks, unblocking, status "it actually ships" Quality reviews, tests, CI gates, operability, technical debt "it stays good" People mentoring, delegation, feedback, how the team works "the team grows"
Four areas, one measure: what the team delivers. The lead's own code sits inside the job, not instead of it.

The roles a lead works next to are easy to blur, and interviewers sometimes ask you to separate them:

Tech lead (hands-on)Engineering managerArchitectProduct manager / owner
Ownshow the team builds: design, standards, technical risksthe people: hiring, performance, careers, team healthcross-team technical direction in a domainwhat gets built and why: priorities, outcomes
Writes codeyes, typically a substantial part of the weekrarelysome, often prototypesno
Decidestechnical approach, review standards, task breakdownstaffing, promotions, processplatform and interface choices across teamsscope and priority
Success looks likethe team ships reliably and keeps a healthy codebasepeople grow and stay; the team is effectivesystems fit together and evolveusers and the business get the outcome

These boundaries move by company. In many US companies leading offshore or contract teams, the "lead engineer" also does part of the manager's job day to day (one-to-ones on technical growth, onboarding, feedback) while a manager elsewhere owns hiring and performance. Clarify it in your first week, in writing, so nobody assumes you own something you don't, or the reverse.

A short role charter, agreed with your manager in the first couple of weeks, is the practical way to clarify it:

role-charter.md
# Lead engineer: role charter (draft for discussion with <manager>, <date>)

## Scope
- Team: 6 engineers (4 in Bengaluru, 2 in the US), web UI + REST API for <product area>.
- I own: technical approach and design reviews, code review standards, sprint technical planning,
  risks and status for the workstream, onboarding and technical mentoring.
- I don't own: hiring decisions, performance ratings, the product roadmap (I contribute to all three).

## Decisions
- I decide: design within the team's area, review and testing standards, task breakdown.
- We decide together: cross-team API contracts (with <other team>), release dates (with <PM>).
- I escalate to <manager>: scope changes over ~1 week of effort, staffing gaps, client conflicts.

## How I spend a typical week (target)
- ~30% hands-on: design spikes, the riskiest or most cross-cutting pieces, pairing.
- ~30% reviews and unblocking.
- ~20% planning, status, client and stakeholder conversations.
- ~20% people: one-to-ones, mentoring, onboarding.

## How we'll know it's working (after 90 days)
- The team ships to plan more often than not, and says when it won't, early.
- Review turnaround under one working day; no PR waits on me alone.
- At least two people can change each critical area of the codebase.
What’s happening
  1. Scope lists what you own and, just as important, what you don't. "I don't own performance ratings" prevents the awkward discovery, three months in, that the team thought you did, or that your manager expected you to.
  2. Decisions splits them three ways: yours, shared, and escalated. This is the part that saves the most conflict later: when the client asks for a change, everyone already knows whether you can say yes, and when you must bring in your manager.
  3. The week is a target, not a timesheet. Writing "~30% hands-on" protects coding time from meetings and, just as often, protects leading time from your own wish to just write the code. The percentages are a starting point to discuss; they depend on team size and how much is still unknown.
  4. How we'll know it's working turns the role into checkable outcomes: delivery honesty, review turnaround, and knowledge spread across people. None of them is "lines of code I wrote".
  5. Everything in angle brackets is a placeholder. The point is the structure: one page, written early, agreed with your manager, revisited after 90 days.
AdvancedWhat interviewers listen for

For a lead role, interviewers listen for signals in each of the four areas, and for the word "we":

  • Technical direction: you can explain a decision you'd make, the alternatives, and the trade-off, and you write decisions down (see Architecture decision records).
  • Delivery: you talk about scope, sequencing, risks and dates, not just code; you surface bad news early.
  • Quality: you have opinions about reviews, tests and CI that are specific, and you know when "good enough" is right.
  • People: you can describe how you'd onboard, delegate, give feedback and grow someone, without micromanaging.
  • Ownership language: "I made sure we…", "I decided…, because…", "the team shipped…", and honesty about what went wrong.
AdvancedSame situation, senior versus lead

The clearest way to see the difference is to take one event and compare the responses:

SituationA strong senior engineerA lead engineer, additionally
A production bug in device statusFinds and fixes it, adds a testMakes sure the client is told, someone is fixing it (not necessarily the lead), a blameless review happens, and the class of bug gets a check
A vague feature requestAsks clarifying questions about their partTurns it into scope, an Out list, milestones and risks; agrees it with the PM and the client
A slow, flaky CI pipelineRetries, maybe fixes one flaky testTreats it as a team delivery problem: measures it, plans the fix, protects the time for it
A junior engineer's PR with a poor designLeaves detailed commentsComments, and asks why the design wasn't discussed earlier; sets up a quick design chat for next time
A new engineer joinsAnswers their questionsPlans their first month: a buddy, a starter task, a first real feature, check-ins
AdvancedWhere a lead's week goes

The role charter says "~30% hands-on". Whether that's possible depends on your calendar, especially when the team is ten and a half hours ahead (New York in winter; nine and a half while US daylight saving time is on, from March to early November) and every meeting with them has to fit into your early morning. A quick audit of a typical week:

calendar-audit.mjs
// A lead's calendar in New York (08:00–17:00), working with a team in Bengaluru. How much time is left
// for hands-on work, and is there any block long enough to do it? Times are local "HH:MM".
const DAY_START = "08:00", DAY_END = "17:00", FOCUS_MIN = 120;
const week = {
  Mon: [["08:00", "08:15", "standup (India evening)"], ["08:15", "09:00", "1:1 Asha"], ["10:00", "11:00", "sprint planning"], ["13:00", "14:00", "design review"]],
  Tue: [["08:00", "08:15", "standup"], ["08:15", "09:00", "1:1 Ravi"], ["11:00", "12:00", "client sync"], ["15:00", "15:30", "PM catch-up"]],
  Wed: [["08:00", "08:15", "standup"], ["08:15", "09:00", "1:1 Meera"], ["10:00", "10:30", "hiring screen"], ["11:00", "11:30", "architecture forum"], ["14:00", "14:30", "1:1 manager"], ["15:30", "16:00", "incident review"]],
  Thu: [["08:00", "08:15", "standup"], ["08:15", "09:00", "1:1 Karthik"], ["11:00", "12:00", "client sync"]],
  Fri: [["08:00", "08:15", "standup"], ["08:15", "09:00", "refinement"], ["14:00", "15:00", "demo + retro"]],
};
// Every day also has a review block for the India team's PRs and lunch.
for (const meetings of Object.values(week)) meetings.push(["09:00", "09:45", "PR reviews"], ["12:00", "12:30", "lunch"]);
const min = (hhmm) => { const [h, m] = hhmm.split(":").map(Number); return h * 60 + m; };
const hours = (m) => `${Math.floor(m / 60)}h${String(m % 60).padStart(2, "0")}`;

let freeTotal = 0, focusTotal = 0;
console.log("day    booked  free   longest free block   focus blocks ≥ 2h");
for (const [day, meetings] of Object.entries(week)) {
  const sorted = meetings.map(([a, b]) => [min(a), min(b)]).sort((x, y) => x[0] - y[0]);
  const gaps = [];
  let t = min(DAY_START);
  for (const [a, b] of sorted) { if (a > t) gaps.push(a - t); t = Math.max(t, b); }
  if (min(DAY_END) > t) gaps.push(min(DAY_END) - t);
  const free = gaps.reduce((s, g) => s + g, 0);
  const busy = min(DAY_END) - min(DAY_START) - free;
  const focus = gaps.filter((g) => g >= FOCUS_MIN).reduce((s, g) => s + g, 0);
  freeTotal += free; focusTotal += focus;
  console.log(`${day}  ${hours(busy).padStart(8)}  ${hours(free)}  ${hours(Math.max(...gaps)).padStart(19)}   ${focus ? hours(focus) : "none"}`);
}
console.log(`\nweek: ${hours(freeTotal)} unbooked, of which ${hours(focusTotal)} in blocks of 2 hours or more.`);
console.log("Reviews and unblocking come out of the free time first; hands-on work needs the long blocks.");
Result
day    booked  free   longest free block   focus blocks ≥ 2h
Mon      4h15  4h45                 3h00   3h00
Tue      3h45  5h15                 2h30   2h30
Wed      4h15  4h45                 1h30   none
Thu      3h15  5h45                 4h30   4h30
Fri      3h15  5h45                 2h15   4h15

week: 26h15 unbooked, of which 14h15 in blocks of 2 hours or more.
Reviews and unblocking come out of the free time first; hands-on work needs the long blocks.
What’s happening
  1. The calendar is a plausible week for a lead in New York with an India team: a standup and a one-to-one every morning in the overlap (08:00 New York is 18:30 in Bengaluru in winter, 17:30 in summer), a 45-minute block for the India team's PRs straight after (so feedback is waiting when they start), planning, client syncs, a design review, a hiring screen and the rest.
  2. For each day the model finds the gaps between bookings in the 08:00–17:00 day. Booked time is the day minus the gaps, so overlapping meetings aren't counted twice.
  3. Wednesday is the warning: 4h45 unbooked, but the longest stretch is 1h30 and there's no block of two hours. Unbooked time in 30-minute fragments is good for reviews and answering questions, and nearly useless for writing a tricky piece of code.
  4. Across the week, 26h15 is unbooked, but only 14h15 is in blocks of two hours or more. If reviews, unblocking and ad-hoc questions take even half the fragments, a 12-hour hands-on budget (as in Leading by example in code) only fits if the long blocks on Monday, Thursday and Friday are protected.
  5. The practical moves: cluster meetings (Wednesday's could move to Tuesday afternoon), keep one or two meeting-free afternoons as a team norm, and choose hands-on work that fits the blocks you have: a spike on Thursday, not a critical-path feature that needs five uninterrupted days.
AdvancedHow expectations grow with level

Companies define levels differently, but the direction of travel is consistent: scope widens, time horizons lengthen and more of the impact comes through other people.

Senior engineerLead / tech leadStaff engineer
Scopea feature or componenta team's whole area of workseveral teams, or a domain across the organisation
Time horizonthis sprint to this quarterthis quarter to nextthe next year or more
Ambiguity handleda well-defined problem with open designa fuzzy goal that needs scoping and sequencinga problem nobody has framed yet
Impact mostly throughtheir own code and reviewsthe team's delivery and growthdirection, standards and other leads
Typical artefactsPRs, design notes for their partsworkstream brief, risk register, ADRs, plansstrategy documents, cross-team architecture, guidelines
AdvancedThe super-IC trap
Lead, manager or product?

A client stakeholder emails the lead: "Can you add CSV export to the device list this sprint? Also, Karthik seems slow lately; is he going to be OK on this team?" Which parts does the lead own, and what's a good reply?

Show answer

The lead owns the technical answer about CSV export (effort, risk, what it would displace) but not the priority call, and not the performance question about Karthik. A good reply handles each part with the right owner.

What’s happening
  1. CSV export: the lead can estimate it and explain the trade-off ("about two days; it would push the groups page to next sprint"), but whether it displaces planned work is a priority decision for the product owner with the client. Answer with the cost and route the choice, don't silently absorb it.
  2. Karthik: performance concerns about a person are for their manager, discussed privately, not over email with a client. The lead can say "thanks, I'll pick that up with the team" and then talk to the manager (and to Karthik, if the lead has that responsibility in the charter) about what the stakeholder is seeing.
  3. The reply might read: "CSV export is about two days of work; we can do it this sprint if we move the groups page to the next one. I've asked <PM> to confirm the priority with you. On the team, thanks for flagging it; I'll follow up internally." Short, honest about cost, and each decision goes to its owner.
  4. This is why the role charter's "decisions" section exists: the lead doesn't have to improvise who decides what each time a stakeholder asks.

#Owning a workstream

Added

A workstream is a piece of work big enough to need its own plan: a feature area over a quarter ("device groups and bulk actions"), a migration ("move the admin UI from AngularJS to Angular"), an integration with a client's system. Owning it means you're the person who can answer, at any moment, five questions: what are we building and why (scope), in what order and by when (roadmap), what could stop us (risks), what have we decided and what's still open (decisions), and who's doing what (people). You don't do all the work; you make sure the answers exist, are current and are written down where others can find them.

The mental model: you're the pilot in command, not the person flying every leg. The crew flies, navigates and talks to the tower; the pilot in command is answerable for the flight, keeps the picture of where everything is, makes the calls that matter and says early when the plan has to change. On a workstream that picture is a handful of short documents kept current: a one-page brief, a risk register, a decision log and a plan. When someone asks "where are we on device groups?", the owner can answer in two minutes, and so can anyone who reads the brief.

The brief is the first thing to write. One page, linked from the ticket board and the team channel:

workstream-brief.md
# Device groups and bulk actions: workstream brief
Owner: <lead> · Updated: 2026-11-02 · Status: on track for the 15 Dec release, two risks escalated

## Why
Site admins manage 50–400 devices per site one at a time. Groups let them reboot, update or
re-configure a whole floor at once. Asked for by <client> (ticket DEV-380).

## Scope
In:  create/rename/delete groups; add devices to groups; bulk reboot and bulk firmware update;
     an audit entry for every bulk action.
Out: scheduled actions, nested groups, groups shared across sites (revisit in Q1).

## Plan (milestones, not tasks)
1. 13 Nov  Firmware bulk-command spike done, API contract agreed with <client>'s firmware team
2. 27 Nov  Groups API and schema in staging behind the device-groups flag
3. 08 Dec  Admin UI complete; load test with 400 simulated devices
4. 15 Dec  Release to the pilot site; general release after two weeks without incidents

## Risks (top 3; full register linked)
- Firmware API docs incomplete → spike on a real device, assumptions confirmed in writing.
- Staging has 3 devices, production sites have 400 → simulated-device load test (no owner yet).
- Diwali (8 Nov): most of the India team on leave that week → plan around it.

## Decisions (log linked)
- 2026-10-28 Bulk actions run as a server-side job, not from the browser (ADR-014).
- Open: do bulk reboots go in waves of 25 or all at once? Needs <client> input by 13 Nov.

## People
API: Asha, Ravi · UI: Meera, Karthik · Firmware contact: <name> at <client> · PM: <name>
What’s happening
  1. Why comes first and is two sentences: the user problem and who asked. When priorities shift (and they will), this is what you argue from.
  2. Scope has an explicit Out list. Most scope creep arrives as "while you're in there, could it also…"; a written Out list turns that into a visible decision instead of a silent addition.
  3. The plan is milestones with dates, each a checkable state ("in staging behind the flag"), not a list of tasks. Tasks live in the tracker; the brief shows the shape. The last milestone includes how you'll release safely (a pilot site, two quiet weeks), which is part of owning delivery.
  4. Risks and decisions are summaries with links to the full register and the decision log. The open decision has a date and an owner of the input ("needs <client> input by 13 Nov"): an open question without a deadline is how projects stall.
  5. Status in the header is one line anyone can read: on track, at risk, or off track, and why. Updating that line every week is the cheapest status report you'll ever write.
AdvancedWeekly risk review as code

A risk register is only useful if it's reviewed. This one is a list in code so the weekly review is the same every time: score each risk, flag the ones that need action, and produce the lines for the status update:

risk-register.mjs
// A workstream's risk register, reviewed every week. Score = likelihood × impact (1–5 each).
const REVIEW_DATE = "2026-11-02";
const risks = [
  { risk: "Client's device firmware API docs are incomplete", likelihood: 4, impact: 4, owner: "Lead",
    mitigation: "Spike against a real device; list assumptions; confirm with client's firmware team", reviewed: "2026-10-30" },
  { risk: "Only Asha knows the device sync code", likelihood: 3, impact: 4, owner: "Lead",
    mitigation: "Pair Ravi on the next two sync tickets; write the sync runbook", reviewed: "2026-10-30" },
  { risk: "Staging has 3 devices; production sites have 400", likelihood: 3, impact: 5, owner: "",
    mitigation: "Load-test the status poller with simulated devices", reviewed: "2026-10-12" },
  { risk: "Diwali (8 Nov): most of the India team on leave", likelihood: 5, impact: 2, owner: "Lead",
    mitigation: "Plan the sprint around it; nothing risky ships that week", reviewed: "2026-10-26" },
  { risk: "SSO library major upgrade pending", likelihood: 2, impact: 3, owner: "Meera",
    mitigation: "Upgrade in its own PR after the release", reviewed: "2026-10-28" },
];

const days = (a, b) => Math.round((Date.parse(b) - Date.parse(a)) / 86_400_000);
const flagged = risks.map((r) => {
  const score = r.likelihood * r.impact;
  const flags = [];
  if (score >= 15) flags.push("ESCALATE");
  if (!r.owner) flags.push("NO OWNER");
  if (days(r.reviewed, REVIEW_DATE) > 14) flags.push(`STALE ${days(r.reviewed, REVIEW_DATE)}d`);
  return { ...r, score, flags };
}).sort((a, b) => b.score - a.score);

console.log(`Risk review ${REVIEW_DATE}`);
for (const r of flagged) {
  console.log(`${String(r.score).padStart(2)}  ${r.risk.padEnd(50)} ${(r.owner || "-").padEnd(6)} ${r.flags.join(", ")}`);
}
const toRaise = flagged.filter((r) => r.flags.length);
console.log(`\nFor the status update: ${toRaise.length} risks need attention, ${flagged.filter((r) => r.flags.includes("ESCALATE")).length} to escalate.`);
Result
Risk review 2026-11-02
16  Client's device firmware API docs are incomplete   Lead   ESCALATE
15  Staging has 3 devices; production sites have 400   -      ESCALATE, NO OWNER, STALE 21d
12  Only Asha knows the device sync code               Lead
10  Diwali (8 Nov): most of the India team on leave    Lead
 6  SSO library major upgrade pending                  Meera

For the status update: 2 risks need attention, 2 to escalate.
What’s happening
  1. Each risk has a likelihood and an impact from 1 to 5, and the score is their product, so the range is 1–25. It's crude on purpose: the value is in talking about each risk every week, not in the precision of the number.
  2. The firmware docs risk scores 4 × 4 = 16 and the staging-devices risk 3 × 5 = 15, so both cross the threshold of 15 and are flagged ESCALATE: they go into this week's status update with what you need from whom.
  3. The staging risk is also flagged NO OWNER and STALE 21d: nobody has looked at it since 12 October. That combination, a high score, no owner, not reviewed, is how a known risk becomes a surprise. The fix is to give it an owner today, not to rescore it.
  4. "Only Asha knows the device sync code" scores 12: not an escalation, but it's in the register with a mitigation (pairing, a runbook) because knowledge concentration is a delivery risk a lead owns (see Leading by example in code).
  5. The last line is the input to the status update: two risks need attention, two to escalate. Writing risks down as data rather than in someone's head is what lets anyone on the team run this review when the lead is away.
AdvancedFinding the dates that matter: the critical path

A plan with dependencies has a critical path: the chain of tasks where any delay moves the end date. Everything else has slack. Knowing which is which tells you where to put your best people, what to check on daily, and which risks matter for the date. It's a two-pass calculation:

critical-path.mjs
// Which tasks decide the date? Earliest start/finish through the dependency graph, then slack per task.
const tasks = [ // id, working days, depends on
  ["spike", 3, []], ["contract", 2, ["spike"]], ["schema", 2, []], ["api", 5, ["schema", "contract"]],
  ["ui", 6, ["schema"]], ["ui-wire", 2, ["ui", "api"]], ["load-test", 3, ["api"]], ["pilot", 1, ["ui-wire", "load-test"]],
];
const byId = new Map(tasks.map(([id, days, deps]) => [id, { id, days, deps }]));

// Forward pass: a task starts when all its dependencies have finished.
const order = [];
const visit = (t, seen = new Set()) => { if (seen.has(t.id)) return; seen.add(t.id); t.deps.forEach((d) => visit(byId.get(d), seen)); if (!order.includes(t)) order.push(t); };
for (const t of byId.values()) visit(t);
for (const t of order) { t.start = Math.max(0, ...t.deps.map((d) => byId.get(d).finish)); t.finish = t.start + t.days; }
const end = Math.max(...order.map((t) => t.finish));

// Backward pass: the latest a task can finish without moving the end date.
for (const t of [...order].reverse()) {
  const next = order.filter((n) => n.deps.includes(t.id));
  t.latestFinish = next.length ? Math.min(...next.map((n) => n.latestFinish - n.days)) : end;
  t.slack = t.latestFinish - t.finish;
}
console.log("task        days  start  finish  slack");
for (const t of order) console.log(`${t.id.padEnd(11)} ${String(t.days).padStart(4)} ${String(t.start).padStart(6)} ${String(t.finish).padStart(7)} ${String(t.slack).padStart(6)}${t.slack === 0 ? "  critical" : ""}`);
console.log(`\nend: day ${end}. Critical path: ${order.filter((t) => t.slack === 0).map((t) => t.id).join(" → ")}`);

// What if the firmware spike takes 2 days longer than planned?
byId.get("spike").days += 2;
for (const t of order) { t.start = Math.max(0, ...t.deps.map((d) => byId.get(d).finish)); t.finish = t.start + t.days; }
console.log(`spike +2 days → end: day ${Math.max(...order.map((t) => t.finish))}`);
Result
task        days  start  finish  slack
spike          3      0       3      0  critical
contract       2      3       5      0  critical
schema         2      0       2      3
api            5      5      10      0  critical
ui             6      2       8      3
ui-wire        2     10      12      1
load-test      3     10      13      0  critical
pilot          1     13      14      0  critical

end: day 14. Critical path: spike → contract → api → load-test → pilot
spike +2 days → end: day 16
What’s happening
  1. Each task has a duration in working days and the tasks it depends on: the groups API needs the schema and the agreed firmware contract, which needs the spike; the UI only needs the schema.
  2. The forward pass visits tasks in dependency order and starts each one when its last dependency finishes: the spike runs days 0–3, the contract 3–5, the API 5–10 (it waited for the contract, not the schema, which finished on day 2). The pilot ends on day 14.
  3. The backward pass works from the end: the latest each task can finish without moving day 14. Slack is latest finish minus earliest finish. The schema has 3 days of slack, the UI 3, the wiring 1.
  4. The tasks with zero slack are the critical path: spike → contract → api → load-test → pilot. The surprise for most teams is that the UI, the biggest single task, isn't on it; the firmware spike and the contract with the client are.
  5. The last line is the risk in one number: if the spike takes two days longer, the end moves from day 14 to day 16, day for day. That's why the firmware risk is the one to escalate, why the lead (or the strongest engineer) takes the spike, and why the contract conversation with the client's firmware team gets booked before the spike is even finished.
AdvancedDecisions: make them, write them down

Owning a workstream means decisions get made at the right time by the right people, and are recorded. A decision log is a dated list; bigger technical decisions become an ADR (see Architecture decision records).

Decision log entry
2026-10-28  Bulk actions run as a server-side job, not from the browser.
  Context:  A bulk reboot of 400 devices from the browser fails if the admin closes the tab.
  Options:  (a) browser loops over devices; (b) server-side job with progress API; (c) firmware batch command.
  Decision: (b) now; (c) later if the firmware supports it (spike by 13 Nov).
  Decided by: <lead>, with Asha and Ravi; <PM> informed. ADR-014.
  Revisit if: the firmware batch command turns out to exist and be reliable.
AdvancedTickets that survive a time difference

Milestones become tickets, and on a team split across a 10.5-hour difference a vague ticket costs a day: the engineer finds the gap at 11:00 in Bengaluru, asks, and gets the answer the next morning. Owning the workstream includes making sure tickets are ready before the sprint starts:

ticket-template.md
## DEV-412: Rename a device group

**Why:** admins rename groups when floors are reorganised; today they must delete and recreate.

**Acceptance criteria**
- PATCH /v1/groups/{groupId} with { name } renames the group; returns 200 with the group.
- Names are 1–80 characters, unique per site (case-insensitive); otherwise 422 with a problem body.
- Only users with device-admin on the group's site can rename (403 otherwise).
- The rename appears in the audit log with the user, old name and new name.

**Out of scope:** renaming from the bulk-action screen (DEV-415).

**Notes for the implementer**
- Follow the pattern in groups-api.js (createGroup) for validation and errors.
- The UI calls this from GroupHeader.jsx; Meera owns that part (DEV-413).

**Questions answered so far:** "Do renames notify devices?" No, devices don't store group names.
What’s happening
  1. Why in one line, so the implementer can make sensible small decisions without asking.
  2. Acceptance criteria are testable statements, including the error cases (422 for a duplicate, 403 for the wrong role) and the audit entry. Each one becomes a test, and the reviewer can check the PR against the list.
  3. Out of scope names the nearby thing that isn't included, with its own ticket, so nobody builds it "while they're in there".
  4. Notes point at the existing pattern to copy and at who owns the neighbouring work, which is how a lead's first-instance code becomes the template (see Leading by example in code).
  5. Questions answered so far keeps the answers in the ticket instead of a chat thread. The next person with the same question finds it at 10:00 in Bengaluru, not the next day.
AdvancedA weekly rhythm for the owner
  • Monday: update the brief's status line and the risk register; post the status update (it takes 15 minutes when the register is current).
  • Daily: standup in the overlap, reviews first thing, and unblock anything waiting on you before you start your own work.
  • Mid-week: check the critical path: is anything with zero slack late?
  • Before each sprint: make sure the next sprint's tickets meet the template above; resolve open questions with the PM and the client.
  • Every sprint: a demo to the client and a short retrospective; feed what you learn back into the plan.
AdvancedBeing the go-to person without being the bottleneck

Owning a workstream makes you the person everyone asks. That's the job, but it shouldn't make you the only path to an answer.

  • Answer in public. Answer questions in the team channel or the ticket, not in private messages, so the answer is searchable and the next person doesn't have to ask.
  • Turn repeated questions into documents. Third time someone asks how to run the device simulator, write the README section and link it.
  • Name a second. For each area, someone besides you can make the call when you're asleep. On a US–India team you're asleep for most of their day.
  • Delegate decisions with a frame, not just tasks: "you decide the table design; it needs to support 400 devices per group and deletes must be soft".
  • Keep an "ask me" window (office hours in the overlap) so people batch questions instead of being blocked one at a time.
AdvancedRisks without owners or dates
What's missing from this status update?

"Device groups: going well. API mostly done. UI in progress. Some concerns about firmware. Will update next week."

Show answer

Nearly everything a reader needs: a status against the plan, dates, the specific risk, and what you need from whom. A better version:

Output
Device groups: AT RISK for 15 Dec.
Done: groups API in staging behind the flag (milestone 2, on time).
Next: admin UI by 8 Dec (Meera, Karthik).
Risk: the firmware team hasn't confirmed the bulk-command API; without it, bulk firmware update
slips to January. Need: a 30-minute call with <client>'s firmware lead by Fri 13 Nov.
Decision needed from <PM>: ship bulk reboot alone on 15 Dec if firmware update slips? (I recommend yes.)
What’s happening
  1. "Going well" isn't a status. Use a fixed vocabulary (on track, at risk, off track) against a named milestone, so readers can compare weeks.
  2. "Mostly done" and "in progress" don't say how far. Reference milestones: "milestone 2 done, on time", "UI by 8 Dec".
  3. "Some concerns about firmware" hides the most important line. Name the risk, its consequence (bulk update slips to January), and exactly what would remove it (a call by a date).
  4. End with the decision you need and your recommendation. Stakeholders can act on "I recommend we ship bulk reboot alone"; they can't act on "concerns".

#The first 30, 60 and 90 days on a new team or contract

Added

Joining a team as its lead, especially as a contractor or through a consultancy, you have less credibility than you'll have later and more attention than you'll ever have again. Everyone is watching what you change first. The classic advice comes from Michael Watkins' The First 90 Days (Harvard Business Review Press, 2003; updated 2013): diagnose the situation before acting, secure some early wins, and agree expectations explicitly with your boss. His STARS model names five situations you might walk into (start-up, turnaround, accelerated growth, realignment, sustaining success), and the right first moves differ: a turnaround needs decisive changes soon; sustaining a successful team mostly needs you not to break what works.

The mental model: learn, then contribute, then lead. In the first month your job is to understand the system, the people and the expectations well enough to make good decisions, and to be useful in small ways that earn trust. In the second month you take ownership of real work and fix one or two things everyone agrees are painful. By the third month you're setting direction: a plan, standards, how the team works. Skipping the first phase is the most common mistake: a new lead who changes the branching strategy in week two, before understanding why it's like that, spends months recovering the team's trust.

day 1 day 30 day 60 day 90 Learn one-to-one with everyone run it, deploy it, use it read code, history, incidents agree expectations in writing small fixes, review a lot earn trust, change little Contribute own a real piece of work fix one pain everyone feels first risk register regular client touchpoint propose, don't impose early wins, shared diagnosis Lead roadmap and workstream brief standards and review norms delegation and growth plans working rhythm across zones review the 90-day plan direction the team agreed to
Learn, contribute, lead. The boundaries are soft; the order isn't.

Write the plan down and share it with your manager (and, on a contract, the client's engineering contact) in week one. It's a commitment and a conversation starter:

90-day-plan.md
# 90-day plan: <name>, lead engineer, <team> (shared with <manager>, <client contact>)

## Days 1–30: learn
- One-to-ones with each engineer (Bengaluru and US), the PM, the QA lead and <client>'s
  engineering contact. Same questions for everyone (below), notes kept private.
- Get the system running locally; deploy to staging myself; use the product as an admin would.
- Read: the architecture docs (whatever exists), the last 3 months of incidents, the open bugs,
  the CI pipeline, and the git history of the busiest files.
- Review PRs every day; ship 2–3 small fixes myself to learn the path to production.
- Agree with <manager>: what I own, what I escalate, how success is judged at day 90.

## Days 31–60: contribute
- Own one real feature end to end (likely <feature>), pairing with <engineer>.
- Fix one pain point the team named in the one-to-ones (candidate: slow, flaky CI).
- Start the workstream brief and risk register; weekly status to <manager> and <client>.
- Write down the review and branching norms we already follow; propose one change at most.

## Days 61–90: lead
- Roadmap for next quarter with <PM>; technical plan and risks with the team.
- Agreed standards: review turnaround, PR size, definition of done.
- Growth conversations with each engineer: what they want to own next.
- Retrospective on this plan with <manager>: what worked, what changes.

## Questions for every one-to-one
1. What's working well that I should be careful not to break?
2. What slows you down most?
3. What do you wish the previous lead (or the client) had done differently?
4. What's the part of the system you'd least like to touch, and why?
5. What would you like to own or learn in the next six months?
What’s happening
  1. The plan is shared, in week one, with the people who will judge you. That turns "how's the new lead doing?" from a vague impression into a conversation about a plan they've seen.
  2. Days 1–30 are almost all inputs: conversations, running the system, reading code and incidents. The small fixes and daily reviews aren't for output; they're the fastest way to learn the codebase, the CI and the release path, and to show you're hands-on.
  3. The same five questions for everyone let you compare answers. When four of six people name the flaky CI as their biggest slowdown, you've found your day-31 win, and it's the team's diagnosis, not yours. Question 1 protects you from "fixing" something that works.
  4. Days 31–60 add one owned feature and one fix to a shared pain, and at most one proposed change to how the team works. Early wins matter, but they should be ones the team asked for.
  5. Days 61–90 are where direction appears: a roadmap, standards and growth plans, built from what you learned. The plan ends with a retrospective on itself, which models the behaviour you'll want from the team.
AdvancedTurning one-to-ones into a shared diagnosis

Asking everyone the same questions pays off when you put the answers side by side. Tag each answer with a theme as you write your notes (keep the notes themselves private), then count:

one-to-one-themes.mjs
// After the first round of one-to-ones: what did people say, and what do they agree on?
// Each answer to "What slows you down most?" was tagged with one or more themes while writing notes.
const notes = [
  { who: "Asha (Bengaluru)", slows: ["ci-slow", "ci-flaky"], protect: "daily standup is short" },
  { who: "Ravi (Bengaluru)", slows: ["ci-flaky", "unclear-tickets"], protect: "pairing with Asha" },
  { who: "Meera (Bengaluru)", slows: ["review-wait"], protect: "freedom to pick UI libraries" },
  { who: "Karthik (Bengaluru)", slows: ["ci-slow", "review-wait", "no-staging-devices"], protect: "-" },
  { who: "Dana (US)", slows: ["unclear-tickets"], protect: "client demos every sprint" },
  { who: "Sam (US)", slows: ["ci-flaky", "review-wait"], protect: "daily standup is short" },
];
const LABELS = { "ci-slow": "CI takes ~40 min", "ci-flaky": "CI fails randomly", "review-wait": "waiting for review",
  "unclear-tickets": "tickets missing detail", "no-staging-devices": "too few test devices" };

const count = new Map();
for (const n of notes) for (const t of n.slows) count.set(t, [...(count.get(t) ?? []), n.who.split(" ")[0]]);
console.log("What slows you down most?");
for (const [theme, people] of [...count].sort((a, b) => b[1].length - a[1].length || a[0].localeCompare(b[0]))) {
  console.log(`  ${String(people.length).padStart(1)}/${notes.length}  ${LABELS[theme].padEnd(24)} ${people.join(", ")}`);
}
const ci = new Set(notes.filter((n) => n.slows.some((t) => t.startsWith("ci-"))).map((n) => n.who));
console.log(`\nAnything about CI: ${ci.size}/${notes.length} people, in both locations.`);

const keep = new Map();
for (const n of notes) if (n.protect !== "-") keep.set(n.protect, (keep.get(n.protect) ?? 0) + 1);
console.log("Don't break:", [...keep].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k} (${v})`).join("; "));
Result
What slows you down most?
  3/6  CI fails randomly        Asha, Ravi, Sam
  3/6  waiting for review       Meera, Karthik, Sam
  2/6  CI takes ~40 min         Asha, Karthik
  2/6  tickets missing detail   Ravi, Dana
  1/6  too few test devices     Karthik

Anything about CI: 4/6 people, in both locations.
Don't break: daily standup is short (2); pairing with Asha (1); freedom to pick UI libraries (1); client demos every sprint (1)
What’s happening
  1. Each person's answer to "what slows you down most?" carries one or more theme tags, and their answer to "what's working that I shouldn't break?" is kept as written. Six people, four in Bengaluru and two in the US.
  2. Counting by theme, flaky CI and waiting for review are tied at 3 of 6. Slow CI is named by 2 more; combined, 4 of 6 people mention CI in some form, in both locations. That's the shared diagnosis behind the quiz below.
  3. "Waiting for review" (3 of 6, mostly in Bengaluru) is the second candidate, and it's one the lead can act on personally: the review habits in Small pull requests and fast reviews.
  4. The "don't break" list matters as much: two people value the short standup, so whatever you change about meetings, that stays. Client demos every sprint are valued too, which is a strength to build on with the client.
  5. Report the themes back to the team ("here's what I heard; I'm starting with CI"), never who said what. It shows you listened, and it makes the first improvement theirs.
AdvancedDiagnose the situation first

Watkins' point is that the same first moves don't fit every team. In engineering terms, the signals and the right first moves look roughly like this (the situations are his; the engineering signals and moves are a practical reading of them, not from the book):

SituationSignals you'll seeGood first moves
Start-upNew product or team, no conventions yet, empty repo or a prototypeSet the basics early: repo layout, CI, review norms, a first ADR. Decisions are cheap now.
TurnaroundMissed dates, incidents, a client losing patience, people leavingStabilise fast: stop the bleeding (incidents, the worst flaky tests), re-plan scope honestly, communicate often. Act within weeks.
Accelerated growthHiring fast, many new engineers, things breaking as the team growsOnboarding, written standards, CODEOWNERS, more automation; make the implicit explicit.
RealignmentThings look fine but the direction is wrong (stale architecture, a roadmap that no longer fits)Build a shared diagnosis first: data, incidents, cost of change. People resist change they don't see the need for.
Sustaining successTeam ships well and is happyLearn why it works and don't break it. Small improvements, protect what's good, grow people.
AdvancedReading the codebase's history in your first month

You can learn a lot about a codebase before reading most of it: where it changes most, where the bugs cluster, and who knows each part. Adam Tornhill's Your Code as a Crime Scene (Pragmatic Bookshelf, 2015; second edition 2024) calls the files that are both large and frequently changed hotspots: that's where complexity meets change, and where most defects and most of your attention will go. Git has the data already:

hotspots.mjs
// First-month reconnaissance: where does this codebase change most, and who knows it? Real git history.
import { execFileSync } from "node:child_process";
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const dir = mkdtempSync(join(tmpdir(), "hotspots-"));
let day = 0;
const git = (author, ...args) => {
  const when = new Date(Date.UTC(2026, 3, 1 + day, 10)).toISOString(); // one commit a day from 1 April 2026
  return execFileSync("git", args, { cwd: dir, encoding: "utf8", env: { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null",
    GIT_CONFIG_NOSYSTEM: "1", GIT_AUTHOR_NAME: author, GIT_AUTHOR_EMAIL: `${author.toLowerCase()}@example.com`,
    GIT_COMMITTER_NAME: author, GIT_COMMITTER_EMAIL: `${author.toLowerCase()}@example.com`, GIT_AUTHOR_DATE: when, GIT_COMMITTER_DATE: when } });
};
const grow = (path, n) => { // append n lines to a file
  const file = join(dir, path);
  mkdirSync(join(file, ".."), { recursive: true });
  const old = existsSync(file) ? readFileSync(file, "utf8") : "";
  writeFileSync(file, old + Array.from({ length: n }, (_, i) => `// ${path} change ${day}.${i}`).join("\n") + "\n");
};

git("Lead", "init", "-q", "-b", "main");
// [author, message, [file, lines added]...]: a made-up history, compressed to one commit a day.
const history = [
  ["Asha", "Device list API", ["src/api/devices.js", 120]], ["Ravi", "Room status page", ["src/ui/RoomStatus.jsx", 90]],
  ["Asha", "Login with SSO", ["src/api/auth.js", 200]], ["Asha", "Fix device paging", ["src/api/devices.js", 15]],
  ["Ravi", "Status colours", ["src/ui/RoomStatus.jsx", 20], ["src/lib/format.js", 10]],
  ["Asha", "Fix offline devices shown online", ["src/api/devices.js", 25]], ["Meera", "Duration formatting", ["src/lib/format.js", 15]],
  ["Asha", "Device reboot endpoint", ["src/api/devices.js", 60]], ["Ravi", "Fix status flicker", ["src/ui/RoomStatus.jsx", 12]],
  ["Asha", "Fix reboot timeout", ["src/api/devices.js", 10]], ["Meera", "Fix negative durations", ["src/lib/format.js", 5]],
  ["Asha", "Firmware version filter", ["src/api/devices.js", 40], ["src/ui/RoomStatus.jsx", 15]],
  ["Asha", "Fix firmware sort", ["src/api/devices.js", 8]], ["Ravi", "Settings page", ["src/ui/Settings.jsx", 70]],
];
for (const [author, message, ...changes] of history) {
  for (const [file, lines] of changes) grow(file, lines);
  git(author, "add", "-A");
  day++;
  git(author, "commit", "-q", "-m", message);
}

// Change frequency, fix commits and authors per file, from git log alone.
const log = git("Lead", "log", "--format=@%an|%s", "--numstat");
const files = new Map();
let author, subject;
for (const line of log.split("\n")) {
  if (line.startsWith("@")) [author, subject] = line.slice(1).split("|");
  else if (line.trim()) {
    const path = line.split("\t")[2];
    const f = files.get(path) ?? { commits: 0, fixes: 0, authors: new Map() };
    f.commits++;
    if (/^fix/i.test(subject)) f.fixes++;
    f.authors.set(author, (f.authors.get(author) ?? 0) + 1);
    files.set(path, f);
  }
}
const loc = (path) => readFileSync(join(dir, path), "utf8").split("\n").length - 1;
const rows = [...files].map(([path, f]) => ({ path, ...f, loc: loc(path), score: f.commits * loc(path) }))
  .sort((a, b) => b.score - a.score);
console.log("file                     commits fixes   LOC  hotspot  main author (share)");
for (const r of rows) {
  const [top, n] = [...r.authors].sort((a, b) => b[1] - a[1])[0];
  console.log(`${r.path.padEnd(24)} ${String(r.commits).padStart(7)} ${String(r.fixes).padStart(5)} ${String(r.loc).padStart(5)} ` +
    `${String(r.score).padStart(8)}  ${top} (${Math.round((100 * n) / r.commits)}%)`);
}
rmSync(dir, { recursive: true, force: true });
Result
file                     commits fixes   LOC  hotspot  main author (share)
src/api/devices.js             7     4   278     1946  Asha (100%)
src/ui/RoomStatus.jsx          4     1   137      548  Ravi (75%)
src/api/auth.js                1     0   200      200  Asha (100%)
src/lib/format.js              3     1    30       90  Meera (67%)
src/ui/Settings.jsx            1     0    70       70  Ravi (100%)
What’s happening
  1. The model builds a small history: 14 commits by three engineers over two weeks of April 2026, with fixed dates so the output is the same on every run. On a real repository you'd run the same git log --format=@%an|%s --numstat over the last six or twelve months.
  2. --numstat prints one line per file per commit (lines added, lines removed, path). Counting those lines per path gives change frequency; checking whether the commit subject starts with "Fix" gives a rough bug-fix count; recording the author gives who knows it.
  3. The hotspot score is commits × current lines, a common simple proxy. src/api/devices.js stands out: 7 commits, 4 of them fixes, 278 lines, score 1,946, more than three times the next file (548). That's where to read first, where review should be most careful, and the strongest candidate for refactoring or better tests.
  4. The last column is the warning: every change to devices.js was made by Asha (100%). The most fragile file in the system is understood by one person. That goes straight into the risk register (it's there in Owning a workstream) with a mitigation: pair someone else on the next changes.
  5. auth.js is big (200 lines) but changed once, so its score is low: big, stable code is less urgent than big, churning code. Tools such as CodeScene do this analysis properly (complexity rather than line counts, changes that happen together); the plain-Git version is enough to plan your first month's reading.
AdvancedOn a contract or through a consultancy

When you're placed with a client's engineering team, a few things change:

  • Two sets of expectations. Your employer's (delivery, reputation, the account) and the client's (their roadmap, their standards). Write both down; where they conflict, raise it early with your own manager first.
  • The client's standards win by default. Their review process, branching model and definition of done apply until you've earned the standing to propose changes, and then you propose through their decision process.
  • Access takes time. Repositories, VPN, devices, staging, CI: list what you need on day one and chase it daily. Lost days waiting for access are the most common slow start, and they're invisible unless you report them.
  • Make the team's work visible to the client. A short weekly written update (see the status example in Owning a workstream) and a demo every sprint build trust faster than anything you say.
AdvancedA day-one checklist

Things to ask for on day one, and chase daily until you have them. Each missing item is a day of the plan lost, so list them in your first status update if they're late:

  • Source control (every repository, with the right permissions), CI and its logs, the artefact or package registry.
  • Staging and production observability: logs, metrics, dashboards, error tracking, alerting (read-only for production is fine at first).
  • The ticket tracker, the team's chat channels, the wiki or docs space, the on-call rota and incident history.
  • Test devices or simulators, and how to book a real device if they're shared.
  • VPN and any client-side accounts, laptops or security training the client requires.
  • Calendars: the team's standups, the client's ceremonies, and both countries' public holidays.
AdvancedQuestions for the client's engineering contact in week one
  1. What does a great outcome from our team look like in three months? In a year?
  2. How do you want to be kept informed: written weekly status, a call, a dashboard?
  3. Which of your standards must we follow (review rules, branching, definition of done, security reviews)?
  4. Who decides scope and priority on your side, and who do we go to when we disagree?
  5. What went well, and badly, with previous vendors or teams on this work?
AdvancedChanging things before you know why they're like that
What's the right first win?

In your one-to-ones, four of six engineers say CI takes 40 minutes and fails randomly. Your manager would like you to "introduce trunk-based development". The client wants a new feature by the end of the quarter. What do you pick as your first visible improvement, and why?

Show answer

The CI pain. It's the team's own diagnosis, it helps every engineer every day, it makes the client's feature faster to deliver, and it's a prerequisite for trunk-based development anyway.

What’s happening
  1. Four of six people named it unprompted: that's a shared diagnosis, so fixing it earns trust instead of spending it. A win the team asked for is worth more than a better one they didn't.
  2. It compounds: a 40-minute flaky pipeline costs every engineer hours a week and makes reviews slower (people batch changes to avoid waiting). Fixing it speeds up the client's feature, which serves the client's expectation too.
  3. Trunk-based development needs fast, reliable CI: merging small changes to main several times a day isn't possible when each merge takes 40 minutes and fails randomly. So the CI fix is step one of your manager's goal, and you can say so (see Trunk-based development vs GitFlow).
  4. What you say: "I'd like to start with CI: the team named it, it'll speed up the feature work, and it's what we need in place before moving to trunk-based development. I'll have a plan in two weeks." That aligns the team's pain, the client's date and the manager's goal in one sentence.

#Leading by example in code

Added

A lead who stops coding loses touch with the codebase, the tools and the team's daily pain, and soon makes decisions that sound right in a meeting and are wrong in the code. A lead who codes like a senior engineer, picking the most interesting tickets, becomes the bottleneck and the team stops growing. The useful question isn't "how much should I code?" but "which code is mine to write?" The answer: work where your experience changes the outcome or that unblocks everyone, and nothing that sits on the critical path while you're in meetings all afternoon.

The mental model: you set the pattern; the team fills it in. Your code is the example people copy. The first endpoint in a new module, the first test with a fake clock, the first well-described PR with a rollback plan: once those exist, everyone writes the next ten the same way. So write the first one, or the riskiest one, and make it exemplary; then hand the next ones over, with the first as the template. What you show in your own PRs (small, tested, clear description, graciously reviewed) becomes the team's standard faster than any document.

What usually stays with the lead, and what usually goes to the team:

Usually the lead'sUsually delegated (with the right support)
Spikes into the unknown that decide the design (a new firmware API, a new library)Features in well-understood areas, even large ones
The first instance of a new pattern (the template others copy)The second to tenth instance of that pattern
Cross-cutting changes that touch every team's code (an auth change, a framework upgrade plan)Work that's a growth step for someone, with pairing at the start
Urgent production fixes nobody else can do yet (and then teach someone)Chores, flaky tests, small bugs (or automate them away)
Prototypes to make a trade-off concrete for stakeholdersAnything on the critical path that needs uninterrupted days

A simple way to decide, task by task, is two questions: how costly is getting this wrong, and how much would doing it teach someone on the team? Then check the total against the time you actually have for hands-on work:

delegation.mjs
// Who builds what this sprint? Each task is placed by two questions: how costly is getting it wrong,
// and how much does doing it teach someone on the team? The lead's own coding is capped.
const LEAD_CODING_BUDGET = 12; // hours of the lead's ~40 this sprint (the rest: reviews, planning, client, people)

const tasks = [
  { name: "Spike: firmware API for bulk reboot", hours: 8, risk: "high", teaches: "low", why: "unknown API, sets the design" },
  { name: "Device groups API (CRUD)", hours: 16, risk: "high", teaches: "high", why: "core feature, auth rules" },
  { name: "Groups admin page", hours: 20, risk: "low", teaches: "high", why: "good stretch for a mid-level dev" },
  { name: "Fix flaky status-poller test", hours: 4, risk: "low", teaches: "low", why: "chore" },
  { name: "Prod incident: SSO tokens expiring early", hours: 4, risk: "high", teaches: "low", why: "client-facing, urgent" },
  { name: "Write the device sync runbook", hours: 6, risk: "low", teaches: "high", why: "spreads Asha's knowledge" },
];

function plan(task) {
  if (task.risk === "high" && task.teaches === "low") return { who: "lead does it", leadHours: task.hours };
  if (task.risk === "high") return { who: "engineer drives, lead pairs", leadHours: Math.round(task.hours * 0.25) };
  if (task.teaches === "high") return { who: "delegate as a stretch, check-ins", leadHours: 2 };
  return { who: "delegate fully (or automate)", leadHours: 0 };
}

let leadTotal = 0;
for (const t of tasks) {
  const p = plan(t);
  leadTotal += p.leadHours;
  console.log(`${t.name.padEnd(42)} ${`${t.risk} risk / ${t.teaches} learning`.padEnd(26)} ${p.who.padEnd(33)} lead ${p.leadHours} h`);
}
console.log(`\nlead's hands-on time: ${leadTotal} h of a ${LEAD_CODING_BUDGET} h budget` +
  (leadTotal > LEAD_CODING_BUDGET ? ` → over by ${leadTotal - LEAD_CODING_BUDGET} h: hand the spike or the incident to someone, with support` : ""));
Result
Spike: firmware API for bulk reboot        high risk / low learning   lead does it                      lead 8 h
Device groups API (CRUD)                   high risk / high learning  engineer drives, lead pairs       lead 4 h
Groups admin page                          low risk / high learning   delegate as a stretch, check-ins  lead 2 h
Fix flaky status-poller test               low risk / low learning    delegate fully (or automate)      lead 0 h
Prod incident: SSO tokens expiring early   high risk / low learning   lead does it                      lead 4 h
Write the device sync runbook              low risk / high learning   delegate as a stretch, check-ins  lead 2 h

lead's hands-on time: 20 h of a 12 h budget → over by 8 h: hand the spike or the incident to someone, with support
What’s happening
  1. Each task is placed in one of four boxes. High risk, low learning (the firmware spike, the production SSO incident) defaults to the lead. High risk, high learning (the core groups API) is driven by an engineer with the lead pairing, about a quarter of the hours. Low risk, high learning (the admin page, the sync runbook) is a stretch assignment with check-ins. Low risk, low learning (the flaky test) is delegated fully, or automated.
  2. The per-task answers look reasonable, and that's the trap: added up, the lead has 20 hours of hands-on work in a 12-hour budget. The rest of the lead's week is reviews, planning, client calls and one-to-ones, and those don't shrink because the coding grew.
  3. So the model says what to do: hand the spike or the incident to someone, with support. Which one? The incident is urgent and client-facing, but the knowledge model below shows that only the lead knows the SSO code, so the incident is exactly the one to do with someone (they drive, you navigate) so it's never only you again.
  4. If you changed the spike's learning value to "high", it would become "engineer drives, lead pairs" at 2 hours and the total would drop to 14 hours, still over. The budget forces the conversation a lead has to have every sprint: what am I holding on to that someone else should be learning?
  5. The two questions and the hour budget are a heuristic, not a formula; the point is to make the choice explicitly each sprint instead of defaulting to "I'll just do it, it's faster". It is faster, this once.
AdvancedIs the lead the only one who knows it?

Knowledge concentration (a low "bus factor") is a delivery risk, and leads are often the worst offenders: they built the hard parts early. Git can tell you in seconds:

knowledge.mjs
// Is the lead the only person who knows the critical code? `git shortlog` per area answers it in seconds.
import { execFileSync } from "node:child_process";
import { mkdtempSync, mkdirSync, appendFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

const dir = mkdtempSync(join(tmpdir(), "knowledge-"));
let n = 0;
const git = (author, ...args) => {
  const when = new Date(Date.UTC(2026, 4, 1, 9, n)).toISOString();
  return execFileSync("git", args, { cwd: dir, encoding: "utf8", env: { ...process.env, GIT_CONFIG_GLOBAL: "/dev/null",
    GIT_CONFIG_NOSYSTEM: "1", GIT_AUTHOR_NAME: author, GIT_AUTHOR_EMAIL: `${author.toLowerCase()}@example.com`,
    GIT_COMMITTER_NAME: author, GIT_COMMITTER_EMAIL: `${author.toLowerCase()}@example.com`, GIT_AUTHOR_DATE: when, GIT_COMMITTER_DATE: when } });
};
const commit = (author, path) => {
  mkdirSync(join(dir, path, ".."), { recursive: true });
  appendFileSync(join(dir, path), `change ${++n}\n`);
  git(author, "add", "-A");
  git(author, "commit", "-q", "-m", `Change ${n} to ${path}`);
};

git("Lead", "init", "-q", "-b", "main");
const work = [["Asha", "src/sync/poller.js", 9], ["Ravi", "src/sync/poller.js", 1], ["Lead", "src/auth/sso.js", 6],
  ["Asha", "src/api/devices.js", 5], ["Ravi", "src/api/devices.js", 4], ["Lead", "src/api/devices.js", 1],
  ["Ravi", "src/ui/RoomStatus.jsx", 6], ["Meera", "src/ui/RoomStatus.jsx", 3]];
for (const [author, path, times] of work) for (let i = 0; i < times; i++) commit(author, path);

for (const area of ["src/sync", "src/auth", "src/api", "src/ui"]) {
  const counts = git("Lead", "shortlog", "-sn", "HEAD", "--", area).trim().split("\n")
    .map((l) => l.trim().split("\t")).map(([c, who]) => [who, Number(c)]);
  const total = counts.reduce((s, [, c]) => s + c, 0);
  const [top, topCount] = counts[0];
  const share = Math.round((100 * topCount) / total);
  const warn = share >= 80 ? (top === "Lead" ? "  ← only the lead knows this" : `  ← only ${top} knows this`) : "";
  console.log(`${area.padEnd(9)} ${counts.map(([w, c]) => `${w} ${c}`).join(", ").padEnd(28)} top ${share}%${warn}`);
}
rmSync(dir, { recursive: true, force: true });
Result
src/sync  Asha 9, Ravi 1               top 90%  ← only Asha knows this
src/auth  Lead 6                       top 100%  ← only the lead knows this
src/api   Asha 5, Ravi 4, Lead 1       top 50%
src/ui    Ravi 6, Meera 3              top 67%
What’s happening
  1. git shortlog -sn HEAD -- <path> counts commits per author for that path, most first (-s summary counts, -n sort by number). Passing HEAD matters in a script: without a revision, shortlog reads a log from standard input instead of the repository.
  2. src/sync: Asha made 9 of 10 commits (90%), the same concentration the risk register and the hotspot analysis found. The mitigation is already planned: pair Ravi on the next sync tickets and write the runbook.
  3. src/auth: the lead made every commit. This is the uncomfortable one: when the SSO incident in the delegation model arrives, the lead is the only person who can fix it, which is why that incident should be fixed by someone else with the lead pairing.
  4. src/api and src/ui are healthy: the top author has 50% and 67%, and others work there regularly.
  5. Commit counts are a rough signal (one person can make many tiny commits; reviews spread knowledge too), so treat 80% as "look closer", not as a verdict. Run it in your first month and again each quarter; the trend tells you whether delegation is working.
AdvancedWhat the lead still reviews personally

Leads shouldn't review everything (that's the bottleneck again), but some changes deserve your eyes even when someone else approves them:

  • Changes to auth, permissions and anything touching personal data.
  • Migrations on large or critical tables, and anything that's hard to roll back.
  • Public API contracts other teams or the client's devices depend on.
  • CI, CODEOWNERS and the review rules themselves (the files under .github/).
  • The first PR in a new area or from a new team member, so you can set expectations early and kindly.
  • A sample of everything else, now and then, to see how reviews are going: are comments labelled, is feedback kind, are tests real? Reviewing the reviews is how you keep standards without being on every PR.
AdvancedPairing: the lead's most useful hands-on tool

Pairing (two people, one problem, one screen, taking turns at the keyboard) lets a lead be hands-on without owning the ticket. It's also the fastest way to move knowledge out of one head, and it works across time zones if it's planned for the overlap.

  • Driver and navigator: the engineer drives (types), the lead navigates (thinks ahead, asks questions). Swap if you're showing something specific. A lead who grabs the keyboard has stopped pairing.
  • Short and planned: 60–90 minutes in the overlap, with a goal ("get the first groups endpoint passing its tests"), beats an open-ended screen share.
  • Use it where knowledge is concentrated: the knowledge model above says src/auth and src/sync. Pair someone on the next change in each.
  • Leave the engineer owning the result. After the session, they finish it, open the PR and take the review. The pairing was support, not a takeover.
AdvancedWhat leading by example looks like in your own PRs
  • Small, focused PRs with a real description (what, why, how to review, risk), because the team will copy the size and the description, good or bad.
  • Tests with every change, including the boring ones. If the lead skips tests "because it's urgent", everyone will.
  • Your code goes through review like everyone's. No self-merging, no "lead's PRs are approved by default". Ask a junior engineer to review your change, and take their comments seriously in public.
  • Do some of the unglamorous work: fix the flaky test, update the README, speed up CI. It signals that this work matters.
  • Admit your mistakes in the open: "my change caused yesterday's 500s; here's the fix and the test that would have caught it". A lead who does this makes it safe for everyone to.
AdvancedRewriting their code in review
Should the lead take this ticket?

A two-day ticket: add rate limiting to the public device API. You've done it before; nobody on the team has. It's on the critical path for a release in eight working days, and you have client workshops on three of those days. What do you do?

Show answer

Don't take it alone. Pair: an engineer drives, you design it with them on day one and review closely. You get the right design, the release isn't waiting on your fragmented calendar, and the team learns rate limiting.

What’s happening
  1. Your experience matters (high risk if done wrong: lock out real clients, or no protection at all), which argues for involvement. But you have three days of workshops in an eight-day window, so if you own it, the release depends on the person with the most interruptions.
  2. It also has high learning value: nobody else has done it, and rate limiting will come up again. In the delegation model's terms it's "high risk, high learning": engineer drives, lead pairs.
  3. So: spend an hour with the engineer on the design (algorithm, keys, limits, what the client sees on 429), let them build it, review it closely, and be available in the overlap. Your hands-on time is a few hours instead of two days.
  4. Tell the release owner what you've done and the fallback ("if it's not in review by day 5, I'll pair full-time on day 6"). Delegating doesn't mean giving up on the date.

#Balancing delivery, quality and people

Added

The three things a lead is responsible for pull against each other all the time. Delivery wants the feature by the date. Quality wants the tests, the refactor and the time to do it properly. People want a sustainable pace, interesting work and room to learn. Push only delivery and you get burnt-out engineers and a codebase that slows every sprint. Push only quality and you get beautiful code that ships late to a client who stops trusting you. Push only people and nothing hard gets done. The job isn't to pick one; it's to make the trade-off explicit, choose it deliberately, and say it out loud to the people affected.

The central idea is technical debt, Ward Cunningham's metaphor from 1992: shipping code you know isn't right is like borrowing money. You get something sooner, and you pay interest on it every time you work in that code, until you pay it back by fixing it. Debt isn't bad in itself; companies borrow on purpose. Martin Fowler's Technical Debt Quadrant (martinfowler.com, October 2009) adds the useful distinctions: debt can be deliberate or inadvertent (did you know you were taking it?) and prudent or reckless (did you weigh the trade-off?). Prudent, deliberate debt ("we ship now and fix it next sprint, here's the ticket") is a business decision. Reckless debt ("no time for tests") is just a slower way of failing. Inadvertent, prudent debt is inevitable: you learn what the design should have been by building it.

The mental model for the conversation with stakeholders: interest, not mess. "The code is messy" means nothing to a product owner. "Every change to device sync takes three days instead of one, and it's where four of our last six bugs came from" is interest, and anyone can weigh interest against features. This toy model shows the shape of the argument: a team that spends some of each sprint paying debt down against one that spends none:

debt-toy.mjs
// A toy model of technical debt as interest. Made-up parameters: the shape is the point, not the numbers.
const CAPACITY = 40;        // points the team can do in a sprint
const START_DRAG = 0.10;    // share of capacity lost to working around debt today
const DRAG_PER_FEATURE_POINT = 0.0008; // shortcuts: every feature point adds a little drag
const DRAG_REMOVED_PER_DEBT_POINT = 0.004; // paying down: every debt point removes some
const MIN_DRAG = 0.05;      // some friction never goes away

function run(debtShare, sprints = 20) {
  let drag = START_DRAG, delivered = 0;
  const perSprint = [];
  for (let s = 1; s <= sprints; s++) {
    const usable = CAPACITY * (1 - drag);
    const debtWork = usable * debtShare;
    const featureWork = usable - debtWork;
    delivered += featureWork;
    perSprint.push(delivered);
    drag = Math.min(0.9, Math.max(MIN_DRAG, drag + featureWork * DRAG_PER_FEATURE_POINT - debtWork * DRAG_REMOVED_PER_DEBT_POINT));
  }
  return { perSprint, drag };
}

const baseline = run(0);
console.log("debt share  features after 4 / 10 / 20 sprints   drag at the end   ahead of 0% from sprint");
for (const share of [0, 0.1, 0.2, 0.3]) {
  const { perSprint, drag } = run(share);
  const ahead = perSprint.findIndex((v, i) => share > 0 && v > baseline.perSprint[i]);
  console.log(`${String(share * 100).padStart(5)}%      ${[3, 9, 19].map((i) => perSprint[i].toFixed(0).padStart(4)).join(" / ")}` +
    `                   ${(drag * 100).toFixed(0).padStart(3)}%              ${ahead === -1 ? "-" : ahead + 1}`);
}
Result
debt share  features after 4 / 10 / 20 sprints   drag at the end   ahead of 0% from sprint
    0%       137 /  312 /  538                    53%              -
   10%       127 /  306 /  575                    30%              13
   20%       116 /  296 /  600                     5%              14
   30%       104 /  264 /  530                     5%              -
What’s happening
  1. It's a toy with made-up parameters, stated at the top: 40 points of capacity, 10% lost to working around debt today, every feature point adding a little drag (shortcuts), every point spent on debt removing some. Don't quote its numbers as facts; use its shape.
  2. 0% on debt is ahead early: 137 feature points after four sprints against 127, 116 and 104. That's why "no time for refactoring" feels right in any single sprint. But its drag climbs from 10% to 53%: by the end, half the team's capacity goes to fighting the code.
  3. 10% and 20% fall behind at first, then overtake the no-paydown team at sprints 13 and 14, and finish ahead (575 and 600 against 538). 20% gets drag down to the 5% floor and keeps it there. Paying a little interest-reduction every sprint wins over a horizon of months, not weeks.
  4. 30% never catches up within 20 sprints (530): once drag is at the floor, extra debt work just displaces features. More quality work isn't always better; past a point it's gold-plating.
  5. That's the argument to make with a client or a product owner, in their terms: "if we keep zero time for this, we'll be noticeably slower by spring; with about a fifth of each sprint we stay fast. Which risk would you rather carry?" The decision is theirs to share; making the trade-off visible is yours.
AdvancedFowler's quadrant, in practice
RecklessPrudent
Deliberate"No time for tests, just ship it." Avoid; if it happens, record it as debt immediately."We ship the simple version for the pilot, ticket DEV-412 to replace it before general release." A business decision, recorded with a payback plan.
InadvertentDebt from not knowing better practices. Fix with reviews, pairing, standards and training."Now we know how it should have been designed." Normal; refactor as you touch it.
AdvancedKeeping debt visible
  • Record deliberate debt when you take it: a ticket with the reason, the interest ("each new device type needs changes in three places") and the trigger to pay it ("before we add the fourth type").
  • Budget for paydown every sprint rather than asking for a "refactoring sprint" that never gets approved. A fixed share (often 10–20%, agreed with the product owner) is easier to defend than one-off requests, and the toy model shows why.
  • Pay it where you work. The best debt to fix is in code you're about to change for a feature anyway: the cost is lower and the benefit is immediate. The hotspot analysis in The first 30, 60 and 90 days tells you where that is.
  • Measure the interest: time to make a typical change in that area, bugs that trace back to it, onboarding time. Numbers turn "the code is bad" into a business case.
AdvancedSaying no, and what to say instead

A lead says no often, to scope, to dates, to shortcuts, and the skill is saying it so the other person still has options. Never "no" alone; always the cost and a choice:

Saying no with options
To a client asking for a feature mid-sprint:
  "We can do CSV export this sprint if we move the groups page to next sprint, or we can do it
   next sprint without moving anything. Which matters more to you for the 15 Dec release?"

To a date that isn't achievable:
  "With the current scope, 15 Dec isn't realistic: our estimate is 22 Dec, and the firmware
   API is still a risk. We can hit 15 Dec if bulk firmware update moves to January. I'd
   recommend that; bulk reboot alone covers the main request."

To a shortcut that creates reckless debt:
  "We can skip the integration tests to save two days, but the last three device-sync bugs
   were exactly what those tests catch. I'd rather cut the settings page polish instead."

To your own team, when someone wants to rewrite a module:
  "I agree it's painful. Let's write down what it costs us (time per change, bugs) and fix it
   in slices as we touch it. A full rewrite won't get scheduled; slices will."
What’s happening
  1. Each "no" is really "yes, if": here's what it would cost, here's the choice. The client keeps control of their priorities; you keep control of the plan's honesty.
  2. Dates come with the reason and the trade that makes the date possible, plus your recommendation. Stakeholders can't evaluate "it's hard"; they can choose between two scopes.
  3. Refusing a shortcut uses the team's own evidence (the last three bugs) and offers a different cut. You're not defending quality for its own sake, you're pointing at the interest.
  4. Saying no to your own team works the same way: agree with the pain, ask for the cost in numbers, and offer a path that will actually happen. Big-bang rewrites rarely get approved and often fail; incremental refactoring on the hot path usually does both.
AdvancedOrdering competing requests with cost of delay

When several things compete for the same people, "which first?" is easier to agree when you put a rough number on what waiting costs. Weighted shortest job first (from Don Reinertsen's The Principles of Product Development Flow, 2009) orders work by cost of delay divided by duration: short, valuable things first. The numbers are estimates agreed with the product owner; the point is to make the reasoning visible:

wsjf.mjs
// Four requests and one team. Order them by cost of delay ÷ duration (weighted shortest job first).
// Cost of delay is an estimate agreed with the product owner, in "value lost per week" units.
const requests = [
  { name: "Bulk reboot for device groups", costPerWeek: 8, weeks: 3 },
  { name: "CSV export of device list", costPerWeek: 2, weeks: 0.5 },
  { name: "Fix SSO tokens expiring early", costPerWeek: 10, weeks: 0.5 },
  { name: "Pay down device-sync debt", costPerWeek: 3, weeks: 2 },
];

function schedule(order) {
  let t = 0, totalCost = 0;
  const lines = order.map((r) => {
    t += r.weeks;
    totalCost += r.costPerWeek * t; // each request keeps costing until it's done
    return `${r.name} (done week ${t})`;
  });
  return { lines, totalCost };
}

const asked = schedule(requests); // the order they arrived in
const byScore = [...requests].sort((a, b) => b.costPerWeek / b.weeks - a.costPerWeek / a.weeks);
const wsjf = schedule(byScore);
console.log("score = cost of delay per week ÷ weeks:");
for (const r of byScore) console.log(`  ${(r.costPerWeek / r.weeks).toFixed(1).padStart(5)}  ${r.name}`);
console.log(`\nin arrival order: total cost of delay ${asked.totalCost}`);
console.log(`by score:         total cost of delay ${wsjf.totalCost}  →  ${wsjf.lines.join(", ")}`);
Result
score = cost of delay per week ÷ weeks:
   20.0  Fix SSO tokens expiring early
    4.0  CSV export of device list
    2.7  Bulk reboot for device groups
    1.5  Pay down device-sync debt

in arrival order: total cost of delay 89
by score:         total cost of delay 57  →  Fix SSO tokens expiring early (done week 0.5), CSV export of device list (done week 1), Bulk reboot for device groups (done week 4), Pay down device-sync debt (done week 6)
What’s happening
  1. Each request has a cost of delay (how much value is lost for every week it isn't done, in made-up units agreed with the product owner) and a duration. The SSO bug costs 10 a week because clients are being logged out; CSV export costs 2 a week because one stakeholder wants it.
  2. The score is cost per week divided by weeks: the SSO fix scores 20 (high cost, half a week), CSV export 4, bulk reboot 2.7, debt 1.5. The order is SSO, CSV, bulk reboot, debt.
  3. schedule adds up what each request costs while it waits: each one keeps costing until its finish week. In arrival order (bulk reboot first) the total is 89; by score it's 57. Doing the half-week SSO fix and CSV export first delays bulk reboot by only a week, while leaving SSO until week 4 costs 40.
  4. That's the "saying no with options" conversation in numbers: "CSV export is half a week, so yes, after the SSO fix; bulk reboot moves by a week." It's also a reason to say no: a request with a tiny cost of delay and a long duration sinks to the bottom on its own.
  5. Debt scores last here because its cost of delay is entered as a constant 3 a week. Real debt's cost grows (the toy model above), which is why debt is better handled as a fixed share of every sprint than as an item competing in this queue.
AdvancedWhat's negotiable and what isn't

Not every quality trade-off is open for discussion. Being clear about the line makes the negotiable ones easier to agree:

Not negotiable (a lead says no, and escalates if pushed)Negotiable (a real trade-off to make with the product owner)
Security basics: authorisation on every endpoint, no injection, no secrets in code or logsTest coverage beyond the critical paths
Data integrity: migrations that can't lose data, backups, a way to roll backRefactoring code that works and isn't changing soon
Privacy and legal requirements (personal data, accessibility obligations in contracts)UI polish, animations, edge-case empty states
Not knowingly shipping a bug that harms usersPerformance beyond the agreed targets
The checks that keep main releasable (required CI, review)Internal tooling and developer-experience work

When someone asks for a non-negotiable item to be skipped, the answer isn't a debate about quality; it's "we can't ship that, here's what we can ship by that date instead".

AdvancedThe people side of the balance
  • Sustainable pace is a delivery practice. Overtime to hit a date works once; repeated, it produces more bugs, more attrition and slower delivery. If a date needs weekends, that's a scope or date conversation to have, not a cost to hide.
  • Spread the good work and the bad. If the same engineer always gets the interesting features and another always gets bugs, both notice. Rotate, and use the delegation questions from Leading by example in code.
  • Protect focus. Across US–India time zones the overlap fills with meetings fast; guard blocks of uninterrupted time on both sides.
  • Adding people late doesn't speed things up quickly. Fred Brooks's observation in The Mythical Man-Month (1975), that adding people to a late software project makes it later, still holds for the first weeks: onboarding costs the existing team's time. Scope is usually the faster lever.
AdvancedWhen the interviewer asks for a story

"Tell me about a time you pushed back on a deadline" or "…balanced quality against a date" are standard questions for this role. Your answer has to be a real situation of yours; this page can't supply one. What it can give is the shape interviewers listen for (more in Behavioural Interviews), with prompts to find your own example:

  • Situation, in two sentences: the date, who wanted it, what was at stake. Prompt: when did a date or a scope request arrive that you knew wasn't realistic?
  • What you weighed: the options and their costs, with numbers if you had them. Prompt: what would have broken, or slowed down, if you'd just said yes?
  • What you did and said: the conversation, the options you offered, your recommendation. Prompt: who did you go to, and what exactly did you propose?
  • Result: what shipped, when, and what happened afterwards (including what you'd do differently). Prompt: was the trade-off right in hindsight?

Write yours in the site's My notes page and practise saying it in under two minutes.

Advanced"We'll clean it up later" without a ticket
Which is prudent, deliberate debt?
  1. "No time for tests this sprint; we'll add them when things calm down."
  2. "For the pilot site we'll poll device status every 10 s. Before general release (ticket DEV-430) we switch to push, because polling 400 devices per site won't scale."
  3. "We didn't know about database indexes when we built the reports page."
  4. "Now that we've built it, we can see the device and group models should share one permission check."
Show answer

2 is prudent and deliberate. 1 is reckless and deliberate, 3 is reckless and inadvertent, 4 is prudent and inadvertent.

What’s happening
  1. Statement 2 knows it's taking a shortcut (deliberate), has weighed it (polling is fine at pilot scale, not at 400 devices) and has a ticket and a trigger (before general release). That's debt as a business decision: prudent, deliberate.
  2. Statement 1 is deliberate (they know they're skipping tests) but reckless: no trade-off is weighed, and "when things calm down" is not a trigger. Things don't calm down.
  3. Statement 3 is inadvertent (they didn't know) and, in Fowler's terms, reckless: it comes from not knowing a basic practice. The fix is skills (reviews, pairing, standards), not just the index.
  4. Statement 4 is the normal kind: they did their best, and building it taught them the better design. Prudent, inadvertent debt is inevitable; pay it down when you next work in that code.

Written fresh for a lead full stack role. Git examples were run; GitHub Actions workflows were checked with actionlint. Behavioural stories are yours to write in My notes.

RohitDownloads · All chapters

Sync progress across devices

Type the same private phrase on your Mac and your phone, and your Learned ticks follow you between them — on every study site.

The phrase never leaves this device: only a fingerprint of it is sent, and the server stores a fingerprint of that. Anyone who knows the phrase could see or change your ticks, so pick something you don’t use elsewhere.

Sync is on in this browser.

Sync ID

This ID must be the same on every device. If another device shows a different one, its phrase is different (capital letters count): tap “Turn off here” on it and type the phrase again exactly.