📒 problems 📖 glossary
Mock interview
46:30
ready
of 46:30
Lesson 88 · Engineering craft

From interview to the job

Everything up to here trained you to pass the interview — to solve a clean, self-contained problem alone, on a whiteboard, in 45 minutes, with a fresh blank function and a single right answer. That is a real skill, but it is a thin slice of the job. The interview asks: can you write correct code under pressure? The job asks something much larger and stranger: can you safely change a system you did not write, that you do not fully understand, alongside people you did not choose, in a way that does not break the thing currently paying everyone's salary — and then keep doing that, on the same system, for years? This lesson is the on-ramp to that wider craft. Think of the interview as one tile; here is the whole mosaic.

A concrete day one

Picture your first morning on a real team. You are handed a laptop and a task that sounds trivial: "the checkout page shows the wrong tax for orders shipped to Canada — fix it." In an interview this is a five-line function. On the job, you open the repository and find around 200,000 lines of code spread across hundreds of files, written by forty people over eight years, half of whom have left the company. There is no function called calculateTax. There are three of them, in different modules, and you have no idea which one the checkout page actually calls. The build takes nine minutes. There is a 600-line file whose comment at the top says "do not touch — see incident 4471." Nobody can tell you what incident 4471 was.

Nothing you practiced for the algorithm round helps with this moment. The bottleneck is no longer "can I think of the solution" — the fix itself may be one line. The bottleneck is understanding the system well enough to know which one line, and being confident that changing it will not break checkout for the other forty countries. That gap — between writing correct code in a vacuum and changing live code in a crowd — is what the rest of this phase fills in. The four shifts below are the heart of it.

You read far more code than you write

In an interview you start from a blank function. On the job you start from a 200k-line codebase that will outlive your tenure. Most engineers spend the majority of their time reading: tracing how a request flows, figuring out why a test exists, learning the blast radius of a change before they dare make it. Writing is the easy 20% that happens after you understand.

Correctness is table stakes — maintainability wins

"It passes the tests" is the floor, not the ceiling. The code lives for years and is edited by strangers (including future you, who will have forgotten everything). Clear names, small functions, and obvious control flow beat a clever one-liner every single time. The next person's comprehension is a feature you are shipping, as real as any the user sees.

Work flows through tickets, branches, PRs, review, and increments

You don't hand in a finished solution. You pick up a ticket, cut a branch, make a small change, open a pull request, get it reviewed, let automated checks run, then merge a small, safe delta — and repeat, forever. Big-bang rewrites are how projects die; steady small increments are how they ship.

You own code in production, not until it compiles

The interview ends when the code runs once. The job continues: your code gets deployed, serves real users, pages you at 3am, and has to be diagnosed from logs and metrics — not a debugger you can attach. "Done" means observable, operable, and recoverable, not merely merged.

The mental shift in one sentence: an interview rewards finishing fast and alone; the job rewards moving a shared system forward safely, forever. The same engineer who aces the algorithm round can flounder on day one — not for lack of smarts, but because nobody taught them the surrounding craft. That craft is learnable, and it is what the rest of this phase teaches.

Shift 1 — You read far more code than you write

Why is reading the dominant activity? Because of one structural fact: production codebases are large and long-lived. A feature you ship today will be read, debugged, and extended hundreds of times before it is ever deleted. The ratio of reading to writing in a mature codebase is often quoted as ten to one, and that feels low once you are living it. You cannot safely add to a system you do not understand, and understanding a system you did not write means reading it.

What does "reading to understand" actually look like? It is not reading top to bottom like a book. It is investigative. Three concrete moves come up constantly:

  • Tracing a request. You follow one user action through the layers it touches. For the Canada-tax bug: the browser sends POST /api/checkout; a route handler receives it; that calls an OrderService; which calls a TaxCalculator; which loads a tax_rates table from the database. You read each hop in order, building a map of what actually runs, not what you assume runs. Tools that help: full-text search for the URL string, "find all references" on a function, and reading the call stack from a real error.
  • Finding where a change belongs. Given three calculateTax functions, which does checkout use? You search for where each is imported and called, and you may add a temporary log line, run the page, and watch which one fires. The goal is to locate the single correct place for your change before writing anything.
  • Estimating blast radius. Before editing TaxCalculator, you ask "who else calls this?" If twelve other features depend on it, your one-line fix for Canada could silently break tax for everyone. "Find all references" answers this. Blast radius is the set of everything that could be affected by a change — knowing it is the difference between a safe fix and an outage.

