A service makes several calls to a volatile tier whose cost grows with stored data; which bounded forms do you require, and what does each cost?
answer
- bound what a call may return
- range, cursor, cap, precompute
- every bound has a price
- refuse one caller or delay all
- cost must not follow stored data
basics
~20 sRequire that no call's cost be decided by stored data: ask for a bounded range, walk incrementally with a cursor, cap what one request may return, or precompute the answer into its own entry. Each bound costs trips, freshness or a refusal.
solid answer
~50 sThe rule to write down is that **a call's cost must be decided by the request, not by what happens to be stored**. Four bounded forms deliver it. Ask for a **range or slice** instead of everything — cheap, but the caller must now decide the bound and may need several trips. Walk **incrementally with a cursor**, which bounds each call but multiplies round trips and gives a view that can shift while you walk. **Cap** what one request may return and fail loudly past it — that converts "everyone is slow" into "one caller is refused", usually the trade you want, but somebody now owns the limit. Or **remove the growth**: maintain the derived answer as its own entry on write so the read is constant, at the cost of a second thing that can drift. Which of these exist varies by store, so confirm before the design depends on one.
go deeper
The takeaway is a habit: when you read something from this tier, ask how much it will return, and ask for a limited amount rather than everything. Unbounded reads are the thing to notice in your own code.
Know the bounded forms and be able to apply one: a range, a cursor walk, an enforced cap, or a precomputed value. Say what each costs, especially the extra round trips a cursor walk adds.
Judge which bound fits a given call and confirm the store actually offers it. Argue the cap as a failure-mode choice — one attributable error instead of a diffuse slowdown — and check the arithmetic for placement before trading one call for many.
Own the invariant across teams: no call's cost decided by stored data, a stated bound per request path, and a review question about ten times the data. Decide who owns limits and exceptions, since the cap is a policy, not a setting.
## What you are actually deciding This is not an optimisation question. A call whose cost is proportional to stored data is an **unbounded liability** in a component that many request paths share: it is correct today, it passes review, it gets slower every week, and on a store that executes one operation at a time its worst case is everyone's worst case. The decision is which bound you require, and what you are willing to pay for it — because every bound costs something, and a proposal that claims otherwise has hidden the cost rather than removed it. State the invariant first, because it survives changes of store and of topology: > No call to the volatile tier may have its cost decided by how much is stored. ## The four bounded forms 1. **Ask for a range or a slice.** Request the first hundred members, or a window, rather than everything. The bound is explicit and the cost is now in the request, where a reviewer can see it. 2. **Walk incrementally with a cursor.** Each call returns a slice and a position; the caller repeats until done. Per-call cost is bounded no matter how large the collection grows. 3. **Cap what a single request may return, and refuse past the cap.** A limit enforced at the tier or in a shared client wrapper, with a loud failure rather than a silent truncation. 4. **Remove the growth entirely.** Maintain the answer as its own entry, updated when the data changes, so the read is a constant-cost single-key operation. Counting, ranking a top-N and "is this present" are the usual candidates. ## What each one costs | Bounded form | What it bounds | What it costs | When it is unavailable | |---|---|---|---| | Range or slice | one call's work and reply size | the caller must choose the bound; more trips for the rest; the tail is never read unless someone pages | where the store exposes no ranged form of the operation | | Incremental cursor walk | per-call work, independent of total size | many round trips; a view that can change mid-walk, so items may be missed or seen twice; longer total elapsed time | where the store offers no cursor, leaving only the unbounded form | | A cap with a refusal | the worst case across every caller | a legitimate large caller now fails; somebody owns the number and the exceptions | never technically unavailable, but needs a place to enforce it | | Precomputed derived entry | the read path, permanently | a second value to keep correct; it can drift; the write path gets more expensive | where the derivation cannot be maintained incrementally | The cap deserves the most thought, because it is the only one of the four that changes **which failure you get**. Without it, an oversized request is served, slowly, at everyone's expense. With it, one caller receives an error and everyone else is unaffected. Choosing the second is choosing a localised, attributable failure over a diffuse one — and that is almost always the right trade in a shared tier, provided the refusal is visible enough that the caller fixes the call rather than retrying it. ## Where the answer changes - **By store.** A store holding **opaque bytes** has no collection operations to bound, so the same discipline applies instead to how large one value may grow and to operations that touch the whole keyspace. A store that **understands structure** typically offers ranged and incremental forms — but which operations have them varies, and "use the incremental form" is not portable advice. - **By execution model.** On a store that **executes one operation at a time**, a bound is a shared-fate requirement, because the unbounded call's duration is everyone's worst-case latency. On a store that **serves requests from a thread pool** the same call costs a share of capacity, so the bound is still required but the argument for it is capacity rather than head-of-line delay. - **By topology.** On a **partitioned tier**, a bound applied per partition still fans out across partitions, so the request-level cost is the number of partitions multiplied by the per-call bound. Bound the fan-out as well as the call. - **By placement.** Replacing one expensive call with many bounded ones multiplies the **round-trip time**, which is negligible when the tier is co-located and dominant across a **cross-zone hop**. The same refactor is a clear win in one placement and a regression in another. ## The contract worth writing down For each request path, record the calls it makes to the tier, the bound on each one's cost, and the arithmetic for the path as a whole. Then add the review question that catches the next instance before it ships: **what does this call cost at ten times today's data?** A team that can answer that for every call has already removed this failure mode; a team that cannot has it and does not know when it will arrive.
- When is refusing the caller better than serving the expensive call slowly?Whenever the tier is shared. Serving it spreads a diffuse, unattributable delay across every other caller; refusing it produces one clear error with an obvious owner, which is the failure that actually gets fixed. Reserve the lenient path for a tier with one caller, or for an operational task run deliberately outside serving hours.
- What does an incremental walk not promise?A consistent view. It reads the collection over many calls while other callers are changing it, so an item added during the walk may or may not appear and an item can be seen twice if positions shift. It is a bound on per-call cost, not a snapshot, and any logic that needs exactly-once treatment of members has to tolerate that.
- Does replacing one call with fifty bounded ones always help?No. It bounds the store's per-call work, which is the point, but it multiplies round-trip time: fifty calls across a cross-zone hop can cost the request more wall time than the single expensive call did, while co-located the same change is nearly free. State the placement before claiming the refactor is an improvement.
saying these in an interview costs you the question
- Answers a cost that grows with data by adding connections or a larger machine.
- Sets a cap but never decides what the caller that exceeds it receives.
- Assumes every store offers an incremental or ranged alternative.
- Treats an incremental walk as a consistent snapshot of the collection.
- Bounds per-call work and ignores the round trips the caller now makes.
- Calls the unbounded read acceptable because it is fast on today's data.