I don’t meet this bar every day. I wrote it down because writing it down is how I hold myself to it. This series lays out the practices I try to work by as an engineer: patterns and anti-patterns across data modeling, delivery, code quality, code structure, architecture, and performance.


1. Make impossible states unrepresentable — and parse, don’t validate

This is the single highest-leverage type-level habit. Every time you reach for boolean, number, or string to represent a domain concept, ask: can this type contain values that are invalid in the domain?

Senior engineers amplify through team success, not individual output. The leverage formula applies directly to career development: the highest-return activities multiply team effectiveness, they don’t just add to your own.

The key insight: the most visible, most impactful technical contributions at a company level are almost never feature work. They’re the infrastructure improvements that make feature work easier, safer, or faster for everyone else.

Finding the opportunity

Diagnose before you propose. Ask:

This tension runs underneath most of the practices in this series. Making it explicit helps you pick the right mode at each stage of the work.

Top-down starts from the highest-level model, requirements, domain, architecture, and works toward implementation. It asks: what are we building? What does success look like? What structure should this system have?

Bottom-up starts from concrete, working pieces and builds upward. It asks: what actually works? What do the real constraints look like? What can I demonstrate right now?

Code review has its highest leverage at the design level, not the style level. The questions that matter most are structural.

Are invariants maintained?

What does the system depend on being true, and does this change preserve that? This question catches whole classes of bugs a line-by-line read misses entirely.

// The invariant: a shipped order always has a shippedAt timestamp.
// This PR adds a new status without touching that guarantee.
async function markShipped(orderId: OrderId, carrier: string) {
  await db.orders.update(orderId, { status: 'shipped', carrier })
  // shippedAt never gets set — nothing enforces it, nothing warns you
}

Line by line, this reads fine: it updates a status, it takes a carrier, it compiles. The bug only shows up when you ask the invariant question directly — “shipped orders always have a shippedAt” was true before this PR and isn’t after it. A line-by-line read has no way to know that invariant exists unless someone states it and checks the diff against it.

The hardest case: no tests, no architecture decision records, no diagrams. The tactics shift from consuming documentation to producing it as a byproduct of exploring.

Here’s what that actually looks like, walked through on one example: you’ve just joined a team maintaining Fulfill, an internal order-fulfillment service. Nobody’s written a README beyond npm install && npm start. You have a week before you’re expected to ship anything.

Start outside the code

Use the product as a user before you read a line of source. Check the error monitoring dashboards, Sentry, Datadog, early: they show where the system actually breaks, which tells you more than reading happy-path code ever will. The production error log is an honest account of what the system struggles with. The codebase only shows you what the engineers thought would happen.

The layers of feedback

The practices in this series keep circling back to feedback, for a reason: the speed of your feedback loop sets the speed of your learning, and the speed of your learning sets almost everything else about how effective you are.

Feedback loops come in layers, and the right fix is different at each one.

  1. Editor feedback. TypeScript errors, ESLint, inline warnings. Zero latency, the tightest loop that exists. Investing in it, a stricter tsconfig, more complete type coverage, pays back with compounding interest.
  2. Unit tests in watch mode. Sub-second, on the file you just changed. If your unit tests don’t run automatically on save, you’re leaving real developer experience on the table.
  3. Hot module replacement. UI changes with no full reload. Trivial to turn on in a modern toolchain, and often just left off.
  4. Integration tests. Should run in seconds, not minutes. A slow test doesn’t get run. That’s not a discipline problem, it’s an incentives problem: a four-minute suite gives engineers a reason to skip it before pushing.
  5. CI. Parallelize aggressively. A twenty-minute pipeline is a morale tax paid on every single PR, and the cost isn’t just the twenty minutes, it’s the context switch of sitting there waiting.

The heuristic: if running tests takes willpower, the tests are too slow. Running them should be the path of least resistance, not a deliberate act you have to talk yourself into.

Measure first, know your bottlenecks

Performance intuition is wrong more often than it’s right. The thing you’re certain is the bottleneck almost never turns out to be. Profile first and let the data decide where effort goes. There’s no reliable shortcut around this.

Most web application code spends its time waiting, not computing. A request handler calls the database, waits, calls another service, waits, maybe hits a cache, waits again — and the CPU sits idle through nearly all of it. That’s why the common bottleneck categories skew so heavily toward I/O, roughly in this order of frequency:

Strangler fig, not the big bang rewrite

The big bang rewrite has a seductive pitch: the existing system is a mess, the new one will be clean, we’ll migrate users when it’s ready. What actually happens: the rewrite starts with “just the core functionality” and spends years discovering that the old system’s quirks were load-bearing. Edge cases nobody documented, accumulated over years. Integrations with external systems nobody wrote down. Data migration complexity nobody scoped. Behavioral requirements nobody stated because everyone assumed they were obvious.

Characterization tests, one dimension at a time

The term “characterization tests” comes from Michael Feathers’ Working Effectively with Legacy Code.

Refactoring without tests isn’t refactoring. It’s rewriting with optimism. “Looks equivalent” is not “is equivalent.” The subtle behavioral change hiding in an untested path finds its way to production six months later, git blame points at a commit that moved some files around, and nobody can tell what the intended behavior even was.

Vertical slices, not horizontal layers

Here’s a failure mode I’ve watched play out on almost every large feature: the team builds all of one layer before starting the next. Every database table first, then every endpoint, then every screen. Progress looks real — the database layer is “done,” the API is 70% there — right up until week six, when integration starts. The data model was built on assumptions that turned out wrong. The API contract doesn’t match what the UI actually needs. Two weeks of rework follows.