Library
00/07 · ~36 min
GUIDEDECK · the workshop around the code

Developer
Tooling — the craft
around the craft.

A 36-minute working session on the tools fast teams take for granted: Git's model and workflows, CI/CD pipelines, build & dependency management, and the debugging/profiling kit that turns guessing into measuring.

~36 MINMIXED TEAMLANGUAGE-AGNOSTIC
SCROLL
01 · Why this matters 3 min

Fast teams aren't faster typists —
they have a shorter loop.

The gap between slow and fast teams is rarely talent. It's feedback latency: how long from "I changed something" to "I know if it worked." Every tool ahead exists to shrink that loop and make it automatic, repeatable, and shared.

100×

A bug caught at your desk versus in production can cost two orders of magnitude more to fix.

"Works on my machine" — the failure mode every tool here is built to delete.

1

command to clone, build, test, ship. The number a healthy repo aims for.

<10m

A green pipeline that runs in minutes is one people actually wait for. Slow CI gets bypassed.

1 · change 2 · build 3 · test 4 · know the loop minutes

You change something, build it, test it, and find out if it worked — then go round again. Every tool ahead exists to make one trip around this loop take minutes instead of days.

What "feedback latency" means

It's the wait between "I changed the code" and "I know whether I broke anything." A slow team might wait a day for that answer; a fast team gets it in a couple of minutes.

  • Short loop— you catch a mistake while it's still fresh in your head and cheap to fix.
  • Long loop— you've moved on, forgotten the details, and the bug has had time to spread.

The two questions every tool answers

  • Did it change? — version control: a perfect, shared memory of every state the code has ever been in.
  • Does it still work? — automation: builds, tests, and deploys that run the same way on every machine, every time.

Master those two and the rest of engineering gets dramatically cheaper to do well.

02 · Git's model 7 min

A commit is a snapshot;
a branch is just a pointer.

Most Git confusion dissolves the moment you see the data model. Git is not storing diffs — it stores immutable snapshots linked into a graph. Branches and tags are nothing more than sticky notes on that graph. Learn the model and the commands stop feeling like magic.

Git — the tool that remembers every version of your project. Every commitis a complete photo of all your files at one moment, plus a note (who, when, why). It's named by a hash — a short fingerprint calculated from the contents, like a1c9f2. Once made, a commit never changes. Each one points back to the commit before it, so your history is a chain (that can branch but never loops back on itself). A branch is just a sticky note pointing at one commit; HEADis the sticky note for "where you are right now."

Read the graph

  • Each node is a commit, named by its hash. Arrows point to the parent — history flows backward in time.
  • main and feature are branches — labels stuck on a commit. Committing just moves the label forward.
  • HEAD marks your current branch. Branching is cheap because it creates one pointer, not a copy.
a1c9f27e4 b80c33 main feature HEAD

Commits link to parents; branches (main, feature) and HEAD are just pointers into the graph.

Words you'll see at the terminal

working tree

Your files, right now

The actual files on disk. Where you edit. Nothing is tracked until you stage and commit it.

staging area

The next commit, drafted

An explicit middle layer. git add picks exactly which changes go into the next snapshot — so commits stay focused.

commit

A saved snapshot

Immutable. Identified by its hash. Carries a parent link, message, author, and timestamp. The unit of history.

remote

A shared copy

Another repository (e.g. origin). push and fetch sync commits between yours and it.

A commit is a snapshot — work like it

One giant "save point"
# a week of work, one commit git add . git commit -m "wip stuff" # later: a bug is in here, somewhere. # bisect is useless, revert is all-or-nothing, # review is impossible.
Small, atomic, reversible
# each commit = one coherent change git add src/auth.ts git commit -m "fix: reject expired tokens" git add src/login.tsx git commit -m "feat: remember-me checkbox" # now: bisect finds the bad commit, # revert is surgical, review reads like a story.
  • The mental model: commits are snapshots; Git computes diffs for display, but stores whole trees (de-duplicated by hash).
  • Atomic commits are the payoff of the staging area — they make revert, cherry-pick, and bisect powerful instead of theoretical.
  • Nothing is lost. Even "deleted" commits live in the reflog for weeks — Git is a safety net, once you trust the model.
03 · Git workflows 7 min

Short-lived branches,
small pull requests, clean history.

The model is simple; the discipline is the workflow. The goal is to integrate often and in small pieces so merges stay trivial and review stays human-sized. Then a deliberate choice — rebase or merge — decides what your history looks like.

