Web Development

Real-Time Bidding: Keeping an Online Auction Fair in the Final Seconds

Every online auction looks fair until the last thirty seconds of a popular lot. Here is how we handle the problems that decide whether bidders trust the result: whose clock counts, simultaneous bids, sniping and soft close, pushing updates instead of polling, and load at the moment it matters.

Polished brass auction gavel resting on its sound block

Every online auction looks fair until the last thirty seconds of a popular lot. Then two bidders click within milliseconds of each other, one on a fast office connection and one on a phone in a car park, the countdowns on their screens disagree by a second, and someone is convinced they were robbed. Fairness in an auction is not a feeling. It is a set of rules the system enforces the same way every time, and can prove afterwards.

We rebuilt the live saleroom for a fine art and collectibles auction house, and its live auction bidding platform now holds over 11,000 concurrent bidders on a single sale with a p95 bid latency of 42 ms. It runs on Elixir, Phoenix and Go, built through our Go, Rust and Elixir development service. These are the problems that shaped it, and they apply to any real-time bidding system, from timed online sales to live salerooms.

The Server's Clock Is the Only Clock

A bid is placed when the server receives it, not when the bidder clicked. That sounds harsh, but it is the only rule that can be applied consistently: device clocks drift, phones sleep, and any timestamp sent from a browser can be edited. Every bid on the auction house's platform is written to an append-only history with a server timestamp, and that record is what settles disputes.

The countdown on screen still matters, because bidders act on it. Rather than trusting the device clock, the client should estimate its offset from the server: send a ping, note the server time in the reply and the round-trip time, and correct the display. Repeat it periodically, because the offset changes when a phone moves between networks. The display is then accurate to a fraction of a second for most users, and the server-receipt rule belongs in the published terms so nobody is surprised by it.

One Decision-Maker per Lot

The classic concurrency bug in auction software goes like this: two bids read the same current price, both are judged valid, and both are accepted. Database locks can prevent it, but locks slow down under exactly the load where they matter most.

The cleaner design is a single decision-maker per lot. On the auction house's platform, a dedicated Go service validates every bid, applies increment rules and reserves, resolves proxy bids and extends lots when late bids arrive. Because one service processes bids in a defined order, two bids that arrive almost simultaneously always resolve the same way. Separating that engine from the real-time layer also means each can be tested on its own: the engine for correctness, the channels for throughput.

Two smaller rules prevent a lot of support tickets:

  • Make bids idempotent. Each bid carries a client-generated ID, so a double click or a retry after a dropped connection cannot place two bids.
  • Bid on a price, not on "next increment". The request says "I bid 4,200". If the price moved before it arrived, it is rejected with the new price, rather than silently placing a higher bid than the bidder intended.

Soft Close: Ending Sniping Without Ending the Sale

With a hard close, the winning strategy is to bid in the final second, and the winner is whoever has the lowest latency. That rewards connection speed rather than willingness to pay, and sellers lose money because nobody gets the chance to respond.

A soft close fixes this. Any bid in the final minutes extends the lot, by two minutes in a hypothetical timed sale, so every bidder always has time to answer. The auction house's bid engine applies these anti-sniping extensions within the same ordered process, so an extension and the bid that triggered it can never disagree.

Decide the details explicitly and publish them: the length of the window, whether extensions are capped, and whether one lot's extension holds up the next in a staggered close. Bidders accept rules they dislike far more readily than rules they did not know about.

Push, Do Not Poll

The auction house's previous platform polled for updates every few seconds. During popular sales, online bidders saw stale prices, lost lots they believed they had won, and the auctioneer could not fully trust the screen. Polling is unfair by nature: a bidder whose poll lands just after a bid sees it almost at once, while another waits several seconds.

The rebuilt saleroom keeps a WebSocket connection open to every participant through Phoenix channels. When the engine accepts a bid it is broadcast to everyone at once, along with lot changes and auctioneer actions, and presence tracking shows genuine bidder counts. The auctioneer's console receives the same updates as every bidder and shows floor, phone and online bids in one view, so all three compete under the same rules.

For smaller sales the same pattern works well in Node.js, which we cover in Node.js for real-time features and build through our Node.js development service. Elixir earned its place here because of the connection counts involved, a trade-off explained in when specialist languages are worth it.

Load: The Final Minute Is the Real Test

