skip to content

Your booking service derives the charterer at the edge — how do you make that value reach every layer, including cache keys and log fields?

level: middleimportance: must knowfreq 58%

answer

  1. bind once, read everywhere
  2. immutable, cleared, never inherited
  3. absent is not the same as all
  4. the charterer is part of every derived key
  5. pooled threads keep what you leave behind

basics

~10 s

Bind the derived charterer once to a request-scoped context any layer can read without being passed it, immutable for the request, cleared when it ends, and required in every cache key and log field.

solid answer

~50 s

Threading the charterer through every method signature fails the moment one function forgets, and the forgetting is invisible. Instead bind it once, immediately after derivation, into a request-scoped context: a slot keyed to the current unit of work that any layer reads directly. That context must guarantee four things — it is written once and immutable for the request, it is readable without being passed as an argument, it is **cleared** when the request ends so a pooled thread cannot serve the next caller with it, and *absent* is a distinct state from *any charterer*. Then make every derived key carry it: the charterer belongs in the cache key, in the log fields, in the metric labels. A memoized result keyed on a vessel id alone is the classic cross-charterer read, and no amount of correct `WHERE` clauses downstream will catch it.

code

pseudocode · 10 lines
pseudocode
# WRONG — the expensive result is keyed on the vessel only
function bookedTeu(vesselId):
    return cache.getOrCompute("teu:" + vesselId,
                              () -> query(vesselId))   # rows the caller may see

# RIGHT — the charterer is a component of the key, taken from the binding
function bookedTeu(vesselId):
    charterer = context.charterer()      # raises if nothing is bound
    return cache.getOrCompute("teu:" + charterer + ":" + vesselId,
                              () -> query(vesselId, charterer))

go deeper

for a junior

Know that the derived charterer is stored once per request in a place every layer can read, rather than passed down by hand, and that it has to be part of any cache key you build.

for a middle

Design the binding: written once, immutable for the request, readable without being passed, cleared at the end, and with absent as a distinct state. Explain why explicit threading fails silently where the context read fails loudly.

for a senior

Bring the production failure. The memoized result keyed without the charterer, the pooled thread that served the next caller the previous binding, the asynchronous hand-off that ran unbound because the runtime did not copy the value.

for a principal

Decide the invariant and the enforcement. One binding point, a key-building helper that refuses when unbound, a charterer field on every event — so correctness is a property of the plumbing and not of what each developer remembered.

You have derived exactly one charterer for this request. Now roughly forty functions across five layers need it, and none of them should have to ask for it. The design question is where that value lives between derivation and use. ## Why not a parameter on every signature Passing the charterer explicitly is honest and it is what a reviewer can see. It also fails in a specific, repeatable way: it only works if **every** function on **every** path takes it and passes it on. Add a helper that does not, or call an existing utility written before the service became multi-charterer, and you have a code path with no charterer — and nothing about it looks wrong. Explicit threading makes the value visible where it is passed and silently absent where it is not, and the absent case is the dangerous one. The practical answer is a request-scoped context: one write near the edge, many reads everywhere else, and one rule that the data-access layer will not run a charterer-scoped query without it. ## What the binding must guarantee - **Written once, immutable for the request.** If any layer can overwrite the charterer, then a bug or an injected value can move a request between charterers halfway through. Re-binding belongs to a different unit of work, not to this one. - **Readable without being passed.** That is the entire point; a layer that must be handed the value is a layer that can be written without it. - **Cleared at the end of the request.** Server threads are pooled and reused. A binding left behind is served to the next caller, which is the same bug as reading from the body except it now depends on traffic timing and will not reproduce locally. - **Absent is not `all`.** The context must be able to say *nothing is bound here* as a distinct state, and the code that consumes it must treat that as a refusal. - **Carried deliberately, not inherited by luck.** A value bound to the current unit of work does not automatically follow work you hand to another thread or schedule for later. Any hop that continues the request elsewhere must capture the binding at the hand-off and install it on the other side. ## Where the value has to reappear | Hop | What must carry the charterer | What happens if it does not | |---|---|---| | A query in the data-access layer | The predicate, added centrally rather than per call site | The query returns every charterer's rows | | Work handed to a worker thread | An explicit capture at hand-off and an install on the borrowed thread | The work runs unbound, or worse, inherits whatever the borrowed thread last held | | A memoized or shared cache entry | The cache key itself | One charterer is served another's cached result | | A response stored by a shared HTTP cache | `Vary` naming the request header the charterer was derived from | A shared cache keyed only on the URL serves one charterer's response to the next caller | | A log line or a metric sample | A field or label on every event | A cross-charterer incident cannot be reconstructed afterwards, and per-charterer load cannot be attributed | | A call out to another service | That service deriving it from its own verified credential, not trusting a field a peer sent | The callee's isolation is only as strong as its least careful caller | ## The cache key is where this actually breaks Every service eventually memoizes something expensive. `bookedTeuFor(vesselId)` is a perfect candidate: slow, hit constantly, and apparently charterer-independent — until you notice it is computed from rows the caller was allowed to see. Key it on the vessel id alone and the first charterer to ask populates the entry, and every other charterer on that vessel is served it. The database query underneath was *correct*; the filter was there and it ran. The leak happened above it, in a key that forgot the dimension the whole system is partitioned on. The rule that survives review is mechanical: **the charterer is a component of every derived key, not a consideration when composing one.** A helper that builds keys and takes the charterer from the context, refusing when nothing is bound, is stronger than a convention that developers remember to include it. ## Filtering is not the last line Application-side filtering is what your code owns and this is how you make it consistent. It is worth saying plainly that it is not, on its own, a structural guarantee: a backstop inside the data store makes a missed filter unexploitable rather than merely unlikely, and that argument belongs to the data store's own design rather than to the request path. What this leaf owns is narrower and still load-bearing — that there is exactly one value, bound once, reachable everywhere, and absent nowhere by accident.

  • A handler hands a slice of work to a thread from a pool. What exactly has to happen for the charterer to be correct on the other side?
    Capture the binding at the hand-off and install it on the borrowed thread before the work runs, then clear it when the work finishes. Do not rely on inheritance: whether a runtime copies a request-scoped value across a hand-off varies, and when it does not, the borrowed thread either runs unbound or, worse, still holds the previous task's charterer.
  • Why does the charterer belong in the log fields when the response already filtered correctly?
    Because the value you need at three in the morning is the one the request actually ran with, not the one you believe it ran with. A charterer field on every event is how you reconstruct a suspected cross-charterer read, attribute load to the charterer that caused it, and prove a background path ran bound. Without it the incident is unanswerable from the logs alone.
  • Your context can hold `null`. Is that enough to express `nothing is bound`?
    Only if every consumer treats it as a refusal rather than as a wildcard, and that is the part that goes wrong. A `null` charterer concatenated into a cache key silently makes a shared entry, and interpolated into a filter it often removes the constraint. Make the read itself raise when unbound, so the absent case cannot be spelled the same way as the all case.

A shipment reference is stamped once when the file is opened and every document in it carries the stamp. Nobody at any later desk is asked to remember which shipment they are working on.

saying these in an interview costs you the question

  • Threads the charterer through every method signature and calls that sufficient
  • Assumes a request-scoped value automatically follows work handed to another thread
  • Leaves the binding in place when the request ends on a pooled thread
  • Keys a memoized result on the vessel id alone because the query underneath filters
  • Treats an unbound charterer as a wildcard rather than a refusal
  • Lets any layer overwrite the bound charterer mid-request