FIELD NOTE · BOTS · SCALING · MAY 2026

Polling vs Event-Driven

Polling is the easiest way to make a bot feel "alive": keep checking, anything new, anything new. It works. But when you scale up the number of bots and APIs start counting every request, polling quietly becomes expensive. This is the story of switching, and when polling is actually still fine.

PATTERN: 2 TRIGGER: Cost RESULT: Event-driven LEVEL: Scaling
— TL;DR

Polling = check repeatedly at fixed intervals, pay for every request even when nothing changed. Event-driven = only work when an event arrives (command, callback, webhook). For bots waiting on user actions, event-driven is far more efficient and responsive. But polling still makes sense for things that don't have push events: on-chain prices, transaction status, data sources without webhooks. The key isn't "polling is bad" — it's knowing when it's the wrong tool.

01

Polling: Easy, Until It Isn't

The polling pattern is intuitive. A loop that asks the API every few seconds: "any updates?". If yes, process them. If no, sleep, repeat. For one small bot, this is perfect — easy to understand, easy to debug.

# Polling pattern while running: updates = api.get_updates() # request every loop for u in updates: handle(u) sleep(POLL_INTERVAL) # e.g. 5 seconds

The problem shows up at scale. A 5-second interval means 12 requests per minute, 17,280 per day — per bot. And most of those requests return empty: no updates, but still counted. Multiply by ten bots, and the wasted request count gets large.

02

Hidden Costs of Polling

There are three costs that aren't obvious at first:

Cost 1

Empty Requests

Most polls return without new data. Every empty request still counts toward rate limits and API quotas.

Cost 2

Interval Latency

An update that arrives right after a poll has to wait until the next cycle. A 5-second interval = the bot can be 5 seconds late.

Cost 3

Loops Without Backoff

If the API starts erroring, the polling loop keeps hammering at the same interval. Without backoff, you're hitting an already struggling API harder.

The third cost is the most dangerous. I once found a polling loop that, when the API errored, burned through credits faster because each error was instantly retried without delay. Polling that "quietly runs" turns into polling that "quietly drains quota".

03

Event-Driven: Work When There's Work

The event-driven pattern flips the logic. The bot doesn't keep asking "what's up?". It sits idle, and only moves when an event arrives — a command from a user, a button click, or a webhook from outside. Zero requests when nothing is happening.

# Event-driven pattern app.add_handler(CommandHandler("check", do_check)) app.add_handler(CallbackQueryHandler(on_button)) # bot idles until an event arrives; # zero requests when idle app.run()

For bots waiting on user actions — the majority of my Telegram bots — this immediately drops the request count drastically. A bot that was doing 17,280 polls per day now only runs when users actually type commands. The rest of the time, it's idle, zero cost.

Response is also instant. No interval latency — event arrives, immediately processed. My unified scraper is now fully event-driven: not a single background polling loop, everything runs from handlers.

04

When Polling Is Still Right

This is the part that often gets missed. Event-driven wins for things that have push events. But many data sources don't have that — on-chain token prices, transaction confirmation status, APIs without webhooks. For those, polling is the only way.

The difference is, proper polling has brakes:

# Safe polling (for sources without webhooks) while running: try: data = poll_source() process(data) backoff = BASE_INTERVAL # reset on success except Exception: backoff = min(backoff * 2, MAX) # increase on error sleep(backoff)

So the decision isn't "polling vs event-driven" as right-wrong. Ask one thing: can the source notify me when something changes? If yes (user commands, webhooks), event-driven. If no (prices, on-chain status), polling — but with backoff and a baseline, not a naked loop.

— RULE OF THUMB

Default to event-driven for anything waiting on users. Use polling only for sources that don't have push, and when polling, always add backoff on errors plus a baseline so you don't flood on the first run. Polling without brakes is the source of two other writeups I have here.