Prompt and scope
A quota service answers whether a resource amount can be claimed now; it does not move files or execute the business operation. The design must separate authoritative capacity, temporary reservations, and final usage while keeping shared infrastructure fair.
What the interviewer is testing
- Distinguishing rate limits, capacity quotas, reservations, and commits.
- Preventing oversell with concurrent updates and idempotent APIs.
- Recovering expired reservations after caller crashes and retries.
- Reasoning about tenant fairness, hot keys, regional consistency, and degradation.
Clarifying questions to ask
Clarify the resource dimension (requests, bytes, or concurrent jobs), scope (tenant, project, user, or endpoint), burst and hierarchy rules, reservation lifetime, billing consistency, and whether cross-region requests can use one authority.
The 30-second answer
I would maintain an authoritative state machine per resource key with limit, committed, and reserved, exposing reserve, commit, release, and query APIs. Reserve performs an atomic conditional check and carries an idempotent reservation_id and expiry. Commit converts a reservation to usage; release or expiry returns it. An immutable event ledger and reconciliation repair drift, while routing and fairness rules prevent one hot tenant from starving others.
Step-by-step deep dive
1. Define state and API boundaries
Each quota record has a resource key, limit, committed amount, reserved amount, version, and update time. reserve returns a reservation_id, available amount, and expiry; commit can consume only its reservation; release is repeatable; query returns remaining capacity and freshness. A caller reserves before writing the resource, commits after success, and releases on failure or cancellation.
2. Prevent oversell under concurrency
A resource-key update must be one atomic transaction or linearizable store operation: reserve only when committed + reserved + amount <= limit. Repeating an idempotency key returns the original result; changing its parameters is rejected. Hot keys can shard by tenant or resource, but the design must state whether temporary overage is allowed and how shards reconcile.
3. Reclaim leaked reservations
Callers can crash after reserving, so store an expiry and reclaim through a scanner or delayed queue. Race recovery and commit with state conditions: a committed reservation cannot be released, and a released reservation cannot be committed. Track reclaim lag so delayed cleanup is not mistaken for available capacity.
4. Handle shared pools and fairness
A shared pool can have global, tenant, and project constraints. Check them in a fixed order and within one transaction. Allocate bursts by tenant quota, priority, or weighted fairness so one tenant cannot consume the pool. Return remaining capacity, retry timing, or queue state to reduce blind retries.
5. Design failures, regions, and reconciliation
If the authority is unavailable, conservatively reject high-risk reservations rather than spending billable capacity from an unknown state; low-risk reads may return a timestamped approximation. Pin a tenant to a home region, or define an explicit local-overage boundary and reconcile asynchronously. Write every reserve, commit, and release to an immutable ledger, compare it with real resource usage, and repair drift with idempotent compensation jobs.
A strong sample answer
I would clarify the resource, tenant hierarchy, reservation lifetime, and consistency requirement. The service stores limit, committed, reserved, and version per resource key and exposes reservation_id-based reserve, commit, release, and query operations. Reserve atomically checks the sum; retries return the same result. Callers commit after a successful write, release on failure, and a reclaimer handles expiry. Shared pools check global and tenant limits together and use fixed routing, priority, or weighted fairness. On authority failure, high-risk writes fail closed. Cross-region placement uses a tenant home region or an explicit bounded overage. An immutable ledger and periodic reconciliation keep tracked quota aligned with actual usage.
Common mistakes
- Treating quota as a rate limiter that only returns 429.
- Reading remaining capacity and writing later without an atomic condition.
- Omitting reservation identity, expiry, and idempotency semantics.
- Letting a crashed caller hold capacity forever.
- Ignoring shared pools, hierarchy, and noisy-neighbor fairness.
- Failing open during a storage outage and fixing counters manually afterward.
Follow-up questions and responses
The caller times out before commit. Retry or reserve again?
Query or retry commit with the same reservation_id first. Create a new reservation only after confirming the old one was released or expired.
What if several products share one tenant quota?
Treat the tenant as the shared parent and products as child constraints, checking both in one transaction. Report which layer is exhausted so clients can queue or degrade.
Can regions reserve concurrently?
For non-oversellable billing capacity, use one authority or strong coordination. If bounded overage is acceptable, give each region a borrowing limit and reconcile debt explicitly.
How do you detect quota drift?
Compare ledger events and resource scans with committed, reserved, and actual usage by tenant and resource type. Alert on differences and run auditable, idempotent compensation jobs.