Library Management
Manage book copies, fair reservations, loans and returns with one active holder per physical copy.
Manage book copies, fair reservations, loans and returns with one active holder per physical copy. This worked design targets SDE-1; the scale figures below are exercise assumptions, not claims about a named company's implementation.
Clarify the prompt
| Ask the interviewer | Decision to record |
|---|---|
| Is reservation for a title or a specific physical copy? | Title queues can allocate any eligible copy. |
| What are loan durations, reservation expiry and renewal rules? | They drive state transitions. |
| Are multiple branches and transfers in scope? | Start with one branch and explicit copy IDs. |
Requirements and scope
The core deliverable is: Manage book copies, fair reservations, loans and returns with one active holder per physical copy. The first design implements the APIs below and the state constraints in the data model. Agree the latency, freshness, retention and failure policy before adding infrastructure. Every successful acknowledgment must have an explicit meaning: accepted intent, committed state or completed external work. These are separate milestones.
Capacity and bottleneck reasoning
Assume 100,000 titles, 300,000 copies and 10 checkouts/s at peak. A 500-byte loan history row and one million annual loans require about 500 MB/year before indexes. Index title queues and active loans.
Keep average and peak demand separate. Add replication, indexes, retries and safety headroom to raw arithmetic before selecting capacity. Load-test the busiest ownership key and recovery traffic as well as uniform happy-path throughput.
APIs and data model
- POST /titles/{isbn}/holds -> queue position
- POST /copies/{id}/checkout {memberId, requestKey} -> loan or 409
- POST /loans/{id}/return -> returned; GET /members/{id}/loans
book(isbn PK, title); copy(copy_id PK, isbn, branch, state, version)
hold(hold_id PK, member_id, isbn, created_at, expires_at, state)
loan(loan_id PK, copy_id, member_id, due_at, returned_at); UNIQUE active loan per copyAuthorize resource ownership at the API boundary; do not trust IDs or state supplied by a client. Bind retry identity to the caller and payload for mutating operations. Reject an incompatible replay instead of returning an unrelated stored result.
Component relationships
Read the diagram
Follow the labeled relationship panels; they are separate directed paths, not one global timeline.
Step-by-step design
1. Establish the authority
Separate bibliographic titles from physical copies. A hold expresses demand for a title; allocation converts the earliest eligible hold into an expiring offer for a copy. Order by created_at plus hold_id to break ties.
2. Make retries and concurrent work safe
Serialize return-and-reassignment on the title or a documented allocation key. Change copy state and hold ownership in one transaction. Checkout verifies the allocated member and expiry against database time.
3. Scale and recover the critical path
Renew only if policy permits and no conflicting reservation wins. Persist due dates and emit reminder events through an outbox; reminder delivery does not change loan ownership.
Worked request and concurrency example
Copy C1 returns while members M1 and M2 wait. The return transaction releases C1 and assigns it to M1 with expiry E. M2 stays queued. At E, a worker conditionally expires M1 only if its hold remains unclaimed. If M1 checks out first, the expiry update affects zero rows. A repeated return key returns the original result and cannot allocate the same copy twice.
Failure and recovery walkthrough
Two reservation workers race after a return. Lock the title allocation boundary or use a conditional copy update plus a unique active assignment. A notification failure leaves the assignment intact and retries the outbox event.
Read the diagram
This numbered trace follows one scenario; explain the durable evidence at each transition.
Deep dive: titles are not copies, holds are not loans
This original one-branch baseline lets members reserve a title, receive an expiring offer for a returned physical copy, check it out and return it. A catalog search may use a projection, but assignment and borrowing use authoritative copy/hold/loan state. Start with a relational transaction; inter-branch delivery, payments and recommendation/search scaling are separate extensions.
| Entity | Identity and invariant |
|---|---|
| Title | Bibliographic identity; many physical copies may refer to it |
| Copy | One physical item, with branch and circulation state |
| Hold | One member's demand for a title, ordered by (created_at, hold_id) in this exercise |
| Offer | A particular active hold/copy allocation with member, generation and expiry |
| Loan | Borrowing interval for one copy; at most one active loan per copy |
A returned copy does not make every reservation fulfillable. Assign it only to the next eligible waiting member. Eligibility includes membership/policy and any suspension under the chosen contract. “FIFO” here means oldest eligible hold under a serialized title-allocation decision, not an accidental worker execution order.
Make return and reassignment one decision
begin local transaction
resolve return operation key and verify active loan/copy
lock the title allocation guard, then affected copy/hold/loan in fixed order
close the matching active loan once
select oldest eligible waiting hold ordered by created_at, hold_id
if found: allocate this copy to that hold, set offer expiry/generation
otherwise: mark copy available
store return result and notification outbox intent
commitEvery conflicting return, allocation, checkout, expiry and renewal path must follow the same authority/lock-order protocol. Define the exact row acquisition order in an implementation; a guard used by only one endpoint is not protection. Keep external reminder/email work outside the transaction and recover publication through the outbox. A repeated return key returns the original result; it cannot allocate the same returned copy twice.
Checkout and expiry must agree at the boundary
The exercise checks offer eligibility at authoritative database time, with checkout allowed only while now < expires_at. At equality, expiry may win; a delayed worker does not extend eligibility implicitly. Both transitions match current offer identity/generation and state.
Read the diagram
Copy C is offered to member M1 at generation 4 → The offer reaches its expiry boundary → Allocator offers C to M2 at generation 5 → M1 submits a delayed generation 4 checkout → Reject M1; preserve M2 ownership
If checkout wins before expiry, create the loan and consume the offer atomically. A duplicate expiry sees a non-active offer and does nothing. If expiry wins first, M1 cannot borrow C1 using the old offer even if an email still says it is available. M2's new assignment must not be released by M1's old expiry work. The final unique active-loan constraint protects against two successful borrowings of one copy.
Renewal, loss and policy changes add real state
A renewal is not a blind overwrite of due_at. Validate active loan identity, member status, renewal count and competing eligible holds under the same title/copy authority. Under this exercise's policy, an eligible waiting hold blocks renewal. Another product can choose a different policy, but must make the conflict decision explicit.
A lost/damaged copy leaves circulation through an audited transition. Do not leave an active offer promising an unavailable item. Requeue or cancel its hold under a declared rule, preserve queue-order evidence, and resolve the member's notification. Fee calculation or an external payment is a distinct workflow; a payment timeout cannot change physical-copy existence.
Catalog-title deletion and copy withdrawal are different operations. Historic loans may require retained identity even after a copy is withdrawn, while privacy/retention policy governs personal details. Remove a title from search without silently deleting the evidence needed to reconcile active borrowing.
Recover from a notification failure
Offer allocation is the durable business fact. If email publication fails, the member can still see the offer through an authorized account view while the outbox retries the same event identity. Decide whether the pickup expiry begins at allocation or after a defined notification milestone; this exercise uses allocation time and makes that visible to the member. Provider delivery is not silently assumed from a queue acknowledgement.
At an illustrative 20,000 returns/day, average return traffic is only about 0.23/s. School/library closing-time bursts can be much higher, and a single popular title is a separate contention point. Use bounded allocations and short transactions; a global library-wide lock unnecessarily serializes unrelated titles. Monitor active offer age, expiry/reassignment conflicts, notification lag, missing copies and circulation discrepancies.
Library assessment: preserve fair and exclusive circulation
Question: M1 and M2 have identical hold timestamps. Which one receives a returned copy under this exercise?
Show answer and explanation
Answer: Use hold_id as the deterministic tie-break within the authoritative eligible queue. Do not let whichever notification worker runs first choose the winner.
Question: M1's checkout reaches the service at the offer's exact expiry instant. Can client send time authorize it?
Show answer and explanation
Answer: No under the stated rule. The authoritative eligibility decision uses now < expires_at; an expired/reassigned generation cannot create a loan. Client clocks and an old email do not extend the offer.
Question: May a renewal succeed while another member has an eligible waiting hold?
Show answer and explanation
Answer: Not under this exercise's policy. Recheck that predicate with the renewal decision; a stale preliminary list cannot establish that no hold exists.
Score one point for title/copy separation, deterministic eligible ordering, atomic return/allocation, offer-generation expiry/checkout and renewal/withdrawal policy. Then add another branch and explain which ownership and transfer states must be introduced before relaxing the single-branch transaction boundary.
Review Transactions and Isolation and PostgreSQL locking before translating the pseudocode into an implementation. This is an original circulation exercise; tests model selected state transitions, not an integrated library system.
Decisions and alternatives
| Decision | Trade-off |
|---|---|
| Alternative 1 | Title-level serialization is simple and fair but a popular title becomes a hot key. Branch-scoped queues reduce contention while changing fairness across branches. |
| Alternative 2 | Store an authoritative due_at rather than recomputing from current policy. Versioned policies require extra history but keep old loans explainable. |
Operational contract
Monitor overdue counts, queue wait time, allocation age and outbox age. Audit every state change with member and copy IDs; restrict staff overrides and keep a reason. Recover lost jobs by scanning due active holds.
Choose alert thresholds from the agreed user-facing objective. Assign an owner for stuck work and a replay/repair procedure that preserves the original business identity. Bound queue depth, concurrency and recovery throughput so an outage does not produce an uncontrolled catch-up burst.
Foundation, senior and staff depth
- Foundation: Distinguish titles, physical copies, holds and loans.
- Senior: Serialize FIFO assignment, expiry and checkout using the same authority.
- Staff: Defend renewal fairness, notification recovery and lost-copy reconciliation.
Practice before revealing the answer
Spend 45 minutes: 5 clarifying scope, 7 on capacity/APIs/state, 12 on the first architecture, 15 on the concurrency and failure examples, and 6 on trade-offs and operations. Draw your own design before comparing it with this chapter.
Question: What happens if hold expiry and checkout arrive at the same time?
Show answer and explanation
Answer: Both must compete at the same authoritative state transition. Checkout succeeds only for an active unexpired allocation; expiry succeeds only for the current hold version. Exactly one transition wins, and the loser receives the current state.
Verification and rollout
Race checkout against expiry at equality and replay an old offer after reassignment. Verify one active loan per copy, deterministic eligible FIFO order and atomic return/allocation/publication. Test renewal when another eligible member is waiting.
Primary technical reference
Official documentation for the mechanism used in this exercise. This lesson is an original interview design, not a description of undisclosed company internals.
Related study
Before this design, review these mechanisms and identify which owns the critical state transition:
Use the Interview Playbook to adapt the depth and timing, then return to the question breakdown overview.