Multi-tenant resource allocator: entity model, lifecycle state machine, optimistic locking
Company: Salesforce
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: medium
Interview Round: Other
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.
```hint 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.
```
#### 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.
```hint 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.
```
#### 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.
```hint Follow the version number
Trace both requests from the read to the conditional write and ask what the losing write sees.
```
#### 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.
```hint 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.
```
#### 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
### What a Strong Answer Covers
- A compact model whose classes each have one clear responsibility
- State transitions and concurrency control that work together, with every transition applied as a versioned conditional write
- Isolation as a structural property of the code, not a convention
- Explicit trade-offs: optimistic versus pessimistic locking, reservation versus direct allocation
- Edge cases such as double release, lease expiry racing with a late release, and tenant offboarding
### 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?
Overview: 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.
Read the full Salesforce Software Engineer interview experience this question came from