BookMyShow
Advanced · 18 min read · ▶ live playground · ✦ checkpoint
“Design a movie-ticket booking system like BookMyShow.” The seat grid is not the hard part. The interview is testing what happens when two people tap A1 at the same moment and both believe payment makes it theirs.
Requirements
Fix the booking boundary before naming services. A seat that looks available is only a display fact; a seat that has been claimed atomically is a promise the system can keep.
| Kind | Requirement for this design |
|---|---|
| Functional | A cinema contains screens, and movies play as scheduled shows on those screens. |
| Functional | Every show has its own seat map, even when two shows use the same physical screen at different times. |
| Functional | A user selects one or more seats and asks the system to hold the complete selection. |
| Functional | A successful hold marks those show seats HELD for that user until a stated expiry time. |
| Functional | A held seat cannot be held or booked by another user. |
| Functional | After payment succeeds, the same user confirms the active hold and receives one booking. |
| Functional | Confirmation changes every seat in the hold to BOOKED as one operation. |
| Functional | If payment does not complete before the hold expires, its seats return to AVAILABLE. |
| Non-functional | Two concurrent requests for the same show seat must produce one winner, never two bookings. |
| Non-functional | The shared seat store, not one application process, is the source of truth for claims. |
Payment-gateway internals, dynamic pricing engines, recommendations, refunds, coupons, and cancellation policy are out of scope. The design receives a trusted payment reference; it does not decide how a card or wallet was charged.
Clarifying questions to ask
Put the double-booking question first. Its answer changes the storage boundary, the API result, and the failure tests.
- What stops two users booking A1 at the same moment? The hold is an atomic conditional claim in shared storage. Only a request that changes A1 from
AVAILABLEtoHELDwins; the loser receives a conflict and must refresh or choose another seat. Reading first and writing later is not enough. - Is a group of selected seats all-or-nothing? Yes. If a user selects A1, A2, and A3, the hold succeeds only when all three can be claimed in one transaction. Returning a surprise one-seat hold creates a poor checkout contract and complicates payment.
- How long does a hold last? Five minutes in this baseline. A time-to-live (TTL) is the deadline after which a temporary claim stops being valid. The API returns
expiresAtso the client can show a countdown without inventing one. - What happens exactly at expiry? A hold is valid only while
now < expiresAt. At or after the deadline, confirmation fails and an atomic reclaim may move matchingHELDseats back toAVAILABLE. A background sweep cleans old rows, but correctness cannot depend on the sweep running at the perfect millisecond. - Do we use optimistic or pessimistic concurrency? Either can close the race. The baseline contract is a conditional claim; a relational implementation may use compare-and-set updates, while a high-contention path may lock the selected rows before checking and changing them.
- Can the lock live in an in-memory
Map? Only for a one-process exercise. Two servers have two maps, so each could declare itself the winner. Production must put the authoritative status, hold owner, and expiry in the shared database or lock store. - Can one user hold seats across two shows? Not in this baseline. One user may have one active hold at a time, which limits inventory hoarding. A marketplace that permits several carts would make that a configurable policy and cap the total seats or holds per account.
- What is the lock granularity? One show seat, identified by
(showId, seatId). Locking a whole show would serialize unrelated users; locking only physical seat A1 would incorrectly block A1 in a later show. - Does selecting a seat in the UI reserve it? No. Selection is local intent. Only a successful
hold()response reserves inventory, and the client must handle a conflict even when its seat map was fresh a moment ago. - Does payment success guarantee confirmation? No. Payment can finish after the TTL or while the booking service is unavailable.
confirm()rechecks hold owner and expiry; recovery for a captured payment without a booking belongs to the excluded refund or reconciliation workflow. - What happens when the client retries confirmation? The command is idempotent: repeating the same hold confirmation returns the first booking instead of booking the seats twice. The hold ID is the idempotency key, and the stored booking retains the payment reference as evidence.
- Can two shows share the same screen and seats? Yes.
Screendefines the physical layout, while eachShowcreates independent seat-status records for its scheduled time.
Entity extraction
Run the noun pass from requirements-to-classes against the clarified flow:
- A cinema contains screens, and each screen schedules shows for a movie.
- A show owns a map of seats, each with a seat status of available, held, or booked.
- A user selects seat IDs and receives a seat hold with a user ID and an expiry time.
- A booking service coordinates hold and confirmation, while a seat lock manager owns the atomic claim.
- A successful payment reference turns the active hold into a booking.
Sort the nouns before drawing one box per word:
| Bucket | Nouns | Model choice |
|---|---|---|
| Place and layout | cinema, screen | Cinema contains Screen; a screen defines physical seat IDs |
| Scheduled inventory | movie, show, seat | Show stores the movie title and owns show-specific Seat state |
| Enum | seat status | SeatStatus contains AVAILABLE, HELD, and BOOKED |
| Temporary claim | seat hold, expiry, user ID | SeatHold records ownership, selected seats, and TTL |
| Durable result | booking, payment reference | Booking records the confirmed user, show, seats, and payment evidence |
| Application boundary | booking service | BookingService orders hold, payment evidence, confirmation, and retry handling |
| Protected resource | seat lock manager | SeatLockManager performs the all-or-nothing seat-state transition |
| Actor | user | Selects, pays, and calls the boundary; no core User class is required here |
| Excluded concepts | gateway, pricing engine, recommendation, refund | No speculative classes in the core model |
The verb pass assigns the contested state to one owner:
| Verb phrase | Owner | Reason |
|---|---|---|
| schedule a movie on a screen | Screen creates a Show | The screen owns its physical layout and schedule boundary |
| expose show-seat status | Show finds its Seat records | Status belongs to one show, not to the physical seat forever |
| hold a complete seat selection | SeatLockManager.tryHold() | One operation must check and claim every selected seat atomically |
| expire a temporary claim | SeatLockManager.releaseExpired() | It owns both hold metadata and the matching seat transitions |
| confirm held seats | SeatLockManager.confirm() | The same owner verifies user, TTL, and HELD → BOOKED |
| create or return a booking | BookingService.confirm() | The application boundary owns idempotent command handling |
| record the completed reservation | Booking | The durable result joins user, show, seats, and payment reference |
BookingService is an orchestrator, not the lock itself. It can validate a payment reference and return an existing booking, but the shared-store implementation behind SeatLockManager is what decides whether inventory changed.
Core class diagram
Keep the show inventory, temporary claim, and confirmation boundary visible. A payment adapter, repository, expiry worker, and notification publisher can surround this core after the ownership rules are stable.
class diagram
Core movie-booking and seat-lock model
- 1Cinema is the venue boundary and contains its physical screens.
- 2Each Screen defines a physical seat layout and schedules shows.
- 3A Show turns that layout into independent inventory for one movie time.
- 4Each show-specific Seat carries the AVAILABLE, HELD, or BOOKED state.
- 5BookingService receives commands for one user and one Show.
- 6The service delegates contested inventory changes to SeatLockManager.
- 7SeatLockManager checks and changes the selected Seat records as one claim.
- 8A SeatHold records the winner, selected seats, and expiry deadline.
- 9BookingService stores the durable result for idempotent retries.
- 10The Booking retains which temporary claim produced the confirmed seats.
SeatStatus stays a field vocabulary rather than consuming another box. Movie also remains data on Show because this flow needs its title, not a movie catalog lifecycle. If catalog management arrives, it can earn its own entity then.
The diagram shows SeatLockManager using seats, not owning the cinema's layout. In production, that collaborator maps its operations to show-seat rows in shared storage; the in-memory objects below only make the responsibility boundary executable.
Critical-flow sequence diagram
The critical flow is “select → hold → pay → confirm.” The sequence follows the winning request; a competing request receives conflict at the atomic claim and never reaches payment for those seats.
sequence diagram
Select, hold, pay, and confirm show seats
- 1The selected seats become one conditional claim in the shared store.
- 2Only the winning claim returns a hold ID and its expiry to the user.
- 3Payment happens outside the seat store, then its reference enters confirm().
- 4Confirmation rechecks owner and TTL before changing the same seats to BOOKED.
- 5BookingService records or returns the one booking associated with H-1.
If user B races user A, both may have read AVAILABLE, but only one conditional write may return SeatHold H-1. The loser receives a conflict at message 3. It does not wait for user A's payment to discover that A1 was already promised.
If the clock reaches expiresAt before message 10, confirmation fails. A request path or expiry worker conditionally releases only seats still marked HELD by H-1; it must never turn a seat already confirmed by another transaction back to AVAILABLE.
The atomic claim closes the race
Optimistic concurrency control assumes conflicts are uncommon and detects a loser at write time. A version or compare-and-set (CAS) update changes a row only when its current version and status match the expected values. For several seats, run the conditional updates in one transaction and commit only when the changed-row count equals the requested seat count; a zero or short count means conflict, rollback, refresh, and retry with another selection.
Pessimistic concurrency control prevents a competing writer from entering the critical section. A relational implementation can select the requested rows in stable seat-ID order with SELECT ... FOR UPDATE, verify availability or expiry, then update them before releasing the row locks. Stable order reduces deadlock, where two transactions each wait for a row held by the other. A distributed lock with a TTL can serialize claims when row locks are unavailable, but the durable seat rows must still decide the final status.
The TTL hold is the standard bridge between browsing and a slow external payment. It gives the user a short exclusive window without reserving inventory forever. An expiry sweep keeps storage tidy; lazy reclamation during tryHold() makes an expired seat claimable even when that sweep is late.
An in-memory Map demonstrates the state machine for one process only. It has no authority over a second server, a restarted process, or a delayed worker. Production places the atomic comparison, hold owner, and deadline in the shared store and treats cached seat maps as read projections.
TypeScript skeleton
The skeleton models show-specific seats, an all-or-nothing synchronous claim, ownership checks, expiry, and idempotent booking confirmation. tryHold() has no await between its check and updates, so it behaves atomically inside this one process; the production equivalent is one shared-store transaction.
export {};
enum SeatStatus {
AVAILABLE = "AVAILABLE",
HELD = "HELD",
BOOKED = "BOOKED",
}
enum HoldStatus {
ACTIVE = "ACTIVE",
CONFIRMED = "CONFIRMED",
EXPIRED = "EXPIRED",
}
function requireText(value: string, label: string): string {
const normalized = value.trim();
if (normalized.length === 0) {
throw new Error(`${label} is required`);
}
return normalized;
}
function normalizeSeatIds(seatIds: readonly string[]): string[] {
if (seatIds.length === 0) {
throw new Error("select at least one seat");
}
const normalized = seatIds
.map((seatId) => requireText(seatId, "seat id"))
.sort((a, b) => a.localeCompare(b));
for (let index = 1; index < normalized.length; index += 1) {
if (normalized[index] === normalized[index - 1]) {
throw new Error(`duplicate seat ${normalized[index]}`);
}
}
return normalized;
}
class Seat {
private currentStatus = SeatStatus.AVAILABLE;
private currentHoldId: string | null = null;
public constructor(public readonly id: string) {
requireText(id, "seat id");
}
public get status(): SeatStatus {
return this.currentStatus;
}
public get holdId(): string | null {
return this.currentHoldId;
}
public hold(holdId: string): void {
if (this.currentStatus !== SeatStatus.AVAILABLE) {
throw new Error(`seat ${this.id} is not available`);
}
this.currentStatus = SeatStatus.HELD;
this.currentHoldId = holdId;
}
public book(holdId: string): void {
if (
this.currentStatus !== SeatStatus.HELD ||
this.currentHoldId !== holdId
) {
throw new Error(`seat ${this.id} is not held by ${holdId}`);
}
this.currentStatus = SeatStatus.BOOKED;
}
public release(holdId: string): void {
if (
this.currentStatus === SeatStatus.HELD &&
this.currentHoldId === holdId
) {
this.currentStatus = SeatStatus.AVAILABLE;
this.currentHoldId = null;
}
}
}
class Show {
private readonly seats = new Map<string, Seat>();
public constructor(
public readonly id: string,
public readonly movieTitle: string,
public readonly screenId: string,
public readonly startsAt: Date,
seatIds: readonly string[],
) {
requireText(id, "show id");
requireText(movieTitle, "movie title");
requireText(screenId, "screen id");
if (Number.isNaN(startsAt.getTime())) {
throw new Error("show time is invalid");
}
for (const seatId of normalizeSeatIds(seatIds)) {
this.seats.set(seatId, new Seat(seatId));
}
}
public seat(seatId: string): Seat {
const seat = this.seats.get(seatId);
if (seat === undefined) {
throw new Error(`unknown seat ${seatId} for show ${this.id}`);
}
return seat;
}
}
class Screen {
private readonly seatIds: readonly string[];
private readonly shows = new Map<string, Show>();
public constructor(
public readonly id: string,
seatIds: readonly string[],
) {
requireText(id, "screen id");
this.seatIds = normalizeSeatIds(seatIds);
}
public schedule(
showId: string,
movieTitle: string,
startsAt: Date,
): Show {
if (this.shows.has(showId)) {
throw new Error(`duplicate show ${showId}`);
}
const show = new Show(
showId,
movieTitle,
this.id,
startsAt,
this.seatIds,
);
this.shows.set(show.id, show);
return show;
}
}
class Cinema {
private readonly screens = new Map<string, Screen>();
public constructor(
public readonly id: string,
public readonly name: string,
screens: readonly Screen[],
) {
requireText(id, "cinema id");
requireText(name, "cinema name");
for (const screen of screens) {
if (this.screens.has(screen.id)) {
throw new Error(`duplicate screen ${screen.id}`);
}
this.screens.set(screen.id, screen);
}
}
public screen(screenId: string): Screen {
const screen = this.screens.get(screenId);
if (screen === undefined) {
throw new Error(`unknown screen ${screenId}`);
}
return screen;
}
}
class SeatHold {
public readonly seatIds: readonly string[];
public readonly expiresAt: Date;
private currentStatus = HoldStatus.ACTIVE;
public constructor(
public readonly id: string,
public readonly userId: string,
public readonly showId: string,
seatIds: readonly string[],
expiresAt: Date,
) {
requireText(id, "hold id");
requireText(userId, "user id");
requireText(showId, "show id");
if (Number.isNaN(expiresAt.getTime())) {
throw new Error("hold expiry is invalid");
}
this.seatIds = normalizeSeatIds(seatIds);
this.expiresAt = new Date(expiresAt);
}
public get status(): HoldStatus {
return this.currentStatus;
}
public isActive(at: Date): boolean {
return (
this.currentStatus === HoldStatus.ACTIVE &&
at.getTime() < this.expiresAt.getTime()
);
}
public confirm(): void {
if (this.currentStatus !== HoldStatus.ACTIVE) {
throw new Error(`hold ${this.id} is not active`);
}
this.currentStatus = HoldStatus.CONFIRMED;
}
public expire(): void {
if (this.currentStatus === HoldStatus.ACTIVE) {
this.currentStatus = HoldStatus.EXPIRED;
}
}
}
class SeatLockManager {
private readonly shows = new Map<string, Show>();
private readonly holds = new Map<string, SeatHold>();
private holdSequence = 0;
public constructor(
shows: readonly Show[],
private readonly holdDurationMs: number,
) {
if (
!Number.isSafeInteger(holdDurationMs) ||
holdDurationMs <= 0
) {
throw new Error("hold duration must be positive milliseconds");
}
for (const show of shows) {
if (this.shows.has(show.id)) {
throw new Error(`duplicate show ${show.id}`);
}
this.shows.set(show.id, show);
}
}
public tryHold(
userId: string,
showId: string,
seatIds: readonly string[],
at: Date,
): SeatHold {
requireText(userId, "user id");
this.requireTime(at);
this.releaseExpired(at);
const activeHold = [...this.holds.values()].find(
(hold) => hold.userId === userId && hold.isActive(at),
);
if (activeHold !== undefined) {
throw new Error(`user already has active hold ${activeHold.id}`);
}
const show = this.requireShow(showId);
const normalizedSeatIds = normalizeSeatIds(seatIds);
const seats = normalizedSeatIds.map((seatId) => show.seat(seatId));
const unavailable = seats
.filter((seat) => seat.status !== SeatStatus.AVAILABLE)
.map((seat) => seat.id);
if (unavailable.length > 0) {
throw new Error(`seats unavailable: ${unavailable.join(", ")}`);
}
const holdId = `H-${++this.holdSequence}`;
const hold = new SeatHold(
holdId,
userId,
showId,
normalizedSeatIds,
new Date(at.getTime() + this.holdDurationMs),
);
for (const seat of seats) {
seat.hold(holdId);
}
this.holds.set(holdId, hold);
return hold;
}
public confirm(
holdId: string,
userId: string,
at: Date,
): SeatHold {
this.requireTime(at);
this.releaseExpired(at);
const hold = this.holds.get(holdId);
if (hold === undefined) {
throw new Error(`unknown hold ${holdId}`);
}
if (hold.userId !== userId) {
throw new Error(`hold ${holdId} belongs to another user`);
}
if (!hold.isActive(at)) {
throw new Error(`hold ${holdId} is not active`);
}
const show = this.requireShow(hold.showId);
const seats = hold.seatIds.map((seatId) => show.seat(seatId));
const invalidSeat = seats.find(
(seat) =>
seat.status !== SeatStatus.HELD ||
seat.holdId !== hold.id,
);
if (invalidSeat !== undefined) {
throw new Error(`seat ${invalidSeat.id} lost hold ${hold.id}`);
}
for (const seat of seats) {
seat.book(hold.id);
}
hold.confirm();
return hold;
}
public releaseExpired(at: Date): readonly string[] {
this.requireTime(at);
const expiredIds: string[] = [];
for (const hold of this.holds.values()) {
if (
hold.status !== HoldStatus.ACTIVE ||
hold.isActive(at)
) {
continue;
}
const show = this.requireShow(hold.showId);
for (const seatId of hold.seatIds) {
show.seat(seatId).release(hold.id);
}
hold.expire();
expiredIds.push(hold.id);
}
return expiredIds;
}
public seatStatus(
showId: string,
seatId: string,
at: Date,
): SeatStatus {
this.releaseExpired(at);
return this.requireShow(showId).seat(seatId).status;
}
private requireShow(showId: string): Show {
const show = this.shows.get(showId);
if (show === undefined) {
throw new Error(`unknown show ${showId}`);
}
return show;
}
private requireTime(at: Date): void {
if (Number.isNaN(at.getTime())) {
throw new Error("time is invalid");
}
}
}
class Booking {
public readonly seatIds: readonly string[];
public constructor(
public readonly id: string,
public readonly holdId: string,
public readonly userId: string,
public readonly showId: string,
seatIds: readonly string[],
public readonly paymentRef: string,
) {
this.seatIds = [...seatIds];
}
}
class BookingService {
private readonly bookingsByHold = new Map<string, Booking>();
private bookingSequence = 0;
public constructor(private readonly locks: SeatLockManager) {}
public hold(
userId: string,
showId: string,
seatIds: readonly string[],
at: Date,
): SeatHold {
return this.locks.tryHold(userId, showId, seatIds, at);
}
public confirm(
userId: string,
holdId: string,
paymentRef: string,
at: Date,
): Booking {
const existing = this.bookingsByHold.get(holdId);
if (existing !== undefined) {
if (existing.userId !== userId) {
throw new Error(`booking for ${holdId} belongs to another user`);
}
return existing;
}
requireText(paymentRef, "payment reference");
const hold = this.locks.confirm(holdId, userId, at);
const booking = new Booking(
`B-${++this.bookingSequence}`,
hold.id,
hold.userId,
hold.showId,
hold.seatIds,
paymentRef,
);
this.bookingsByHold.set(holdId, booking);
return booking;
}
public releaseExpired(at: Date): readonly string[] {
return this.locks.releaseExpired(at);
}
}
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
const cinema = new Cinema(
"cinema-1",
"Regal Cinema",
[new Screen("screen-1", ["A1", "A2", "A3"])],
);
const show = cinema.screen("screen-1").schedule(
"show-42",
"The Last Algorithm",
new Date("2026-07-13T18:00:00Z"),
);
const locks = new SeatLockManager([show], 5 * 60 * 1000);
const service = new BookingService(locks);
const start = new Date("2026-07-13T12:00:00Z");
const holdA = service.hold(
"user-A",
show.id,
["A1", "A2"],
start,
);
console.log(`A hold: ${holdA.id}`);
try {
service.hold(
"user-B",
show.id,
["A1", "A2"],
new Date(start.getTime() + 1_000),
);
} catch (error) {
console.log(`B rejected: ${errorMessage(error)}`);
}
service.hold(
"user-C",
show.id,
["A3"],
new Date(start.getTime() + 2_000),
);
const booking = service.confirm(
"user-A",
holdA.id,
"pay-101",
new Date(start.getTime() + 2 * 60 * 1000),
);
console.log(
`Confirmed: ${booking.id} for ${booking.seatIds.join(", ")}`,
);
const afterExpiry = new Date(start.getTime() + 6 * 60 * 1000);
service.releaseExpired(afterExpiry);
console.log(
`Seat states: ${locks.seatStatus(show.id, "A1", afterExpiry)}, ${locks.seatStatus(show.id, "A2", afterExpiry)}`,
);
console.log(
`A3 after expiry: ${locks.seatStatus(show.id, "A3", afterExpiry)}`,
);A hold: H-1 B rejected: seats unavailable: A1, A2 Confirmed: B-1 for A1, A2 Seat states: BOOKED, BOOKED A3 after expiry: AVAILABLE
User B reaches the same seat records after H-1 owns them, so the complete request is rejected without changing either seat. User A confirms inside the five-minute window. User C never pays, and the later sweep releases only A3; confirmed seats remain booked.
BookingService.confirm() returns an existing booking before touching seat state, which makes a retry idempotent in this process. A durable implementation stores the booking, the HELD → BOOKED transition, and the idempotency record in one transaction. Its hold lookup comes from the shared repository rather than a process-local map.
Design-review checklist
This is where an interviewer presses on the few milliseconds between reading and claiming a seat.
- The check-then-act race:
if (seat.available) seat.status = HELDis unsafe when the read and write are separate. Close it with one conditional shared-store claim, locked rows, or a distributed lock plus authoritative conditional state update. For several seats, commit all or none. - Optimistic versus pessimistic choice: CAS avoids holding database locks while users browse and works well when conflicts are uncommon. Row locks can reduce repeated retries for a hot premiere, but they add blocking and deadlock handling. Measure contention instead of declaring one universally faster.
- Hold TTL and expiry: Store
expiresAtbeside the owner and hold ID. Treat an expired hold as reclaimable inside the atomic claim, then use a sweeper for cleanup and seat-map freshness. A delayed worker must not extend ownership. - Safe release: Release only where
status = HELD AND hold_id = expected. An expiry message may be delayed or delivered twice; an unconditionalstatus = AVAILABLEcan reopen a seat that was already confirmed. - Idempotent confirmation: A retry with the same hold and payment command returns the original booking. A second payment reference for an already confirmed hold should not create another booking or charge path.
- Payment and partial failure: Payment is external to the seat transaction. Persist payment evidence, the booking, and booked seats atomically after the payment result arrives. If that commit cannot complete before expiry, reconciliation handles the captured payment; silently booking another user's reclaimed seat would oversell.
- Overselling boundary: Never rely on a cached seat map, queue consumer, or application-local mutex as final authority. Every booking path, including operator tools and retries, must pass through the same shared-store invariant.
- Seat-map scale: Partition or index show-seat rows by
showId, fetch only the visible section, and publish status deltas instead of the whole auditorium. Cache reads freely, but route writes to the authoritative partition for that show. - Contention and lock order: Sort seat IDs before row locking so two multi-seat requests acquire resources in the same order. Keep the transaction short; no payment network call belongs inside it.
- Cross-show policy: A seat key includes the show ID. A separate user-level rule limits active holds across shows; do not obtain a global user lock when a versioned active-hold record or quota transaction is enough.
- Authorization: Confirmation must match the hold's user, not merely know its ID. Treat hold IDs as identifiers rather than bearer permission, and validate the payment reference at the application boundary.
- Testing concurrent holds: Start two requests for the same seat at a barrier against the real persistence adapter. Assert one hold, one conflict, and one final owner; sequential unit calls do not prove concurrency safety.
- Testing time: Cover one millisecond before expiry, exactly at expiry, delayed sweeping, reclaim after expiry, and confirm after expiry. Use an injected clock or explicit timestamps rather than sleeping.
- Testing retries and failures: Repeat confirmation, repeat expiry delivery, fail booking persistence, and retry after a lost response. Assert one booking, no
HELDleak, and noBOOKED → AVAILABLEregression. - With more time: Add repository ports, an injected clock and ID source, payment reconciliation, event publication, per-user quotas, observability for lock conflicts, and typed domain errors after each consistency promise is explicit.
Checkpoint
Answer all three to mark this lesson complete
The booking model makes one distinction carry the whole design: seeing a seat is a read, while holding it is an atomic ownership change. Next, the LRU cache leaves distributed contention behind and builds constant-time behavior from a HashMap and a doubly-linked list.