Personalization
How fast should a user profile forget?
Exponential decay on a user profile, a half-life you can drag, and the moment where four days of debugging Kubernetes buries a year of interest in retrieval.
Here is a situation every personalized product hits.
A user has spent a year reading about information retrieval. This week their cluster caught fire and they have read eleven things about Kubernetes in four days. What should the feed show them tomorrow morning?
Show retrieval and you are ignoring everything they have done recently. Show Kubernetes and you have thrown away a year of evidence because of one bad week. Both answers are defensible, and the parameter that decides between them is usually a magic number somebody typed in eighteen months ago.
Decay, and the one parameter worth naming
The standard move is to weight each interaction by its age:
is the half-life: the number of days after which an interaction counts for half of what it did when it happened. The profile is then the weighted sum of everything the user has touched, one term per interaction:
Half-life is worth insisting on as the parameterisation rather than the rate . They are the same function, but you can hold an argument about a half-life. “Should a click still count for half as much three weeks later?” is a question a product manager can answer. “Should be ?” is not.
Drag it
Below is a synthetic history: fifty-seven interactions over a hundred and twenty days, mostly retrieval and recommenders, plus that Kubernetes burst in the last four days. Bar height is how many things happened; opacity is how much they still count under the half-life you pick.
Read the two failure modes off the slider
Drag the half-life down to about three days. The whole left side of the timeline fades out. Kubernetes takes over the profile and the feed becomes entirely about container orchestration. A model this forgetful chases whatever happened most recently — and every accidental click, every session where someone was researching a gift for somebody else, immediately becomes their identity.
Drag it up to a hundred and twenty. Now the burst is barely visible. The profile is a near-uniform summary of the year, and the feed is completely unmoved by the fact that this person’s most urgent current problem is Kubernetes. This is the model that keeps recommending running shoes for six months after you bought running shoes.
Watch the 90% of the weight readout as you drag. That is the honest answer to “how far back does this profile actually look” — and it is a much smaller number than most people assume. Even at a sixty-day half-life, the effective window is considerably shorter than the history you are storing.
Why one number cannot win
The two failure modes are not a tuning problem with a sweet spot in the middle. They are two different questions:
- What is this person like? — stable, slow-moving, worth being confident about.
- What is this person doing right now? — volatile, urgent, worth over-reacting to.
A single half-life has to answer both, so it answers neither well. Switch the widget to Short + long memory and you get the standard fix: maintain two profiles with different half-lives — three days and ninety — and blend them.
Now is a product decision rather than a statistical one, and it can vary by context. High on a search results page, where intent is obvious and immediate. Low on a weekly digest email, where the whole point is to reflect settled taste. Some systems set dynamically from session activity: the more someone has done in this session, the more the short profile is trusted.
Three things I would do in production
Decay per topic, not per user. A half-life that suits news does not suit whatever someone’s long-term professional interests are. In the widget both Kubernetes and retrieval decay at the same rate, which is why the burst can dominate so completely — in reality you would want the burst-prone topic to decay faster than the stable one.
Prefer half-life to “the last N events”. A sliding window of fifty events means something completely different for a daily user and a monthly one, and it has a cliff: event fifty-one falls off entirely while event fifty counts in full. Exponential decay is continuous, and it is one multiply — you can even update the profile incrementally, scaling the old vector by before adding the new interaction, without ever replaying the history.
Be careful evaluating this offline. A short half-life looks excellent on a held-out set built from the immediate future, because recent behavior predicts imminent behavior almost tautologically. Evaluate at the horizon you actually care about. If you want the feed to be good next month, hold out next month.
The part that is not a parameter
There is a version of this problem that no half-life solves: sometimes recent behavior is not the user at all. A shared account. A gift. A rabbit hole they regret. Decay treats all evidence as equally trustworthy and merely discounts it by age.
The systems that feel genuinely well-tuned tend to have an escape hatch that has nothing to do with decay — a way for someone to say not this, not any more. It is worth more than another decimal place on the half-life, and it is almost always the last thing built.