skip to content

An entry carries a lifetime, and a caller now replaces its whole value with an ordinary write — what happens to the deadline?

level: middleimportance: must knowfreq 60%

answer

  1. deadline sits beside the value
  2. a write can touch both
  3. postures differ between stores
  4. no error, only a memory graph
  5. send the lifetime in the same call

basics

~20 s

Stores differ: an ordinary full replacement clears the entry's lifetime on some and leaves the deadline standing on others. A design assuming either silently produces permanent entries nothing reclaims, so pass the lifetime in the same write.

solid answer

~50 s

The deadline is state attached to the entry alongside its value, so a write aimed at the value can also touch it — and stores in this class made opposite decisions about that. On some, a plain full replacement resets the lifetime state, and because the refreshing caller passed no lifetime the entry becomes **a permanent entry**: still served, never reclaimed. On others the original deadline survives the new value, so the refreshed entry disappears at the original instant rather than a new one. Some offer both, through **a lifetime-preserving write** that replaces the value and leaves the deadline standing. Because no posture is universal, the safe shape is to state the lifetime in the same call that carries the value, and to put that decision in one shared place rather than at every call site.

code

pseudocode · 13 lines
pseudocode
// caller one, at minute 0 - creates the entry and gives it a lifetime
write(entry: "session:9f2", value: first, lifetime: 30m)

// caller two, at minute 12 - refreshes the value, says nothing about time
write(entry: "session:9f2", value: second)

// at minute 13 - what is left?
readRemainingLifetime(entry: "session:9f2")
//   store whose plain write resets the lifetime state -> "no lifetime attached"
//   store whose plain write leaves the deadline standing -> about 17m

// safe on either store: say what you mean in the call that carries the value
write(entry: "session:9f2", value: second, lifetime: 30m)

go deeper

for a junior

Recall that a lifetime is attached to an entry and enforced by the store, not by application code, and that it is separate from the value the entry holds. Writing a new value is therefore not automatically the same thing as leaving the timing alone.

for a middle

Explain that the deadline is state kept beside the value, that stores took opposite decisions about whether a plain replacement touches it, and that the fix is to carry the lifetime in the same operation as the value rather than in a second call.

for a senior

Demonstrate that you have seen the silent version: a tier growing steadily with no failed operation anywhere, traced to one refresh path. Talk about where the posture is encoded in the client code and how a refresh path gets reviewed.

for a principal

Frame it as a contract rather than a bug. Which writes into this tier are allowed to omit a lifetime at all, who enforces that, and what the tier's growth budget is when a whole team's refresh paths are outside your review.

