Add Hold-Available and Loan Due-Date Notifications to a Library Lending System
InstacartSoftware Engineer · Online AssessmentLast updated
Tests domain modeling, queue management, and idempotent notification triggering for an event- and time-driven library lending workflow.
Problem Statement
Design and implement a notification manager for a digital library lending system. The system manages book copies, patron loans, and hold queues. Your task is to process lending events and periodic time ticks to generate notifications for patrons while enforcing hold priority, loan renewal rules, and duplicate notification suppression.
Requirements
Implement the LibraryNotificationManager class with the following methods:
-
place_hold(patron_id: str, book_id: str, timestamp: int) -> bool- Adds a patron to the hold queue for
book_id. A patron can only have one active hold per book. ReturnsTrueif hold is successfully placed,Falseif duplicate.
- Adds a patron to the hold queue for
-
return_book(copy_id: str, book_id: str, timestamp: int) -> dict | None- Marks a returned copy as available. If patrons are waiting in the hold queue for
book_id, allocate the copy to the patron with the earliest hold timestamp, transition the hold toREADY_FOR_PICKUP(with a pickup window of48hours:expires_at = timestamp + 172800), and return a notification object:{ "type": "HOLD_AVAILABLE", "patron_id": str, "book_id": str, "copy_id": str, "expires_at": int, "timestamp": timestamp } - If no holds exist, the copy remains available in inventory, returning
None.
- Marks a returned copy as available. If patrons are waiting in the hold queue for
-
checkout(patron_id: str, copy_id: str, book_id: str, timestamp: int, duration_days: int = 14) -> bool- If the patron had this copy reserved via a
READY_FOR_PICKUPhold, consume the hold. If the copy is free in general inventory, loan it directly. Creates a loan withdue_date = timestamp + (duration_days * 86400). ReturnsTrueon success,Falseif the copy is not available to this patron.
- If the patron had this copy reserved via a
-
check_due_reminders(current_time: int) -> list[dict]- Scans all active loans and returns a list of reminder notifications for:
- Due Soon: Loans due within the next 24 hours (
0 due_date). Each loan should receive at most oneOVERDUEreminder per calendar day (every 86,400 seconds) while still unreturned.
- Due Soon: Loans due within the next 24 hours (
- Notification format:
{ "type": "LOAN_DUE_SOON" | "LOAN_OVERDUE", "patron_id": str, "copy_id": str, "due_date": int, "timestamp": current_time }
- Scans all active loans and returns a list of reminder notifications for:
Constraints
- All timestamps are Unix epoch timestamps in seconds.
- Up to
10^4calls across all methods. - A copy can only be checked out to one patron at a time.
- Reminder queries must be idempotent: calling
check_due_reminderstwice at the samecurrent_timemust not emit duplicate notifications.
Example
manager = LibraryNotificationManager()
manager.place_hold("p1", "book_A", 1000)
manager.place_hold("p2", "book_A", 1050)
# Book returned; p1 gets first hold
notif = manager.return_book("copy_1", "book_A", 2000)
# notif == {
# "type": "HOLD_AVAILABLE",
# "patron_id": "p1",
# "book_id": "book_A",
# "copy_id": "copy_1",
# "expires_at": 174800,
# "timestamp": 2000
# }
# Checkout copy_1 to p1
assert manager.checkout("p1", "copy_1", "book_A", 2500, duration_days=2)
# Loan due at: 2500 + 2 * 86400 = 175300
# 30 hours later (timestamp = 110500), 175300 - 110500 = 64800 (<= 86400, due soon)
reminders = manager.check_due_reminders(110500)
# reminders contains 1 LOAN_DUE_SOON for patron 'p1'
# Calling again at same timestamp produces no duplicate
assert manager.check_due_reminders(110500) == []
Hints
Hint 1
Separate hold queue state (FIFO per book) from hold reservation state (copy assigned to patron with an expiration deadline).
Hint 2
Track notification dispatch history per loan using a state flag or record of the last emitted reminder timestamp to prevent duplicate alerts.
Hint 3
When a hold pickup window expires without a checkout, ensure the copy can be reassigned to the next patron in the hold queue or returned to general availability.
Follow-up questions
- How would you handle hold cancellations and expired pickups when `check_due_reminders` runs?
- If millions of active loans exist, how would you optimize finding loans due within 24 hours without an O(N) linear scan?
- How would you design the data persistence layer to ensure hold allocations and checkouts remain consistent during concurrent patron requests?
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.