Multi-tenant resource allocator: entity model, lifecycle state machine, optimistic locking

Read the full interview experience this question came from →

Quick 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.

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

Salesforce logo
Salesforce
Aug 8, 2026
mediumSoftware EngineerOtherSoftware Engineering Fundamentals
0
0

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 Guidance

  • 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 Guidance

  • 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 Guidance

  • 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 Guidance

  • 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 Guidance

  • 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 Guidance

  • 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 Guidance

  • 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?