Thinking
How I reason about systems.
Decision making
Questions I ask. Answers I've arrived at.
How do you decide what to abstract?
Wait for three. Two instances of a pattern feel like coincidence. Three feel like a rule. Abstracting at two builds the wrong generalization — you're encoding what two things have in common, which is usually less than what you think. At three, you've seen enough variation to know what the abstraction actually needs to handle.
How do you evaluate a technical decision under time pressure?
I ask: what does this make harder to change? The reversibility of a decision matters more than its correctness in the short term. A reversible wrong decision costs one correction. An irreversible one can compound into a system property. Under time pressure, I prefer the option that preserves the most future optionality — even if it's slower now.
When do you push back on a product requirement?
When the requirement encodes a solution rather than a problem. 'We need a queue here' is a solution. 'We can't lose these requests' is a problem — and it might be solved by a queue, or by synchronous retries, or by idempotency. I push back to find the actual constraint, then design to that constraint rather than its proposed solution.
What's your relationship to complexity?
Adversarial. Complexity is debt in disguise. Every layer of indirection, every configuration option, every conditional branch is a cost that future engineers pay. My default is deletion and simplification. When I can't simplify, I make the complexity visible — documented, tested, isolated — so it can't spread.
How do you think about system failure?
As a specification. The way a system fails tells you what it believes about itself. A system that fails catastrophically believed it wouldn't fail. A system that fails gracefully was designed by someone who knew it would. I design failure paths as first-class features — explicit fallbacks, known degraded states, observable signals when things go wrong.
What does 'done' mean to you?
Observable, reversible, and understood. A feature is done when you can tell whether it's working without reading the code. It's done when you can roll it back without a crisis. It's done when a new engineer can read the system and understand why the decisions were made. 'Working' is necessary but not sufficient.
Working values
Legibility
Code is read 10× more than it's written. Optimize for the reader.
Explicit > Clever
Clever code is the kind that has to be explained. Don't write it.
Make the wrong thing hard
Good interfaces make correct usage obvious and incorrect usage difficult.
Automate what you've done twice
The second time you do something manually, you've made a decision.
Monitor what matters
Exceptions are not the only failures. Outcomes must be observed too.
Design for the failure path
The happy path is easy. What happens when everything goes wrong?
Technical philosophy
I optimize for the system after me, not the system right now.
Every system I build will be maintained by someone who wasn't in the room when the decisions were made. That person is my primary design consideration. If they can't understand why a decision was made, the system is incomplete — regardless of how well it runs today.
On failure
I've shipped systems that had production incidents, race conditions, wrong abstractions, and monitoring gaps. I document these as clearly as the successes — because the failure cases are where the actual learning lives.
Any engineer who claims a flawless record either hasn't built anything important, or isn't being honest about what they built.