Design a Scalable Bus & Subway Ticketing System from Scratch

This article walks through the full architecture of a modern bus and subway ticketing platform, covering functional and non‑functional requirements, core components, interaction flows, database schema, shortest‑path routing with Dijkstra, QR‑code generation in Go, and high‑concurrency strategies.

LouZai
LouZai
LouZai
Design a Scalable Bus & Subway Ticketing System from Scratch

1. Introduction

The author sets the scene of a typical commuter rushing to catch a bus or subway, using a mobile QR code as the "key" to board without buying a ticket on the spot.

2. Requirements Design

2.1 Functional Requirements

User registration and login via mobile app or mini‑program.

Route query for subway lines, stations, departure times and fares.

Generation of a travel QR code linked to the user’s account and payment method.

Real‑time location of subway trains.

Scan‑to‑pay at entry and exit, with automatic fare calculation.

Transaction history query.

2.2 Non‑Functional Requirements

Uneven user distribution: millions of daily users in first‑tier cities.

Peak‑time traffic: far higher load during morning and evening rush hours.

High concurrency handling.

Low latency for queries and payments.

Scalability to accommodate growth.

24/7 availability.

Security and privacy for personal and payment data.

3. High‑Level Architecture

3.1 Core Components

Frontend application (mobile app / mini‑program).

Backend services for user management, route query, QR‑code handling, order processing and payment.

Relational database cluster (MySQL) for persisting user, route and transaction data.

Push system to deliver payment results to the client.

Load balancer and message queue to improve performance.

3.2 Ticketing Flow

3.2.1 Interaction Between Mobile App and Backend

The user registers and logs in, then the backend generates a QR code that can be displayed at any time.

3.2.2 Interaction Between Mobile App and Vehicle

User scans the QR code at the entry gate; the gate forwards the code to the backend.

The backend validates the code, checks entry records and records the time and location.

After travel, the user scans the QR code at the exit gate.

The backend validates the exit, matches it with an entry record and records the exit time and location.

3.2.3 Backend Processing

Fare calculation based on entry/exit stations and city‑specific rules.

Recording the fare and deducting it from the user’s payment method (e.g., Alipay or WeChat Pay).

Storing the trip record for user review and settlement.

Sending a notification to the user when the fare is deducted.

Database interactions for all read/write operations.

4. Detailed Design

4.1 Database Schema

User table (User): user ID, phone, password, payment method, creation time.

QRCode table (QRCode): QR code ID, user ID, city ID, generation time, expiry, QR data.

Vehicle table (Vehicle): vehicle ID, plate or train number, type (bus/subway), scanner device ID.

TripRecord table (TripRecord): record ID, user ID, vehicle ID, board/alight times, start/end stations.

PaymentRecord table (PaymentRecord): payment ID, trip record ID, transaction time, amount, method, status.

4.2 Shortest‑Path Query

The system stores station coordinates and uses Dijkstra’s algorithm to find the shortest route. The step‑by‑step traversal is presented as an ordered list:

Select A (distance 0). Adjacent nodes B (6) and C (3). Choose C.

Select C (distance 3). Update distances: B remains 6, D becomes 7 (A‑C‑D).

Select B (distance 6). No shorter path to D.

Select D (distance 7). Adjacent E (2) and F (3). Choose E.

Select E (distance 9). Update F via D‑F = 10.

Select F (distance 10). All nodes visited; final distances: A=0, C=3, B=6, D=7, E=9, F=10.

Station coordinates are encoded as strings and stored using Redis GeoHash for fast nearby‑station lookup.

4.3 QR Code Management

QR codes are generated with the Go library github.com/skip2/go-qrcode:

import "github.com/skip2/go-qrcode

func main() {
    qr, err := qrcode.New("https://mp.weixin.qq.com", qrcode.Medium)
    if err != nil {
        log.Fatal(err)
    } else {
        qr.BackgroundColor = color.RGBA{50, 205, 50, 255} // background
        qr.ForegroundColor = color.White                     // foreground
        qr.WriteFile(256, "./wechatgzh_qrcode.png")
    }
}

The generated QR data is stored in a dedicated table with fields: QRCodeID, UserID, QRData, GenerationTime, ExpiryTime. High‑concurrency handling includes:

Load balancing across multiple servers.

Caching generated QR codes in Redis to avoid repeated generation.

Rate limiting (e.g., max 5 QR codes per minute per user) to prevent abuse.

Security measures such as encrypted storage protect user privacy and payment information.

5. Future Directions

Smart boarding with facial recognition and automatic fare deduction.

Big‑data analytics on travel data to improve services.

Continuous focus on user experience, performance and security as demand grows.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

backend designQR codeticketing systemdatabase schemasubwaybus
LouZai
Written by

LouZai

10 years of front‑line experience at leading firms (Xiaomi, Baidu, Meituan) in development, architecture, and management; discusses technology and life.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.