Pure strategy
The goal of a build tool is to take a bunch of disparate inputs and compile them into an output:
$ rustc main.rs
flowchart LR
subgraph inputs [" "]
A[main.rs]
end
A --> F[rustc]
F --> O[main]
A pure function is one that, given the exact same input, always returns the exact same output with no side effects. Every single time you call it, you get the exact same value, such that replacing the function call with its return value does not change the behavior of the program.
fn concat(a: &str, b: &str) -> String {
format!("{}{}", a, b)
}
fn main() {
let result = concat("Hello, ", "world!");
println!("{}", result);
}
flowchart LR
subgraph inputs [" "]
h["Hello, "]
w["world!"]
end
h --> concat
w --> concat
concat --> r["Hello, world!"]
Pure functions are a central tenet of the functional programming wing of the engineering zeitgeist. Pure functions carry many benefits: they're simple to debug and refactor; they tend to be more parallelizable and performant; they are reasonable and predictable. Many meaningful advancements in software engineering can be traced back to ideas that have been shouted from the rooftops by the Haskell wing for a while: React, gofmt, Typescript, MapReduce, et cetera.
Nix is a build tool and package manager whose I'm about to try using Nix as a metaphor for strategy, which I suspect will earn me the scorn of Nix enthusiasts and the befuddlement of MBAs alike. core insight is that these two problem spaces are very similar. Developer environments are annoying because of global state and side-effects which pollute the set of inputs from which we compile a program. Nix's approach is to encapsulate all inputs into a single, immutable, and reproducible environment, and then drive all processes into pure functions, recognizing that there are more inputs to successfully compiling a Rust file than just its contents: the type of CPU matters, the version of the compiler, and so on:
flowchart LR
subgraph inputs [" "]
A2[main.rs v1]
B2[rustc 1.75]
C2[x86_64-linux]
D2[libc 2.38]
end
A2 --> F2[nix build]
B2 --> F2
C2 --> F2
D2 --> F2
F2 --> O2[binary hash:a1b2c3]
style O2 fill:#d4edda
Nix is definitionally doctrinaire: if a build isn't deterministic it's because either the input space is not fully defined or the function is impure, at which point the only reasonable course of action is to either expand the input space or make the function pure.
If you've read my writing for a while, you may know that I loathe in equal amounts wasted work and simulacric work. One of my historical antipathies towards the concept of strategizing is that it often feels like the ritual of a good idea rather than a good idea itself. In many past lives, I've sat down for an annual planning meeting — spreadsheets and projections and Jira printouts — only to arrive at the exact same conclusion we've had twelve months prior. It's an unnecessary build step; none of the inputs had changed, and none of the outputs changed.
Which is not to say they never do. When I think back to the past few years of Buttondown and where we've shifted course, every pivot was legitimately due to the input space changing. Back in 2020, the paid subscriptions boom — once dreamily referred to as the "creator economy" — was a completely new development that I hadn't really considered or internalized. So too with the rise of LLM-based programming tools, though I'm still waiting on a final compilation.
flowchart TD
subgraph inputs ["Inputs"]
M["Market conditions
(creator economy boom)"]
T["Technology landscape
(LLM tooling)"]
C["Competitive dynamics
(new entrants)"]
R["Resources & constraints
(team size, runway)"]
D["Durable assumptions
(email as protocol)"]
end
M --> S["Strategy
(Buttondown's roadmap)"]
T --> S
C --> S
R --> S
D --> S
But often the things that make me want to take a step back and reevaluate haven't actually changed since the last time I did so. This, I suspect, is reflexively true: a good strategy is one whose inputs are not invalidated at a particularly flaky cadence. Buttondown's core thesis rests on the durability of email and SMTP as a communication platform, and vagaries in the industry aside, that does not purport to change in a meaningful way.
It is tempting to treat conviction and adaptability as inherently antithetical. The founder who does not want to pay attention to the ground crumbling underneath their feet talks about conviction; the founder who has no real thesis about the world and merely wants to outplay the field and stay alive talks about adaptability. The uncomfortable reality as well as the banal one is that you need conviction where conviction is warranted. You need endurance where endurance is warranted and agility where agility is demanded. Very few meaningful businesses have random walked themselves to durable success. Very few meaningful businesses have emerged from hermitage after ten years of heads-down work and not found the world has passed them by. Decide what really matters and hold them to you like rosary beads. And if you don't know what's sacred to your organization, that is the thing worth evaluating, not your Q2 roadmap.
(Thank you to Myles for the conversation that inspired this essay.)