Auction traffic is the opposite of steady. Thousands of people watch quietly, then act at once as a lot closes, and if a connection drops they all reconnect together. Average load tells you nothing; the final minute of the most popular lot is the number to design for.

  • Load test with simulated crowds well beyond your busiest sale, including mass reconnections. The auction house's platform was tested this way before launch.
  • Keep the decision path short. Accepting and broadcasting a bid should never wait on something slow, such as sending an email.
  • Watch latency, connection counts and bid throughput live. The platform runs Prometheus and Grafana dashboards for exactly this.
  • Rehearse with real people. Auctioneers ran mock sales on the new console before any real lot was sold on it.

Prove It Afterwards

Fairness that cannot be demonstrated is only a claim. An append-only bid history, where records are never updated or deleted, lets you replay a contested lot exactly as it happened. On the auction house's platform, disputed bids are now resolved from that record rather than from screenshots, and it meets the house's record-keeping obligations. Our database development team designs audit trails like this so they stay quick to query as they grow.

If you run online or hybrid auctions and the closing seconds generate complaints, send us your sale rules and the size of your busiest sale. We will tell you which of these problems you are likely to have and what fixing them involves.

Frequently Asked Questions

How long should a soft-close extension be?

Long enough for a bidder on a slow connection to see the new price, decide and respond, which in practice usually means somewhere between one and five minutes for timed online sales. Shorter windows keep a sale moving but favour fast connections; longer ones give everyone a fair chance but can drag a catalogue out. Live salerooms work differently because the auctioneer controls the pace, so extensions there are a matter of fair-warning practice rather than a timer.

How should two identical maximum bids be resolved?

The usual rule is that the maximum received first wins, at that amount, because it was placed earlier and the bidder committed to it first. Whatever rule you choose, the bid engine should apply it in one place and the terms should state it plainly. Showing the losing bidder that an earlier bid at the same amount exists avoids the impression that the system favoured someone.

What happens if a bidder loses their connection in the final seconds?

Any bid the server received before the drop stands; anything not received was never placed. Good clients reconnect automatically, fetch the current state of the lot and show clearly whether the bidder’s last bid was accepted. Encouraging bidders to set a maximum bid on important lots is the best protection, because a proxy bid keeps working on the server whatever happens to their connection.

Can phone and saleroom bidders compete fairly with online bidders?

Yes, if every bid goes through the same engine regardless of where it came from. Staff enter room and phone bids on the auctioneer’s console, and the engine applies the same increments, reserves and ordering to them as to online bids. The auctioneer still controls the pace of a live sale, but the record of who bid what, and when the server received it, is identical for every channel.

How many concurrent bidders can a real-time auction platform support?

It depends on the technology and how the real-time layer is built rather than any fixed limit. The platform we built for an auction house holds over 11,000 concurrent bidders on a single sale without manual scaling. The way to know your own figure is to load test with simulated crowds larger than your busiest sale, including everyone reconnecting at once, and to monitor connection counts during real sales.

How We Can Help

Services Related to This Article

The work we do with the technologies discussed above — with pricing, process and real examples on each page.

  • Go, Rust & Elixir Development

    Services where throughput, latency or concurrency matter — Go APIs and workers, Rust for performance-critical code, Elixir and Phoenix for real-time systems.

  • Node.js Development

    Node.js back-end development: Express and NestJS APIs, real-time services, background jobs, MySQL/MongoDB data layers and Redis caching.

  • Database Development

    Schema design, query and index tuning, migrations and search infrastructure across MySQL, PostgreSQL, MongoDB, Redis and Elasticsearch.

  • API Development & Integration

    Custom REST and GraphQL API development plus integrations with CRMs, ERPs, payment gateways, shipping carriers and marketing platforms.

In Practice

Case Studies on This Topic

Projects where we put these ideas to work, each with a live demo.

  • Antique Watch — Ridgemont Auctions
    • Elixir
    • Phoenix Channels
    • Go

    Real-Time Web Platform

    Live Auction Bidding Platform

    An Elixir and Go platform holding 11,000+ concurrent bidders per sale with bid latency measured in milliseconds.

  • Semi trucks line up to pick — Northline Freight
    • React
    • TypeScript
    • Node.js

    Web Application Development

    Freight Dispatch & Driver Tracking Platform

    A live dispatch board built in React and Node.js that replaced a spreadsheet-and-phone workflow for 120 drivers.

  • Guy Man — Altura Tours
    • Vue 3
    • Nuxt
    • TypeScript

    Booking Platform Development

    Adventure Tour Booking & Itinerary Platform

    A Vue 3 and Nuxt platform handling departures, seat availability, deposits and guide assignments for multi-day tours.

Keep Reading

Let’s Connect

Let’s Build Something Amazing Together

Share your project details and our experts will get back to you within one business day with the best solution — free consultation, no obligation.