Pull request (or merge request) — a proposal to merge one branch into another, wrapped in review, automated checks, and discussion. It's the gate where a change earns its way into the shared branch: CI must pass, a human must approve, and the conversation stays attached to the commit forever.
M ABC f1f2 main feature branch branch off merge back

Fork at B, do focused work (f1, f2) in isolation, then merge back at Mmain stays releasable the whole time.

The branch lifecycle, in four moves

  • Branchgit switch -c feature forks a private line off main; nothing you do can break it yet.
  • Commit — small, focused commits on the branch while main keeps moving independently.
  • Integrategit rebase main (or merge it in) often, so you find conflicts in ones and twos, not all at once.
  • Merge— open a PR; once it's green and reviewed it merges back, and the branch is deleted. Short loop, clean trunk.
Long-lived branch — merge hell
# branch lives for 3 weeks, 60 commits git checkout -b big-refactor # ...meanwhile main moves 200 commits ahead... git merge main # 47 conflicts. nobody remembers why. # review is a 4,000-line wall. rubber-stamped.
Short-lived branch — trivial merge
# branch lives ~1 day, one focused change git checkout -b fix/expired-tokens # open a PR early, keep it < ~400 lines git push -u origin HEAD # CI green + 1 review → merge same day. # integrate often → conflicts stay tiny.
Merge — preserves what happened
M main feature merge commit

A merge commit M has two parents — the branches stay visible. True to history, but the graph forks.

Rebase — replays for a straight line
f1'f2' main replayed

Rebase rewrites f1, f2 as f1', f2' atop main — linear history, but new hashes.

  • Merge keeps the real shape of history and never rewrites commits — safe for shared branches. The cost is a forky graph.
  • Rebase rewrites your commits onto a new base for a clean, linear story. Great before you share — never on commits others already pulled.
  • The golden rule of rebase: never rebase public history. Rewriting commits others have changes their hashes and detonates their clones.
  • Branch off main, keep it short-lived, open a PR early.
  • git rebase main (or pull --rebase) to keep your branch currentand tidy while it's still yours.
  • Merge into main via a PR — squash or merge-commit, pick one and be consistent.
  • Protect main: required checks + review, no direct pushes. The branch everyone depends on is never broken.
04 · CI/CD pipelines 6 min

Every push runs the same
checks, the same way, automatically.

A pipeline is the team's definition of "working", written as code and enforced on every change. It removes the two worst words in software — "it should" — and replaces them with a green check or a red X.

CIContinuous Integration: everyone merges small changes into a shared branch frequently, and an automated build + test suite verifies each one. CD Continuous Delivery / Deployment: that same green build is automatically packaged and released — to a button (delivery) or all the way to users (deployment).
push / PR Lintformat Testunit+int Buildartifact Scandeps/SAST Deploy if green any stage red → pipeline stops, no deploy ✕

One change flows through identical stages. The deploy is gated — a single red stage stops the line.

The pipeline as code

on: [push, pull_request] jobs: verify: steps: - checkout - run: install --frozen-lockfile - run: lint - run: test --coverage - run: build deploy: needs: verify # only if green if: branch == "main" steps: [ deploy ]

Like an assembly line with a quality gate — nothing moves to the next station until the current one passes.

Why automate it

Manual & tribal
# the deploy runbook lives in someone's head scp -r ./dist prod:/var/www # from a laptop ssh prod "restart && pray" # tests? "I ran them locally." # the one person who knows is on holiday.
Coded & repeatable
# merge to main → pipeline does the rest git push origin main # → lint, test, build, scan, deploy # → identical on every run, fully logged # → rollback = redeploy the last green tag
  • Fast feedback:CI runs in minutes and blocks the merge, so broken code never reaches teammates' clones.
  • One source of truth: the pipeline file is the build process — versioned, reviewable, identical for everyone.
  • Safe, boring deploys: when shipping is automated and reversible, you ship often — and small, reversible releases are the least risky kind.

The engines: GitHub Actions · GitLab CI · Jenkins

SaaS · YAML

GitHub Actions

CI baked into GitHub. Zero infrastructure, a huge marketplace of reusable actions, and config that lives next to the code (.github/workflows).

Best when your repo is on GitHub and you want green checks in minutes with nothing to operate.

SaaS / self-host

GitLab CI

One app for repo, CI, registry, and issues. Powerful pipelines (stages, needs, environments) with runners you can self-host for control over where builds run.

Best when you want an all-in-one DevOps platform, or must self-host the whole thing on-prem.

self-host · plugins

Jenkins

The veteran — open-source, runs anywhere, ~1,800 plugins, endlessly customizable. The flip side: you operate it — servers, agents, plugin upgrades and all.

