What we solved
A daily live auction for used cars dies the first time the countdown drifts. If the phone in your hand says 4 seconds and the phone next to you says 8, the dealer has a fight. If a bid placed at t=0.5s doesn’t reset the timer for everyone, the buyer who placed it watches their bid lose. Car Bidding needed a real-time bidding surface where the timer, the highest bid, and the car list were identical across iOS, Android, and the web - every frame.
The system at a glance
A React Native consumer app (iOS + Android) and a React admin web app, both talking to a FastAPI backend. FastAPI’s WebSocket support streams auction events: timer_update, new_bid, and car_rotate. Postgres holds vehicle metadata, users, and bid history. Every client reconciles state from the server, never from local math. Push notifications for auction-start route through OneSignal.
What the user experiences
- Verified buyers log in and see the Available Now feed of cars scheduled for tonight’s event.
- At event start, the Live Auction view opens. Each car is on the block for 30 seconds.
- Every new bid resets the clock to 30.
- Bid entry is one tap: minimum-bid suggestion, confirm, done. The current highest bid updates in real time.
- When the timer expires, the list rotates to the next car automatically.
- Won cars land in the Won list with pickup instructions.
How we built the pieces
The timer - server-authoritative, never client
The countdown is generated server-side and streamed over the WebSocket channel on FastAPI. Clients display what the server says. No clocks are computed locally because two phones in the same room drift apart within minutes. Bids go through REST; the server updates the timer and broadcasts to every connected client. Worst case lag is a network round-trip, not a device-specific bug.
The bid - atomic, with conflict handling
A bid includes the car ID, the user ID, and the client’s seen current-highest. The FastAPI endpoint checks the seen value against reality, rejects stale bids, and locks the Postgres row before writing. Two simultaneous bids resolve cleanly - one wins, one gets a retry hint.
React Native mobile, React web - shared patterns
Both surfaces share validation logic, API-client code, and the auction-view state machine. React Native for the buyer, React for the operator dashboard, one FastAPI backend. One team ships all three.
The admin - verify, onboard, schedule
The admin console (React web build) handles user verification before first bid, vendor and vehicle onboarding, auction scheduling, and sold/unsold tracking with re-list actions. An event can be assembled, scheduled, and live-monitored without engineering involvement.
Cross-device bids - one account, many screens
A buyer with the app on their phone and the web open on their laptop bids from either. The server deduplicates by user ID; both surfaces stay in sync on every bid, regardless of which surface placed it.
Push - OneSignal, not FCM directly
Auction-start pings go out through OneSignal. Same SDK handles iOS + Android + web, with cohort targeting so we only wake up buyers verified for a specific upcoming event.
Timing is the product, listings are decoration
Live auctions are a timing problem, not a listings problem. Most “auction app” MVPs die because they ship the catalogue and forget the timer. Car Bidding gets the timer, the bid-write path, and the cross-device consistency right. Everything else is decoration.
Results
- Daily auction events running with 30-second per-car windows.
- Bids propagate under one network round-trip to every connected device.
- Admin can onboard a vehicle, schedule the auction, and close the event without deploying code.
- Verified-user gate before first bid prevents drive-by disruption.
- React Native iOS + Android consumer app; React web admin; FastAPI + WebSocket backend.
What an engineering team should take from this
If you are shipping anything with a live timer (auctions, voting, flash sales, live-event Q&A), three things:
- The timer is server-authoritative. Never trust clients to agree with each other.
- FastAPI’s WebSocket support + sticky sessions is enough at this scale - but put the sticky-session config in from day one, or reconnects will fragment state.
- Bid writes need seen-current-highest in the payload so late bids don’t silently overwrite.
Tech stack
- Mobile: React Native (iOS + Android)
- Web admin: React
- Backend: FastAPI (REST + WebSocket)
- Database: Postgres (users, cars, bids, auction schedules)
- Real-time: FastAPI WebSocket with sticky sessions
- Push: OneSignal for auction-start alerts
- Media: AWS S3 for vehicle galleries
Screens
