Multi-tenant resource allocator: entity model, lifecycle state machine, optimistic locking
SalesforceSoftware Engineer · OtherLast updated
Low-level design question for a senior backend role: model a multi-tenant resource allocation module where customers request and release resources. It tests compact class design, an explicit resource lifecycle state machine, optimistic locking under concurrent requests, and tenant isolation enforced in code.
You are in the low-level design round of a senior backend loop for a multi-tenant SaaS platform. Design the core of a resource allocation module: customers, each belonging to exactly one tenant, request resources from a pool and later release them. Two requirements are non-negotiable. First, requests arrive concurrently, and the same resource must never be handed to two holders at once. Second, isolation is absolute: a customer of one tenant must never be able to see, allocate, or release a resource that belongs to another tenant.
The round focuses on clean entity modeling, explicit state transitions for each resource's lifecycle, optimistic concurrency control, and tenant isolation enforced in the code itself rather than left to callers. Aim for a small object model with a handful of core classes, sketch the key classes and method signatures in a language of your choice, and walk through the critical paths.
Constraints and Clarifications
- The kind of resource is not specified. Treat a resource as a generic allocatable unit (a slot, a seat, a license) with an identity and an owning tenant.
- Assume the module runs on several service instances that share one transactional data store, so an in-process lock cannot be the only protection.
- Optimistic locking is the expected concurrency strategy; be ready to say when you would switch to a pessimistic one.
Clarifying Questions
- Does each tenant own a private pool, or do tenants draw from a shared pool with per-tenant quotas?
- May a customer hold several resources at once, and is there a cap per customer or per tenant?
- When nothing is free, should a request fail immediately or wait in a queue?
- Do allocations expire if a customer never releases them?
- On a version conflict, should the module retry internally or report the conflict to the caller?
Part 1 — Model the entities
Identify the core entities, their fields and relationships, and the service and repository interfaces that operate on them. Show where tenant identity lives in each entity.
What This Part Should Cover
- A small set of entities with clear ownership and cardinality
- Tenant identity carried on every tenant-owned record
- A single service boundary through which all state changes flow
- Separation between domain objects and persistence
Part 2 — Define the resource lifecycle
Define the states a resource moves through from creation to retirement, the legal transitions, the operation that triggers each one, and how the code rejects an illegal transition such as releasing a resource that was never allocated.
What This Part Should Cover
- An explicit set of states and a transition table
- Handling of expiry, cancellation and double release
- What happens between release and reuse
Part 3 — Allocate safely under concurrency
Two customers of the same tenant ask for the last free resource at the same instant, on different service instances. Walk through exactly what your optimistic locking scheme does, what each caller receives, and how retries behave when many requests contend for a small pool.
What This Part Should Cover
- The version check and the conditional write that enforces it
- Bounded retry behavior and what the caller sees when the pool is exhausted
- Contention hot spots and when optimistic locking stops being the right tool
Part 4 — Enforce tenant isolation in code
Show how the design guarantees that no code path can read or modify another tenant's resources, even if a caller passes an identifier that belongs to someone else.
What This Part Should Cover
- Where tenant identity is derived and how it is propagated
- Repository and query design that cannot omit the tenant filter
- Defense in depth below the application layer
- How isolation is tested
Hints
Hint 1
Count your classes: Separate what a resource is from the record of who holds it, and decide which object is the only one allowed to change resource state.
Hint 2
Make illegal moves unrepresentable: Think about a transition table that every state change consults, and about what a released resource must go through before someone else can receive it.
Hint 3
Follow the version number: Trace both requests from the read to the conditional write and ask what the losing write sees.
Hint 4
Where does the tenant come from?: Decide which layer is allowed to supply the tenant identity, and make it impossible to query a repository without it.
Follow-up questions
- A single tenant suddenly sends a burst of allocation requests against a pool of only a few resources. What happens to your retry rate, and how would you change the design?
- How would you add per-tenant quotas without creating a new contention point?
- How would you move a tenant's resources to a different pool without downtime?
- What would change if every allocation had to be auditable for compliance?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.