Best when you have complex, legacy, or air-gapped pipelines that need total control and custom integrations.

Rule of thumb:already on GitHub/GitLab? Use their native CI — it's the least to run. Reach for Jenkinswhen you need self-hosted flexibility a SaaS runner can't give you.

05 · Build tools & dependencies 6 min

Reproducible builds:
same input, same bytes, every machine.

A build tool turns source into a runnable artifact; a package manager resolves the libraries you depend on. The whole game is reproducibility— your machine, a teammate's, and CI must produce the sameresult, or "works on my machine" comes back to haunt you.

Package manager — the tool that downloads and tracks the outside libraries your code relies on (and the libraries those rely on, and so on). You list what you want in a manifest (e.g. package.json); it then writes a lockfile that records exactly what you got — the precise version of every library, plus a checksum(a fingerprint that proves the downloaded file wasn't tampered with or corrupted).
your app lib A lib B lib C which version?

You install A and B; you also get C — a transitive dependency. The lockfile decides exactly which C.

Semantic versioning, briefly

MAJOR breakingMINOR featurePATCH fix
  • ^1.4.2means "1.x, anything ≥ 1.4.2" — you opt into future minors and patches.
  • The manifest states intent (^1.4.2); the lockfile records reality (1.6.0 + checksum).
  • Commit the lockfile. It's what makes an install today match an install in six months.

Pin it or it'll drift

Floating — non-deterministic
# no lockfile committed install # grabs newest matching ^1.x # CI installs 1.6.0 — passes. # a week later: 1.7.0 ships a regression. # nothing in your repo changed, build breaks. 👻
Locked — deterministic
# lockfile committed & respected install --frozen-lockfile # every machine + CI: exactly 1.6.0 # upgrades are an explicit, reviewable commit: update lib-c # diff shows the change

What modern build tools give you

caching

Don't redo work

Hash the inputs; reuse the output if nothing changed. The difference between a 20-second and a 20-minute build.

incremental

Rebuild only what moved

Track a dependency graph of tasks and recompute just the affected subtree — the heart of fast local loops.

hermetic

No hidden inputs

A "sealed" build that uses only the inputs you explicitly listed — never some random tool that happens to be installed on your laptop. That's what makes it come out the same everywhere.

bundling

Ship one artifact

Combine your code and its libraries into a single file to ship, dropping any code nothing actually uses (called tree-shaking). One output, runnable anywhere.

Package managers: npm · pnpm · yarn

These all do the same core job for the JavaScript world — install your libraries and write a lockfile. They mostly differ in speed and disk usage.

the default

npm

Comes bundled with Node.js, so it's already on every machine.

Pro Zero setup and universal — every tutorial and tool assumes it.

Con Installs are slower and copy each library fresh into every project, eating disk.

fast · lean

pnpm

Keeps one shared copy of each library and links projects to it instead of duplicating.

Pro Fast installs and big disk savings — shines in large multi-package repos.

Con Its stricter folder layout occasionally trips up older packages that assumed npm's.

feature-rich

yarn

The tool that popularised lockfiles and workspaces; modern versions are fast and configurable.

Pro Strong monorepo features and a mature, well-known workflow.

Con Its newer "Plug'n'Play" mode can need extra config and fights some tooling.

How to choose: default to npm— it's already there and fine for most projects. Switch to pnpm when install speed and disk start to hurt in a big repo. Use yarn if your team already has.

Build runners: Make vs Bazel

A build runner is the tool that actually runs your build steps in order (compile, test, package). The two ends of the spectrum:

simple · everywhere

Make

A decades-old tool that runs a list of commands you write down, skipping steps whose inputs haven't changed.

Pro Dead simple, installed almost everywhere, perfect as a one-command task runner.

Con No real caching or fingerprinting of inputs — it doesn't scale to large, multi-language builds.

scalable · strict

Bazel

Google's build system: sealed, cached, incremental builds designed for enormous codebases in many languages.

Pro Reproducible by design and very fast on huge repos thanks to shared caching.

Con Steep learning curve and heavy setup — real overkill for a small or single-language project.

How to choose: reach for Make(or your language's built-in tool) for small projects; only adopt Bazel when a giant, multi-language monorepo makes its complexity pay for itself.

06 · Debugging & profiling 5 min

Stop guessing.
Observe, then measure.

Debugging is about correctness — why is it wrong? Profiling is about performance— why is it slow? Both replace opinion with evidence. The senior move isn't knowing the answer; it's knowing how to find it fast.

Debugger — a tool that pauses a running program so you can inspect it from the inside: set breakpoints, step line by line, read every variable in scope, and walk the call stackthat led here. It answers "what is actually true right now?" — not what you assumed.
Print debugging — slow loop
total(items) { log("here 1") // edit, rerun, for (const i of items) { log("i =", i) // edit, rerun, sum += i.price // edit, rerun... } log("sum =", sum) // one variable at a time }
Breakpoint — whole state at once
total(items) { for (const i of items) { sum += i.price // ⏸ breakpoint here } return sum } // pause → inspect items, i, sum, the call // stack & every scope. step / continue. no rerun.
handleRequest (100%) parse (33%) render (61%) ← hot path fmt serialize (48%) deepClone (45%) ← fix this width = time spent · stack = who called whom

A flame graph: the widest bars are where time goes. deepCloneeats 45% — that's your one change.

Profile before you optimize

  • Measure first.Intuition about hot spots is wrong far more often than it's right. Let the profiler point.
  • CPU vs. memory.A CPU profile shows where time goes; a heap profile shows what's holding memory (and leaking).
  • Fix the widest bar.A 45% function fixed beats ten 1% micro-optimizations — and you'll know it worked because you re-measure.

Like a doctor ordering a scan before surgery — you don't cut where it doesn't hurt.

The everyday kit

debugger

Breakpoints & stepping

Pause, inspect, step, watch. Conditional and logpoint breakpoints catch the rare case without flooding output.

git bisect

Find the bad commit

Binary-search history: mark a good and bad commit and Git walks you to the exact one that broke it — in log₂(n) steps.

observability

Logs, metrics, traces

In production you can't pause the program with a debugger. Instead you lean on logs (what happened), metrics (numbers over time), and traces (the path a single request took) to figure out what went wrong.

Where you live all day: VS Code · JetBrains · Neovim

Your editor (or IDE— Integrated Development Environment, an editor with the debugger, search, and tools built in) is where most of this work happens. The three you'll meet most:

free · popular

VS Code

A free, lightweight editor from Microsoft with a huge library of add-ons for nearly every language.

Pro Easy to pick up, works well for almost anything, and the extension marketplace is enormous.

Con Loading lots of extensions can slow it down, and its deepest code smarts sometimes trail a full IDE.

powerful · paid

JetBrains

A family of full IDEs (IntelliJ, PyCharm, WebStorm…) with deep language understanding built in.

Pro Best-in-class refactoring, navigation, and an excellent debugger out of the box — no setup.

Con Mostly paid, and noticeably heavier on memory and start-up time.

fast · terminal

Neovim

A keyboard-driven editor that runs right in the terminal and is configured entirely by you.

Pro Extremely fast and lightweight, and runs anywhere — even over SSH on a remote server.

Con Steep learning curve, and you assemble your own setup from plugins.

How to choose: VS Code is the safe default for most people. Pick JetBrains when you want maximum built-in power for one main language, and Neovim if you live in the terminal and value raw speed.

07 · A healthy workflow & recap 2 min

One change, from idea to production —
the hard way vs the smooth way.

The slow team
1. edit straight on main 2. one giant "fixes" commit 3. "ran it locally, looks fine" 4. scp to prod from a laptop 5. it breaks; no idea which change 6. revert everything, lose a day
The fast team
1. short branch, atomic commits 2. PR early; CI runs on push 3. green checks + one review 4. merge → pipeline deploys 5. a regression? bisect finds it fast 6. redeploy last green tag, move on
1Shrink the feedback loop.Every tool here exists to tell you "does it still work?" sooner.
2Know Git's model. Snapshots in a DAG, branches are pointers — and the commands stop being scary.
3Small branches, small PRs. Integrate often; never rebase public history; protect main.
4Automate the boring, lock the variable. CI/CD on every push; commit the lockfile; reproducible builds.
5Measure, don't guess. Breakpoints over prints, profilers over intuition, bisect over blame.

Keep going

  • Pro Git— Chacon & Straub (free online; chapters 2–3 & 10 are the model)
  • Accelerate— Forsgren, Humble & Kim (the data behind fast teams)
  • The Pragmatic Programmer— Hunt & Thomas (tooling as craft)
  • git-scm.com& your debugger's docs — read the tool you use daily

One sentence to remember

"Make the loop short and the build boring — then speed is just the default."

— the whole talk, compressed

Knowledge check

Did it stick?

Five quick questions on Git's model, workflows, CI/CD, builds & debugging — instant feedback, no sign-in.

Rate this deck
be the first

Navigate with ← → or scroll · back to library