Concretely, the Canada fix might turn out to be a single wrong value in the tax_rates table, or a missing province lookup in the one TaxCalculator that checkout uses. You will have read perhaps fifty files to change one. That is normal and good — the reading is the work. Reading skill compounds with Git tools like blame (who changed this line, and why) and with disciplined code review, where you read others' code every day.

Shift 2 — Correctness is table stakes; maintainability wins

Maintainability means how cheaply the next person can read, understand, and safely change the code — measured in their time and their risk of introducing a bug, not in yours. Two solutions can both pass every test and be worlds apart on this axis. Suppose the ticket is "apply a 10% discount to orders over $100, but cap the discount at $25." Here is one passing solution:

function d(o) {
  return o.t > 100 ? (o.t * 0.1 > 25 ? 25 : o.t * 0.1) : 0;
}

It works. It also tells the next reader nothing: d and t are meaningless, the nested ternary hides the cap logic, and the rules are not visible. When the business changes the cap to $40 next quarter, the editor must reverse-engineer the math first. Here is the other passing solution:

const DISCOUNT_THRESHOLD = 100;
const DISCOUNT_RATE = 0.1;
const MAX_DISCOUNT = 25;

function discountFor(order) {
  if (order.total <= DISCOUNT_THRESHOLD) return 0;
  const raw = order.total * DISCOUNT_RATE;
  return Math.min(raw, MAX_DISCOUNT);
}

Same output, every test passes the same. But the second version names the rules: the threshold, the rate, and the cap are constants the next reader can change without touching logic. The early return makes the "no discount" case obvious. Math.min states the cap as plainly as English. Changing the cap to $40 is now a one-number edit, made with confidence. The second engineer shipped the same correctness plus a gift to whoever comes next. Over a codebase's life, that gift is worth far more than the microseconds the first version might save. We go deep on the principles behind this — naming, cohesion, coupling, designing for change — in software design, building on OOD and API design.

Shift 3 — Work flows through tickets, branches, PRs, review, increments

On a team you never just "save the file and tell everyone it's fixed." Work moves through a defined loop, and learning the loop is as important as learning to code. Here is the full cycle for the Canada-tax fix, step by step:

  • Pick up a ticket. A ticket (in Jira, Linear, GitHub Issues) is the unit of tracked work: a title, a description, an owner, a status. "BUG-4471: Canada orders charge US tax rate." You assign it to yourself so the team knows it is being handled and no one duplicates it.
  • Cut a branch. You create a branch — an isolated copy of the code where your changes live without touching what everyone else builds on. Convention: fix/canada-tax-rate. The shared main branch stays deployable while you work.
  • Make a small change. You fix the one wrong rate and add a test that fails before your fix and passes after — proof the bug is gone and a guard so it never returns. You keep the change small: one logical thing, reviewable in minutes, not a sprawling refactor bundled in.
  • Open a pull request. A pull request (PR, also "merge request") proposes merging your branch into main. It shows the exact diff and a description of why. It is the unit of review and the permanent record of the change.
  • Get it reviewed. A teammate reads your diff, asks questions, suggests improvements, and approves. Review catches bugs, spreads knowledge, and keeps quality consistent — it is a conversation, not a gate to resent.
  • Let CI run. Continuous Integration automatically builds your branch and runs the whole test suite on every push. If anything is red, the PR cannot merge. Humans review intent; the machine guards correctness tirelessly.
  • Merge. With approval and green checks, the branch merges into main. Your small delta is now part of the shared truth.
  • Deploy. Continuous Deployment ships main to production — often automatically, often gradually (a few servers first, watch the metrics, then the rest). Minutes later, real Canadians are charged the right tax.