## The deadline is state beside the value, not inside it A **lifetime** is a duration the application hands the store when it writes an entry. The store converts it into a **deadline** — an instant after which it stops serving that entry — and enforces it itself, without application code doing anything. The structural fact that makes this question interesting is that an entry has three separable things: the key that names it, the value, and possibly a deadline. The deadline sits *beside* the value, as state the store keeps about the entry. A write aimed at the value can therefore also touch it, and whether it does is a decision each store in this class made independently. ## Three postures, and why you cannot assume one | what the store does on a plain full replacement | consequence for the entry | what the design must do | |---|---|---| | resets the lifetime state to whatever the new write carried | a write carrying no lifetime leaves a permanent entry | always send a lifetime with the value | | leaves the existing deadline standing | the fresh value ends at the *original* instant | expect removal earlier than the refresh implies | | offers both a plain and a lifetime-preserving form | behaviour is whatever the call site chose | choose explicitly, and review it | There is a fourth shape worth knowing: stores where a lifetime can only be given at the moment of the write and never attached afterwards. On those, a refresh that forgot the lifetime cannot be repaired in place at all — the entry has to be written again, correctly. ## The immortal entry, and why nobody reports it The failure this leaf is named for has no error in it. The creating code knew about the lifetime; the refreshing code knew only about the value; every single operation returned success. Nothing is logged, and no announcement fires, because **an expiry announcement** is emitted when an entry is actually reclaimed — and this one never is. The only symptom is arithmetic: entry count and resident size climb slowly while traffic does not, and the tier eventually meets its ceiling holding data whose logical life ended weeks earlier. The shape recurs because creation and refresh are usually written by different people at different times: - a session is created by the sign-in path, which knows the session length, and refreshed by a handler that only updates a field; - a deduplication or staging entry is created with a deadline and then rewritten by a retry path; - a value is repaired by an operator or a backfill job issuing a plain write by hand. ## Reading the state back Asking the store for the **remaining lifetime** of an entry returns one of three kinds of answer: a duration, a distinct answer meaning *no lifetime is attached to this entry*, and a distinct answer meaning *no such entry exists*. Conflating the last two is the classic diagnostic error — one of them is an immortal entry that will hold memory forever, the other is an entry that is already gone. Any audit or alert built on this read has to separate them explicitly. ## Making a refresh path safe 1. **Decide what a refresh means.** Does writing a new value restart the clock, or should the entry still end when it always would have? Both are legitimate; only the unexamined case is a bug. 2. **If it restarts:** pass the lifetime in the same operation that carries the value. One call, no window, and the posture of the store stops mattering. 3. **If it should inherit:** use a lifetime-preserving write where the store has one. Where it does not, the fallback is to read the remaining lifetime and write it back with the value — and you must say out loud that this is a read-then-write with a gap in the middle, and that each round of it nudges the deadline slightly outward. 4. **Never split the write from the lifetime** into two calls without accounting for the gap: a crash between them leaves exactly the permanent entry you were trying to avoid, and a reader in the gap sees an entry no deadline governs. 5. **Put it behind one shared helper.** The store's posture is a property of the tier, so it should be encoded once in the client code the whole service uses, not remembered at each of forty call sites. ## What to say in an interview Name the operation class you mean — this is full replacement of the value, not a change to part of it — then state that the answer varies by store, describe both postures, and go straight to the silent-growth symptom. A candidate who answers "the lifetime is cleared" or "the lifetime is kept" as a flat rule is right about one store and wrong about another, and has missed that the variation is the whole content of the question.

  • The store has no lifetime-preserving write and you must keep the original deadline through a value refresh. What do you actually do?
    Read the remaining lifetime, then write the value together with that duration. Be explicit that this is a read followed by a write with a gap between them, so a concurrent refresh can interleave, and that each round rewrites a countdown from slightly later, so the deadline drifts outward. Where drift is unacceptable, hold the intended end instant inside the value and rebuild the duration from it on every write.
  • Why does an expiry announcement never warn you about this?
    Anything the store emits is emitted when an entry is actually reclaimed, and an entry that lost its lifetime is never reclaimed — so there is nothing to emit. Announcements are also best-effort, delivered to whoever happens to be listening, so they are the wrong foundation for an audit even in the cases where they do fire. The symptom is resident size and entry count, not an event.
  • A backfill job rewrote ten thousand entries and dropped their deadlines. Can you repair them in place?
    On stores that let a lifetime be attached after the write, yes: walk the affected names and attach one, deciding per entry what remains of its intended life. On stores that accept a lifetime only at write time, no — each entry has to be written again with its value and a lifetime together, which means the job needs the values, not just the names.

A deadline is like a use-by date on the shelf label rather than printed on the carton. Restock the shelf and, depending on the shop's rule, the old label either stays and now governs fresh stock, or goes in the bin with the old — and a shelf whose label went in the bin is never checked again.

saying these in an interview costs you the question

  • Says a plain write always clears the lifetime, as a universal rule
  • Says the deadline always survives a write, as a universal rule
  • Expects an error or a warning when a lifetime is dropped
  • Treats no lifetime attached and no such entry as the same answer
  • Assumes the deadline is stored inside the value and travels with it
  • Plans to write the value first and attach the lifetime afterwards