Venue Booking Service Low-Level Design: Availability Calendar, Hold/Confirm Flow, and Pricing

What Is a Venue Booking Service?

A venue booking service allows clients to search available venues, place holds, confirm reservations, and manage cancellations. Examples include platforms like Peerspace and Splacer. The design challenge is maintaining a real-time availability calendar, preventing double-bookings under concurrent holds, applying dynamic pricing rules, and enforcing cancellation policies cleanly.

Requirements

Functional Requirements

  • Search venues by date range, capacity, location, and amenity filters
  • Check real-time slot availability for a specific venue
  • Place a short-lived hold on a time slot
  • Confirm a hold into a paid booking
  • Cancel a booking subject to policy rules and issue applicable refunds
  • Apply dynamic pricing based on day of week, season, and demand

Non-Functional Requirements

  • No double-bookings; holds must be mutually exclusive
  • Hold expiry within a configurable TTL (e.g., 15 minutes)
  • Availability reads must reflect expired holds without explicit cleanup jobs
  • Pricing calculations must be auditable and reproducible

Data Model

  • venues: venue_id, name, address, capacity, amenities (JSON), base_price_per_hour, timezone, status
  • bookings: booking_id, venue_id, user_id, start_utc, end_utc, status (held, confirmed, cancelled), hold_expires_at, total_price, pricing_snapshot (JSON), created_at, updated_at
  • pricing_rules: rule_id, venue_id, rule_type (day_of_week, date_range, demand_tier), multiplier, priority, valid_from, valid_until
  • cancellation_policies: policy_id, venue_id, hours_before_start, refund_percentage
  • payments: payment_id, booking_id, amount, currency, provider_ref, status, created_at

The bookings table has a partial unique index on (venue_id, start_utc, end_utc) WHERE status IN (held, confirmed), preventing overlapping active bookings at the database level.

Core Algorithms

Hold and Confirm Flow

When a user requests a hold: begin transaction, query for any active booking (status in held/confirmed) overlapping the requested window using the interval overlap check existing.start_utc < requested.end_utc AND existing.end_utc > requested.start_utc, if none found insert a row with status=held and hold_expires_at=NOW()+TTL, commit. Return the booking_id and expiry timestamp to the client.

On confirm: verify hold is not expired, initiate payment, on success update status=confirmed and clear hold_expires_at. On payment failure, leave in held state for retry or let it expire. Expiry cleanup can be a background sweeper or handled lazily: treat any held row with hold_expires_at in the past as non-blocking when checking conflicts.

Dynamic Pricing

Evaluate all active pricing_rules for the venue in descending priority order. Apply multiplicative or additive adjustments to the base_price_per_hour. Store the final breakdown in pricing_snapshot (JSON) on the booking row so the price is locked at hold time and does not change if rules update later. The snapshot is the source of truth for disputes.

Cancellation Policy Enforcement

On cancellation request, calculate hours_until_start = (start_utc – NOW()) / 3600. Find the applicable policy row where hours_before_start is the smallest value greater than hours_until_start (i.e., highest refund tier still available). Apply refund_percentage to total_price. Issue refund via payment provider and update booking status to cancelled.

API Design

  • GET /venues?date=&start_time=&end_time=&capacity=&amenities= — search available venues
  • GET /venues/{id}/availability?date= — return booked and held slots for the day
  • POST /venues/{id}/holds — place a hold; body: start_utc, end_utc, user_id
  • POST /bookings/{id}/confirm — confirm hold with payment token
  • DELETE /bookings/{id} — cancel booking; returns refund amount
  • GET /bookings/{id} — fetch booking detail including pricing snapshot

Scalability Considerations

Availability reads are high-frequency. Cache the per-venue, per-day booked slot list in Redis with a TTL of 1-2 minutes, invalidating on any hold or confirm write. For the hold write path, use SELECT FOR UPDATE on the venue row or an advisory lock keyed by venue_id to serialize concurrent hold attempts on the same venue without locking the full bookings table.

Pricing rule evaluation is read-heavy and rarely changes. Cache compiled rule sets in memory with a cache-aside pattern invalidated on rule updates. Store pricing_snapshot as JSON in the booking to decouple future pricing changes from historical records.

Hold expiry at scale: use a Redis sorted set with booking_id scored by hold_expires_at. A sweeper process polls the set and marks expired holds in the database. This avoids full-table scans and keeps expiry latency under a few seconds.

Summary

A venue booking service prevents double-bookings through transactional interval conflict checks and a partial unique index. The hold-and-confirm flow decouples intent from payment commitment. Dynamic pricing is locked into a snapshot at hold time, and cancellation refunds are computed deterministically from a tiered policy table. Caching availability and pricing data while serializing hold writes keeps the system fast and consistent.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do you build a real-time availability calendar for venues?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Store bookings as time intervals in a table indexed on (venue_id, start_time, end_time). Query available slots by finding gaps between confirmed bookings within a requested range. Cache the availability bitmap per venue per day in Redis with a short TTL. Push invalidation events via WebSocket or SSE when a booking changes so clients reflect availability in real time without polling.”
}
},
{
“@type”: “Question”,
“name”: “What is a hold-and-confirm reservation flow?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “When a user selects a slot, create a tentative hold record with a short TTL (e.g., 10 minutes) and decrement available inventory. During the hold, the slot appears unavailable to other requesters. If payment or confirmation completes before TTL expiry, transition the hold to confirmed. If TTL expires, a background sweeper releases the hold and restores inventory. This prevents double-booking without requiring immediate payment.”
}
},
{
“@type”: “Question”,
“name”: “How do dynamic pricing rules work for venue bookings?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Define a pricing rule table with priority, condition predicates (day-of-week, time range, lead time, occupancy threshold), and a price modifier (flat add, percentage, multiplier). At quote time, evaluate rules in priority order and apply the first match or stack additive rules depending on design. Cache computed prices per venue+slot keyed on rule version. Audit log all price computations for dispute resolution.”
}
},
{
“@type”: “Question”,
“name”: “How do you enforce cancellation policies?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Attach a cancellation_policy_id to each booking at confirmation time (snapshot the policy, don't reference a mutable record). On cancellation request, compute refund amount based on (booking_start – now) against policy tiers (e.g., full refund >72h, 50% 24–72h, no refund <24h). Emit a CancellationRequested event, process the refund via the payment provider, and release inventory only after refund success.”
}
}
]
}

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

Scroll to Top