Developing a Mobile Application for Event Ticket Sales
The main technical pain of a ticketing app is competitive selling. When 500 people simultaneously press "Buy" for the last 10 seats, the app must correctly handle it without double bookings, freezes, or incorrect statuses. This is not a frontend task — we design the server side with pessimistic locking and queue-based reservation. The mobile client is ready for the "seat already taken" scenario with clear UX and instant feedback.
We create turnkey ticketing applications: from interactive seating maps to a controller app. Our experience in this field is 5+ years with over 20 successful projects. We guarantee fault tolerance under load and compliance with App Store Review Guidelines (Section 4.2/5.1).
Seat Reservation and Race Conditions
The standard scheme: a user selects a seat → the server creates a reservation with a TTL of 10 minutes → the user pays → the reservation is converted into a ticket. If payment is not completed within 10 minutes, the seat is released.
On the mobile client, the reservation timer is a CountDownTimer (Android) or Timer.scheduledTimer (iOS) synchronized with the server TTL. Not from the moment the button is pressed on the client, but from reservation.expiresAt from the server response. Time zone differences and device clock drift will kill the UI timer if synced incorrectly.
// Android: synchronize timer with server expiresAt class ReservationViewModel : ViewModel() { private val _timeLeft = MutableStateFlow(0L) val timeLeft: StateFlow<Long> = _timeLeft fun startCountdown(expiresAt: Instant) { viewModelScope.launch { while (true) { val remaining = ChronoUnit.SECONDS.between(Instant.now(), expiresAt) if (remaining <= 0) { _timeLeft.emit(0) onReservationExpired() break } _timeLeft.emit(remaining) delay(1000) } } } } When the reservation expires, we don't just show an error. We automatically suggest the nearest available seats if a real-time stream (WebSocket or SSE) supports it.
How to Avoid Double Booking?
Double booking is a classic problem with parallel requests. We solve it using SELECT FOR UPDATE in PostgreSQL or a distributed lock at the reservationId + seatId level in Redis. An alternative is ON CONFLICT DO NOTHING when inserting the reservation. On the client, we duplicate the lock: the "Buy" button is deactivated after the first press, and repeat requests are sent with a unique idempotency key. These methods reduce the likelihood of conflict by 3 times under peak loads.
Seating Map and Interactive Venue
For concert halls and theaters, an interactive seating map is needed. On iOS — UICollectionView with a custom UICollectionViewLayout or Canvas in SwiftUI for venues with non-standard geometry. On Android — Canvas + GestureDetector for pinch-to-zoom.
The map is loaded as JSON with coordinates of each seat, section, and status (available, reserved, sold, disabled). Real-time status updates — via WebSocket: as soon as someone reserves a seat, all connected clients receive { type: "seat_reserved", seatId: "A-15" }.
| Platform | Technology | Advantage |
|---|---|---|
| iOS | SwiftUI Canvas + UIKit | Native performance, bezier support |
| Android | Canvas + GestureDetector | Smooth pinch-to-zoom, custom geometry |
| Cross-platform | react-native-svg / flutter_svg | Single codebase, fast SVG rendering |
| Update Technology | Latency | Server Load |
|---|---|---|
| WebSocket | ~100 ms | Low (persistent connection) |
| Polling (every 5s) | ~5 s | High (frequent requests) |
SVG venue maps are a popular choice for cross-platform solutions. React Native with react-native-svg or Flutter with flutter_svg + GestureDetector render SVG with tap-ability on elements.
Why is Real-Time Update Important?
Without WebSocket, a user might select a seat that is already taken. This leads to frustration and lost sales. We implement WebSocket connections with automatic reconnection (exponential backoff). As soon as a reservation changes, the client receives an event and redraws the map. For offline mode, we cache the last state and show a warning.
Electronic Tickets and Entry Validation
Each purchased ticket gets a unique UUID and a QR code generated on the server. There's no need to store a secret in the QR — just ticketId; verification is done on the server when scanned. The QR should not be a static image in a PDF — we generate it dynamically from ticketId on the client using ZXingObjC (iOS) or zxing-android-embedded, to avoid storing raster images in the database.
The controller app — a separate screen or a separate app with a scanner using AVFoundation/CameraX, POST to /tickets/{id}/validate, response: valid / already_used / invalid. We cache validated ticketId locally on the controller device in case of no internet — synchronization after connectivity is restored.
Payment Flow
Acquiring — YooKassa, CloudPayments, or Stripe. On iOS, additionally Apple Pay (PKPaymentRequest), on Android, Google Pay. For the B2B segment — invoicing by email with payment via SBP (Fast Payment System).
After payment, immediate sending of a PDF ticket via email through SendGrid/Postmark and a push notification via FCM/APNs with a deep link to the "My Tickets" screen.
How We Do It: Stack and Process
We start by analyzing your business requirements and technical constraints. Our typical stack includes PostgreSQL, Redis, Java/Kotlin on the backend, Swift/Kotlin for native mobile apps, and WebSocket for real-time updates. We follow a modular architecture to ensure scalability and maintainability.
Example of controller caching configuration
CREATE TABLE ticket_validations ( ticket_id UUID PRIMARY KEY, validated_at TIMESTAMP DEFAULT NOW() ); When connectivity is restored, we send all ticket_id to the server for synchronization.
Typical Errors
- Double booking. Without
SELECT FOR UPDATEor distributed locking, two users could create a reservation for the same seat. - QR on screenshot. Do not implement screenshot protection. The QR should be single-use upon validation.
- Time synchronization. Always use server-side
expiresAt, not local time.
Stages and Timeline
Data model design → seating map → reservation with TTL → payment → electronic tickets → controller app → load testing (1000+ concurrent users).
6–10 weeks for a full-featured app with an interactive seating map and real-time updates. The cost is calculated individually after requirements analysis.
Get a consultation on ticketing architecture. Order development for your project — we'll make an estimate within 1 day.







