Value Functions
Let’s assume you’re an engineer at a company with a good mission, and a good general product direction for making progress on that mission (big assumptions but I’m just considering them out of scope).
From here, how do you decide what to work on? There’s your main 2-month project, random bug fixes, little side quests you happened to notice, small features your users want, potential performance improvements, code review, architecture review, infrastructure to make code easier to write, cleaning up unused columns, etc.
In hindsight, I did a pretty bad job answering this question for a lot of my career (although to be fair to myself, most people around me weren’t any good at it either). We mostly made our decisions based on pattern matching (“We take up to a day to do code review so I’m going to do that”). Even worse, the patterns we were following were usually rooted in (bad) incentive structures (”I need to build visible features to get promoted”) or what feels good (”adding incremental features feels like progress, stopping to build infra doesn’t”).
But it’s a bit hard to say what the alternative even is. You make some decisions and end up with something built, so how do you even reason about whether making different decisions would have been better?
We need some kind of framework for evaluating results. This is way too imprecise a space for any kind of rigorous framework, so a good framework is a rough value function. We’re not going to be able to give ourselves actual scores, but we can compare courses of action and see when one is clearly better than the other.
The value function I pretty much always use is global time-weighted value delivered to users.
Broken down a bit:
Global: we are all trying to maximize the company’s value, not maximize our own contribution to the company’s value.
Time-weighted: Value delivered 1 year from now is worth X% of value delivered now (maybe 20%? but will vary based on industry, competition, etc). You can imagine a graph of the value provided to users (likely a monotonically increasing step function), and another graph of y=0.2^t, and our goal is to maximize the area under the curve of the product of these two graphs.
Value: We don’t actually define what value to users is. That’s a harder question to answer, but even without an answer you can get pretty far.
Delivered: work in progress counts for nothing, and it’s actually very negative given the brainspace it occupies
to Users: Your users are the only ones that matter at the end of the day.
This definition sounds a bit obvious, even tautological (”value is delivering value”), but it gives us some useful axioms:
Undelivered value doesn’t count for anything
Value now is better than value later
Value later still counts. This one’s a bit fuzzy but if something you build pays off N times over (N=5 or so) in future value over the next year you’re probably coming out positive.
Value that doesn’t go directly to users only matters as far as it indirectly goes to users
Other people’s contributions count just as much as your own
And I’ve seen a lot of common practices that violate these pretty blatantly
Interleaving work on two tasks, instead of completing one then the other.
Waiting any amount of time to do code review.
Building 20+ of something without serious efforts to make each one take less work.
Doing some recurring manual task instead of doing some upfront work to automate it.
Under-investing in clean interfaces (letting N engineers pay a small amount of time so you can avoid paying a medium amount of time).
Over-investing in clean implementations for something no one will touch for a long time.
(You can construct versions of all of these that make sense, like a code review request during something urgent, but I’m talking about the unremarkable cases of them.)
To be clear, my proposed value function definitely isn’t right for everyone. You might need yours to consider revenue, tiers of users, or upcoming fundraising. But my main point is that you need to have something. Without a value function, there’s not much you can do other than pattern match, and next thing you know you have absurd standard practices like 24 hour code review turnaround times, and you have no way to reason about whether that’s bad. When you have a value function, you can iterate. You can try something, evaluate its performance under your function, and adjust. Instead of taking random steps in random directions, you’re stepping toward greater and greater value over time.
P.S. Thank you to my old manager Sasha Vladimirov for introducing a version of this idea to our team back at Robinhood.