Then you do it again with the next ticket. The discipline is small, safe increments: a project that ships a hundred ten-line PRs is far healthier than one that ships a single thousand-line PR, because each small change is easy to review, easy to test, and easy to revert if it misbehaves. The team mechanics of branches, PRs, and merge-versus-rebase are covered in version control in a team (building on solo Git); the automated pipeline is CI/CD (leaning on testing); and the review conversation itself is code review.

Shift 4 — You own it in production

In an interview, "done" is when the code runs once on the examiner's input. On the job, merging is the beginning of your code's life. Ownership means three concrete responsibilities after the merge:

  • Observability. You cannot attach a debugger to a server handling thousands of users. So the code must report on itself: logs (timestamped records of what happened), metrics (numbers over time — request rate, error rate, latency), and traces (the path of one request across services). If your tax fix is wrong, you find out from a spiking error metric and a log line, not a breakpoint.
  • On-call. Teams rotate who carries the pager. When you are on-call, you are the human a monitoring system wakes up when something breaks — at any hour. It is the price and the privilege of owning what you ship.
  • Rollback. The fastest cure for a bad deploy is usually not a heroic fix — it is reverting to the previous known-good version in one command, restoring service in seconds, and diagnosing calmly afterward. A system you can roll back is a system you can change bravely.

Make it vivid. It is 3am and your phone screams. A dashboard shows the checkout error rate jumped from 0.1% to 12% eight minutes ago — right after a deploy. You open the logs and see NullPointerException in the tax module on orders with no province set. You do not debug live; you roll back the deploy, watch the error rate fall to normal within a minute, and go back to sleep. The next morning, calm and caffeinated, you reproduce the null-province case, add a test, fix it properly, and ship again through the normal loop. That whole story — observe, page, roll back, fix safely — is only possible because the system was built to be operated, not just to compile. The tooling and discipline behind it is observability and SRE, and the realities of running things across many machines (flaky networks, disagreeing clocks, partial failures) come alive in distributed systems in practice.

The map of this phase

Each lesson ahead builds on this on-ramp, turning one of the four shifts into concrete, practiceable skill:

  • Version control in a team — branching, pull requests, merge vs rebase, and keeping changes reviewable. Builds on the solo basics in Git fluency.
  • CI/CD — the pipeline that builds, tests, and ships your branch automatically so humans don't gatekeep every deploy. Leans on testing.
  • Containers & cloud — packaging your app so it runs the same on your laptop and in production, and renting the machines to run it.
  • Observability & SRE — logs, metrics, traces, and the on-call discipline that turns "it's down" into "here's exactly what broke."
  • Distributed systems in practice — what system design theory actually feels like when the network is flaky and clocks disagree, drawing on networking and databases.
  • Caching — keeping hot data close to make systems fast, and the tricky business of invalidating it when the source changes.
  • Message queues — decoupling services so work can be handed off, buffered, and retried instead of done synchronously in the request.
  • Data modeling — designing the schemas and relationships your code rests on, so the data stays correct and queryable as the product grows.
  • Auth & security — proving who a user is, controlling what they may do, and not leaking the secrets that protect everyone.
  • Software design in the large — structuring code so it stays soft (changeable), extending OOD, API design, and code review.
Takeaway: the interview measured your ability to solve a clean problem alone in 45 minutes; the job measures your ability to safely evolve a large, shared, living system over years. The four shifts — read before you write, value maintainability over cleverness, move in small reviewed increments, and own your code in production — are the whole difference. None of them are about raw intelligence; all of them are learnable craft, and the rest of this phase teaches them one at a time.
You don't need a job to practice any of this. Contribute to an open-source project and you immediately read a codebase you didn't write, open a PR, and get reviewed by strangers. Deploy a side project with real CI — a small app behind a build-and-test pipeline that ships on every push — and you exercise the entire loop: read, change a little, review, merge, deploy, observe, roll back. The craft is available to you today, exactly where you are. Start small and ship.
Go deeper (optional): two short, free reads pair well with this lesson — the original "Coding Horror" essay on how programmers spend most of their time reading code, and Google's freely published Site Reliability Engineering book (sre.google/books), whose early chapters make the "own it in production" mindset concrete with real on-call practice.