A short, honest description of my working style and defaults — useful if you're deciding whether I'd fit your team.
Scoping
I start by writing the smallest version of the change that a user would notice, then list what it deliberately excludes. That list is usually more valuable than the estimate: it makes the trade-offs reviewable before any code exists.
Write the problem statement
One paragraph, no solution language. If I can't write it, I don't understand the request yet.
Sketch the seams
Which modules change, which stay frozen, and what the rollback looks like.
Ship a thin slice
Get one real path working end to end behind a flag before widening it.
Write the doc page
Architecture and trade-offs, while the reasoning is still fresh.
Code review
- I review for blast radius before style. What breaks if this is wrong at 3 a.m.?
- I leave one-line comments with a location, a problem, and a suggested fix — no essays.
- Nits get labeled as nits so nobody guesses whether they're blocking.
Testing
I aim for a small number of tests that would actually have caught real incidents, rather than coverage percentage. In practice:
// Property under test: total never drifts from the sum of its line items,
// regardless of discount ordering.
test("discount ordering does not change the total", () => {
const cart = buildCart([item(1200), item(499)]);
expect(applyDiscounts(cart, [percentOff(10), flatOff(200)]).total).toBe(
applyDiscounts(cart, [flatOff(200), percentOff(10)]).total,
);
});Warning
I'm skeptical of tests that assert implementation details. They fail during refactors and pass during outages, which is exactly backwards.
Working with others
Written first, synchronous second. I'd rather post a short design note and collect comments asynchronously than hold a meeting to arrive at the same place. When something is ambiguous, I state the assumption I'm proceeding under instead of blocking.
Toolchain
Defaults, not dogma. Each of these is what I pick when there's no reason to pick something else.
Languages
| Language | Where I use it | Why |
|---|---|---|
| TypeScript | Web apps, APIs, tooling | The type system pays for itself at module boundaries |
| Python | Data pipelines, scripting | Ecosystem for anything data-shaped |
| SQL | Modeling, analysis | The most durable skill on this list |
| Go | Small services, CLIs | Single binary, predictable performance |
Web
Server components keep data access on the server by default, which removes a whole category of over-fetching bugs.
Styling stays next to markup and the design tokens live in one file. Fast to change, easy to review.
One schema validates the boundary and produces the type. No drift between the two.
Unit tests for logic, a handful of end-to-end tests for the paths that earn money.
Data and infrastructure
First choice for almost everything, including queues and vectors until scale proves otherwise.
Version-controlled transformations with tests and lineage.
Infrastructure as reviewable diffs rather than console clicks.
One instrumentation layer, swappable backends.
Local setup
# pnpm everywhere, no exceptions — one lockfile format across projects
pnpm install
pnpm dev
# Postgres per project, in a container, seeded from a checked-in fixture
docker compose up -d db
pnpm db:seedNote
I use pnpm rather than npm or yarn: the content-addressed store makes installs fast, and strict resolution catches accidental use of transitive dependencies before it becomes a production surprise.
Things I avoid by default
- A separate vector database before Postgres and pgvector have actually become a bottleneck.
- Microservices for a team small enough to fit around one table.
- Custom CI runners before the hosted ones are demonstrably too slow.
Each is a reasonable choice under real pressure. None is a reasonable starting point, because they trade a problem you have for operational complexity you'll have forever.