Code on your laptop helps nobody. Shipping is everything between "it works on my machine" and "it works for everyone, and we'll know if it stops": saving versions, checking each other's work, building, testing, packaging, deploying carefully, and watching it run.
The 3D is one long conveyor. Follow a change along it, station by station. Each station is a program you can run here, from a git-style hash to the rule that decides whether to wake someone at 3 a.m.
Every code block runs in your browser. Edit it, break it, press Run.
01 · LAPTOP → USERS
The path of a commit
A change is born on the back-left of the floor and ends up in the city at the front. On its way it is committed to version control, reviewed by a colleague, built into files a machine can run, tested by robots, packaged into a container, deployed to a small slice of users and then to everyone, and watched. The crates change shape as they go: a commit, a bundle, a container.
Every arrow on that path is automated in a healthy team. A person decides what to change and whether a change is good; machines do the rest, the same way every time. That is what lets big teams ship many times a day without breaking things.
console.log("from laptop to every user in about",Math.round(minutes/60*10)/10,"hours");
The minutes are made up, but the shape is typical: the human step (review) is usually the slowest. Change the numbers for your own team.
5 metricsthat the DORA research programme uses to measure software delivery: change lead time, deployment frequency, failed deployment recovery time, change fail rate and deployment rework rate. Speed and stability, measured together. DORA
02 · VERSION CONTROL
Version control: a history you can trust
Version control records every saved state of a project, who made it and why, so you can go back, compare, and work in parallel without overwriting each other. Today that almost always means git. A commit is a snapshot of all your files plus a message and a pointer to the commit before it, its parent.
The trick that makes git trustworthy is that a commit's name is a hash of its content, parent included. Here is the idea in a few lines, with a small hash function standing in for git's (git uses SHA-1, hardened against known attacks, and is moving to SHA-256):
Change "hello" in the first commit and run again: all three ids change. The ids on the 3D graph are the ones this prints.
Branches, merges, rebases, checking out the past: the Git Time Machine lets you do all of it to a real commit graph in 3D, so this chapter won't repeat it. Spend ten minutes there, then come back.
03 · BRANCHES & REVIEW
Branches and code review
Nobody commits straight to main on a team. You make a branch, commit your work there, and open a pull request (or merge request): a proposal to merge your branch, shown as a diff, the lines removed in red and added in green. Teammates read it, comment, and approve. Then it merges.
A diff is itself an algorithm: find the longest run of lines the two versions share, and everything else is a removal or an addition. Git's default is Eugene Myers' 1986 algorithm; this is the simpler textbook version, built on the longest common subsequence:
The output is the panel in the 3D. Press Step through and watch i and j walk the two files.
Why review at all? Finding bugs is the reason people give, but a study of review at Microsoft (Bacchelli and Bird, 2013) found reviews are less about defects than developers expect, and bring other benefits: shared knowledge, awareness of what the team is changing, and better alternative solutions. At Google review is mandatory; its 2018 study of the practice drew on logs of 9 million reviewed changes.
04 · BUILDS & BUNDLING
Builds and bundling
The code you write isn't what runs. A build turns source into something you can ship: TypeScript becomes JavaScript, C becomes a binary, many small files become a few big ones. For the web, a bundler follows every import from an entry file, puts each module after the modules it depends on, drops code nothing uses (tree-shaking), shrinks it (minifying), and names the output after a hash of its contents.
The 3D build machine eats the files in this order, and drops unused.js in the bin. Edit any file's code and the output name changes.
That hashed name matters. Browsers can cache app.64fe6c11.js forever, because a new version gets a new name. And a good build is reproducible: the same source and the same lockfile (the exact version of every dependency) give the same output, on your laptop and on the build server.
terminal
npmci# install exactly what package-lock.json saysnpmrunbuild# here: tsc, then a bundler such as esbuild or Vite
05 · CI/CD
CI/CD: robots that check every change
Continuous integration (CI) means everyone merges small changes into the main line often, and every change is built and tested automatically. Kent Beck developed it as part of Extreme Programming in the 1990s; Martin Fowler's 2000 article made it famous. Continuous delivery (CD) goes further: every change that passes is ready to release. Continuous deployment releases it automatically.
A pipeline is a list of stages, and any failure stops the line, so a broken change never reaches users. On the stage, one crate in five fails its tests and is pushed into the reject bin. So does this program, on purpose:
// The pipeline: every stage must pass, or nothing ships
constpipeline=[
["install",()=>true],
["lint",()=>typeofslugify==="function"],
["test",runTests],
["build",()=>true],
["deploy",()=>true],
];
for(const[name,run]ofpipeline){
console.log("▶",name);
if(!run()){console.log("✗ stopped at",name,"· nothing was deployed");break;}
}
One test fails. Fix slugify (hint: strip a leading or trailing - with another .replace) and the pipeline runs to deploy.
In real life the pipeline is a file in your repository, run by a service such as GitHub Actions or GitLab CI on a fresh machine for every push:
.github/workflows/ci.yml
# .github/workflows/ci.yml · runs on every push and pull requestname:cion:[push, pull_request]jobs:test:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v7# get the commit-uses:actions/setup-node@v7with:node-version:24cache:npm# reuse downloads between runs-run:npm ci# exact versions from the lockfile-run:npm run lint-run:npm test-run:npm run build
To drive a full pipeline (build, test, scan, canary) and watch it catch a bad commit, open The Pipeline.
06 · CONTAINERS
Containers and Docker
"It works on my machine" happens because machines differ: a different Node version, a missing library, another operating system. A container image packs your program together with everything it needs: the files of a small Linux system, the runtime, your dependencies and your code. Run it anywhere and you get the same thing.
A container is not a virtual machine. It is an ordinary process that the Linux kernel fences off: namespaces give it its own view of files, processes and network, and cgroups cap the CPU and memory it can use. It shares the host's kernel, so it starts in moments.
Dockerfile
# Dockerfile · each instruction is a layerFROMnode:24-slim# a small Linux with Node installedWORKDIR/appCOPYpackage*.json./# copy only the dependency list first...RUNnpmci# ...so this slow step stays cachedCOPY..# then the code, which changes oftenRUNnpmrunbuildUSERnode# don't run as rootEXPOSE3000CMD["node","dist/server.js"]
Every instruction makes a layer, and layers are cached. A layer's cache key covers its instruction, the files it reads and everything below it, so when one layer changes, every layer above it is rebuilt. This simulation prints what the 3D container shows:
files["src/server.js"]="v2";// edit one source file
console.log("second build →",build(files));
The slow npm ci stays CACHED because the dependency list didn't change. Practice exercise 3 shows what happens when you get the order wrong.
71.1%of developers in the 2025 Stack Overflow survey use Docker, up 17 points in a year: the largest jump of any technology surveyed. Stack Overflow Developer Survey 2025
The finished image is pushed to a registry with a version tag, like the shelf beside the container. Running hundreds of containers across many machines is the job of an orchestrator such as Kubernetes: see it schedule and heal them on The Cluster.
07 · ENVIRONMENTS
Environments and configuration
A change usually passes through several environments: dev (your laptop), staging (a copy of production for final checks) and production (real users). The golden rule: build once, promote the same image. On the stage, the same image id sits on all three platforms. What differs is configuration: which database, which API, how much to log.
The Twelve-Factor App puts it simply: keep config in the environment, never in the code. Its test: could you publish your code as open source right now without leaking a single password?
config.js
12345678910111213141516171819202122
// One build, three environments: only the configuration differs.
Production refuses to start, because LOG_LEVEL is missing. Add it and run again. Crashing at start-up, loudly, beats running half-configured.
.env
# .env · on your laptop only: listed in .gitignore, never committedAPI_URL=http://localhost:8080LOG_LEVEL=debugDB_PASSWORD=local-dev-only# production's comes from a secret store
08 · INFRASTRUCTURE AS CODE
Infrastructure as code
Servers, databases, networks and permissions used to be set up by hand in a web console. Nobody could say exactly how production was built, and nobody could rebuild it. Infrastructure as code (IaC) describes it in files instead, kept in git and reviewed like any other change.
Most IaC tools are declarative: you write the state you want, and the tool compares it with what exists and works out the steps. That comparison is the plan; carrying it out is apply. Here is the heart of it:
plan.js
123456789101112131415161718192021222324252627
// Infrastructure as code: describe what you WANT; a tool works out the steps.
Run it twice in your head: after apply, the plan is empty. Applying the same file again changes nothing. That property, idempotence, is what makes IaC safe to rerun.
The best-known tool is Terraform. In August 2023 HashiCorp moved it from an open-source licence to the Business Source License; the community fork, OpenTofu, now lives at the Linux Foundation and reads the same files. Others: Pulumi (in ordinary programming languages), AWS CloudFormation, Ansible. Kubernetes runs the same compare-and-fix loop all day long.
09 · THE CLOUD
Cloud basics
The cloud is renting computers, storage and services from someone else's data centres, by the second, through an API. NIST's 2011 definition is still the clearest: on-demand self-service, broad network access, resource pooling, rapid elasticity and measured service. It names three service models: IaaS (rent virtual machines), PaaS (hand over your code and let the platform run it) and SaaS (use finished software). "Serverless" functions sit between PaaS and paying per request.
Clouds are split into regions (a city or area) and, inside each, availability zones: separate data centres with their own power, cooling and network, close enough to talk in a few milliseconds. On the stage, zone c goes dark and the load balancer keeps sending traffic to the other two. Spreading across zones is how you survive a fire, a flood or a cut cable:
availability.js
12345678910111213141516171819
constminutesPerYear=365*24*60;
constdown=(a)=>{
constm=(1-a)*minutesPerYear;
returnm>=1?Math.round(m)+" min down a year":(m*60).toPrecision(2)+" s down a year";
};
constzone=0.999;// one zone: up 99.9% of the time
console.log("1 zone: ",down(zone));
// In parallel, the app is up if ANY zone is up.
// (This assumes zones fail independently; real outages don't always.)
consttwo=1-(1-zone)**2;
constthree=1-(1-zone)**3;
console.log("2 zones: ",down(two));
console.log("3 zones: ",down(three));
// In series, a request needs EVERY part to work: multiply.
constdb=0.9995,loadBalancer=0.9999;
console.log("2 zones + db + LB:",down(two*db*loadBalancer));
Redundancy multiplies the failure chances together (parallel). A chain of parts multiplies the success chances (series), so every part you add to the path of a request costs a little uptime.
The catch is in the comment: the maths assumes zones fail independently. Shared software, a shared configuration push or a shared dependency can take them all down at once. To see the hardware inside one of these halls, walk The Data Centre; for how requests reach it, How the Internet Works.
Deploying is the riskiest moment in a change's life, so teams make it small, gradual and reversible.
Blue-green: run two complete copies. Blue serves users; you deploy to green, check it, then switch the router. Rolling back is switching back. Canary: send a small share of real traffic (1%, then 5%, 25%, 50%) to the new version and compare it with the old before going further. On the stage, the router's green share grows step by step.
canary.js
12345678910111213141516171819202122
// A canary rollout: widen step by step, roll back if it looks worse.
"baseline",(baseline*100).toFixed(2)+"%",ok?"✓ widen":"✗ ROLL BACK");
if(!ok)break;
}
As written, v42 is healthy and reaches 100%. Set NEW_ERROR_RATE to 0.02: the canary is caught at 1%, and 99% of users never see the bad release.
Feature flags separate deploying code from releasing a feature. The new code ships switched off, and a flag turns it on for chosen users or a percentage, with no deploy. The trick is to hash the user id, so each person gets the same answer every time:
flags.js
12345678910111213
// Feature flags: decide per user, the same way every time.
functionbucket(userId,flag){// 0..99, stable for this user and flag
And always keep a rollback: a fast, practised way back to the last good version. On 19 July 2024 a faulty CrowdStrike content update went to every customer at once and crashed an estimated 8.5 million Windows machines. Among the fixes CrowdStrike promised afterwards: deploy such updates in stages, starting with a canary.
11 · MONITORING
Monitoring and alerting
Once it ships, you need to know it works without asking users. That is observability, built from three kinds of signal: logs (a line of text per event), metrics (numbers over time: requests per second, error rate, memory) and traces (one request followed across every service it touches). OpenTelemetry is the open standard for collecting all three.
Google's SRE book names four golden signals to watch on any service: latency, traffic, errors and saturation (how full it is). Latency needs care: averages hide pain. Use percentiles:
percentiles.js
1234567891011121314
// 1,000 request times in milliseconds: most fast, a few very slow
console.log("p50 ",pct(50),"ms half the requests are faster than this");
console.log("p95 ",pct(95),"ms");
console.log("p99 ",pct(99),"ms 1 in 100 users waits at least this long");
The mean (120 ms) describes nobody. Most requests take about 70 ms, and one in a hundred takes over a second and a half.
A service level objective (SLO) is the target, like "99.9% of requests succeed over 30 days". The 0.1% left over is the error budget: room to take risks and ship. Alerts should fire on how fast you are spending it, the burn rate:
error-budget.js
1234567891011121314
// An SLO: 99.9% of requests succeed, measured over 30 days.
constslo=0.999;
constbudget=1-slo;// 0.1% may fail: the error budget
consthoursInWindow=30*24;
// Burn rate: how fast you are spending the budget (1 = exactly on pace)
consterrorRateLastHour=0.015;// 1.5% of requests failed
constburn=errorRateLastHour/budget;
constspent=burn*1/hoursInWindow;// fraction of the month's budget, in one hour
console.log("burn rate",burn.toFixed(1)+"×");
console.log("spent",(spent*100).toFixed(1)+"% of the 30-day budget in one hour");
console.log("budget gone in",(hoursInWindow/burn/24).toFixed(1),"days at this pace");
console.log(burn>=14.4?"→ page someone now":"→ a ticket can wait");
At a burn rate of 14.4 for one hour you spend 2% of the month's budget, the paging threshold Google's SRE Workbook suggests. Try 0.003: bad, but a ticket for working hours.
Page a human only for things that need a human, now. Everything else is a ticket or a dashboard.
12 · INCIDENTS
On-call, incidents and postmortems
Somebody has to answer when the alert fires. Teams share that in an on-call rota, usually a week at a time. When something breaks badly, it becomes an incident, with clear roles: an incident commander who coordinates (and doesn't type fixes), people working the problem, and someone keeping everyone else informed.
The first goal is to mitigate: stop the harm, usually by rolling back, before you understand the cause. On the stage the router snaps back to v41, the red arc runs from the on-call desk to the router, and the graphs calm down. Then you find out why, and write it down:
timeline.js
123456789101112131415161718
// A timeline, the backbone of every postmortem
consttimeline=[
["14:02","start","v42 rolls out to 5% (canary)"],
["14:09","","error rate on the canary climbs"],
["14:11","detect","alert fires; on-call is paged"],
["14:14","ack","on-call acknowledges, opens an incident"],
["14:19","","decision: roll back first, debug later"],
["14:23","mitigate","rollback to v41 done; errors back to normal"],
["15:40","resolve","cause found: a config key missing in production"],
console.log("time to detect: ",at("detect")-at("start"),"min");
console.log("time to mitigate:",at("mitigate")-at("start"),"min");
console.log("time to resolve: ",at("resolve")-at("start"),"min");
Time to detect and time to mitigate are what you improve next time: better alerts shrink the first, practised rollbacks the second.
A postmortem follows every serious incident: the timeline, the impact, the causes and the actions that will stop it happening again, each with an owner. It is blameless. It looks for causes in the system (a missing check, a confusing tool) and assumes people did sensible things with what they knew. Blame teaches people to hide mistakes; the system only improves when they are told.
Ready to try it? On Call puts you at the console at 03:12 with five minutes to fix production, and writes the blameless postmortem from what you actually did.
13 · WHAT NEXT
The career map: what to learn next
You have now seen the whole journey, from a value in memory to a release watched by alarms. Above the floor, the career paths grow out of the stations they start from. Most of them share this chapter: every developer ships.
Front-end: HTML, CSS, a framework such as React, accessibility, performance. Back-end: APIs, databases, queues; chapters 11 to 14. DevOps and platform: containers, CI/CD, IaC, Kubernetes. SRE: monitoring, SLOs, incidents, capacity. Security: chapter 15, then threat modelling and secure review. Data: SQL, pipelines, statistics.
Go T-shaped: broad enough to follow a request through the whole system (Your Request's Journey shows one), deep in one part.
A portfolio is proof, not a list. Two or three real projects beat twenty tutorials: each live at a URL, with a README that says what it does and why, tests, a CI badge, and a short write-up of a hard bug you fixed. That write-up shows how you think, which is what an interviewer is trying to find out.
Open source is the closest thing to a real job you can do for free. Start small: fix a typo in the docs, reproduce a bug report, add a test. Look for issues labelled good first issue, read the project's CONTRIBUTING file, and ask before starting something big. Reviewers' comments are free mentoring.
Keep learning the way the course hub describes: retrieval, spacing and projects. Flashcards for what you want to keep, on a schedule like this one:
leitner.js
1234567891011121314151617
// Keep learning: the Leitner box. Right answers wait longer; wrong ones start over.
constgaps=[1,2,4,8,16,32];// days until the next review, by box
functionreviewDays(answers){
letbox=0,day=0;
constdays=[];
for(constrightofanswers){
days.push(day);
box=right?Math.min(box+1,gaps.length-1):0;
day+=gaps[box];
}
returndays;
}
// "What does a canary deploy do?" answered over two months
console.log("review on days",reviewDays(answers).join(", "));
Make the third answer true and see the gaps grow. A wrong answer sends the card back to day one.
PRACTICE
Now you do it
Type the code yourself rather than pasting it: producing an answer builds memory that reading does not. Use the hints one at a time.
1
A three-way merge
Git merges by comparing both sides with the base: the version you both started from. Write merge3: if only one side changed the value, take that change; if both made the same change, take it; if they changed it differently, return "CONFLICT".
exercise-1.js
12345678910
// base: the value before either of you changed it
functionmerge3(base,ours,theirs){
// your code here
returnours;
}
console.log(merge3("red","red","blue"));// only they changed it → blue
console.log(merge3("red","green","red"));// only we changed it → green
console.log(merge3("red","pink","pink"));// same change → pink
console.log(merge3("red","green","blue"));// different changes → CONFLICT
2
Find the circular import
A bundler walks imports depth-first. If a file imports itself through a chain, the walk would loop forever. Finish findCycle so it returns the loop, like ["cart.js", "price.js", "tax.js", "cart.js"], or null if there isn't one.
exercise-2.js
1234567891011121314151617181920
constmodules={
"main.js":["cart.js"],
"cart.js":["price.js"],
"price.js":["tax.js"],
"tax.js":["cart.js"],// tax imports cart, which imports price, which imports tax...
// 1. if name is already on the path, you found a loop: return it
// 2. if it is done, return null
// 3. otherwise push it, visit each import, pop it, mark it done
returnnull;
}
returnvisit(entry);
}
console.log(findCycle("main.js"));
3
Make the Docker cache work for you
This Dockerfile reinstalls every dependency whenever any file changes, which can cost minutes on every build. Reorder the steps (and add one if you need to) so that editing src/server.js only re-runs the last two.
Write the rule a deploy system runs at every step of a canary. Return "rollback" if the canary's error rate is more than double the baseline's, or if its p99 latency is more than 20% higher. Otherwise return "promote".
The SLO is 99.9%, so the error budget is 0.1%. Page the on-call engineer only when the burn rate (error rate ÷ budget) is at least 14.4 over the last hour and over the last five minutes, so a problem that has already stopped doesn't wake anyone.
exercise-5.js
12345678910
constbudget=0.001;// 1 - 0.999
functionshouldPage(errorRate1h,errorRate5m){
// your code here
returnfalse;
}
console.log(shouldPage(0.02,0.02));// true: burning fast, still burning
console.log(shouldPage(0.02,0.001));// false: it was bad, it has stopped
console.log(shouldPage(0.005,0.05));// false: a short blip, not an hour's burn
RECALL
Check yourself, from memory
Answer each one before you open it. Then come back tomorrow, and again next week, and answer them cold: retrieval spaced over days is the most reliable study method we know of. Why that works.
Why does changing an old commit change the id of every commit after it?
What is the difference between continuous integration, continuous delivery and continuous deployment?
Why build one container image and promote it through every environment, instead of building for each?
Blue-green, canary, feature flag: what does each one switch?
Why is the p99 latency often more useful than the average?
What is the first goal in an incident, and what makes a postmortem blameless?
GO FURTHER
Resources
BOOK · FREEPro Git ↗Scott Chacon and Ben StraubThe official git book. Chapters 2, 3 and 5 cover everyday use, branching and working with others.
COURSE · FREEFull Stack Open, part 11: CI/CD ↗University of HelsinkiBuild a real GitHub Actions pipeline for a real app, with deployment and health checks.
DOCS · FREEDocker: Get started ↗DockerBuild an image, run a container, and use the layer cache well.
ARTICLE · FREEThe Twelve-Factor App ↗Twelve short rules for apps that deploy cleanly: config, builds, logs, processes.
ARTICLE · FREEA list of public postmortems ↗Dan Luu and contributorsHundreds of real incident write-ups. Read one a week: it's the cheapest experience there is.
PRACTICE · FREEFirst Contributions ↗Make your first open-source pull request in a repository built for exactly that.
ARTICLE · FREEroadmap.sh: DevOps ↗A community map of what to learn next if this chapter is the part you enjoyed most.