In a push architecture the side that has the data starts the transfer: a server sends an update the moment it exists. In a pull architecture the side that needs the data starts it: a client or consumer asks when it is ready. The real difference is who controls timing and rate, and the same choice appears at every layer, from a live score on your phone to Kafka consumers, news feeds, Prometheus and payment webhooks.
Picture it first: the newspaper and the news stall
Every morning a hawker drops the newspaper at your door. You never ask; it arrives as soon as it is printed. That is push. It is fast and effortless for you, but the hawker must keep a list of subscribers and walk every lane, and when you are away the papers pile up on the doormat.
Your neighbour prefers the stall at the corner. She walks down when she has time and buys whatever is latest. That is pull. The stall keeps no list and never worries about who is travelling. She decides when news reaches her, but some days she walks down for nothing, and she always reads it later than you.
Your phone does both. A WhatsApp message lights up the screen the moment it is sent: push. An email app set to fetch every 15 minutes is pull, and so is dragging a feed down to refresh.
Map it back: the hawker is a server pushing over a held connection or a notification service, the subscriber list is connection and subscription state, the pile on the doormat is the backlog for offline consumers, the walk to the stall is a poll, and the wasted walk is an empty response.
Push and pull, precisely
Strip away the technology and one question remains: who starts each transfer?
- Push: the producer (a server, broker or publisher) initiates. When new data exists, it sends it to the consumers it knows about. The producer decides when data moves and how fast.
- Pull: the consumer initiates. It asks “anything new since X?” when it is ready, and the producer answers with what it has, possibly nothing. The consumer decides when and how fast.
Everything else follows. The initiator must know where the other side is, so a pusher keeps a list of subscribers and their connections or URLs, while a puller only needs the producer’s address. And the side that does not set the rate is the one that can be overwhelmed: with push, a slow consumer gets swamped; with pull, it simply falls behind while the backlog waits at the producer.
Three clarifications prevent most confusion:
- You choose per hop, not per system. Kafka producers push to brokers and consumers pull from them; Kafka’s design notes call this the traditional design shared by most messaging systems.
- It is not synchronous versus asynchronous. That pair is about whether the caller waits for the result (synchronous vs asynchronous communication). A poll is a synchronous request that pulls; a webhook is a synchronous HTTP call that pushes.
- The transport is not the pattern. Long polling sends pull-shaped requests but delivers with push-like latency. A WebSocket is just a pipe on which either side can push.
What each side gains and what it pays
| Push | Pull | |
|---|---|---|
| Latency | As soon as data exists, plus one network transit | Half the polling interval on average, a full interval at worst |
| Wasted work | None when nothing changes, apart from keep-alives | Every empty poll still pays headers, auth and a lookup |
| Who sets the rate | Producer; the consumer needs flow control (prefetch, credits) | Consumer; a slow one falls behind instead of falling over |
| State on the server | Subscriber list, open connections, delivery status per consumer | Little or none; the consumer remembers its position |
| Offline consumers | Producer must buffer, retry or drop | Data waits; the consumer catches up from its last position |
| Replay | Needs a separate store and resend logic | Re-read from an older offset or cursor |
| Reachability | Producer must reach the consumer: hard through NAT, firewalls, sleeping phones | Consumer reaches out from anywhere that can make a request |
| Load grows with | Change rate × subscribers; bursty under fan-out | Consumers × poll frequency, even when nothing changes |
| Failure detection | Silence is ambiguous: dead, or nothing to say? | A failed request is a clear signal |
Read the table as one rule: push wins when data changes less often than it is checked and freshness matters; pull wins when the consumer must control its rate, may be offline, or needs to replay.
The arithmetic: what polling really costs
Take a food-delivery app with 1 million users who have it open, each checking for order updates every 30 seconds.
- Load: 1,000,000 ÷ 30 = 33,333 requests/s.
- Waste: if each user’s data changes about once every 10 minutes, that is 600 ÷ 30 = 20 polls per change, so 19 of 20 come back empty: 95% wasted, about 31,700 requests a second that each carry headers, a token check and a lookup.
- Delay: an update lands at a random moment in the 30-second gap, so the average wait is 15 s and the worst is 30 s. To bring the average to 1 s you must poll every 2 s: 1,000,000 ÷ 2 = 500,000 requests/s, 15 times the load for the same information. RFC 6202 states the general law: with short polling, server and network cost depend strongly on the latency you accept.
Now push the same updates over held connections.
- Connections: 1M held open. Assume about 20 KB each for TLS state and buffers (measure your own stack): 1M × 20 KB ≈ 20 GB of memory across the gateway fleet. At 50,000 connections per gateway that is 20 gateways, plus headroom for reconnect storms after a deploy.
- Messages: only on change: 1,000,000 ÷ 600 ≈ 1,667 messages/s.
- Keep-alives: idle connections still need a ping to detect dead peers and stop proxies and NATs from dropping them; the SSE standard suggests a comment line every 15 seconds or so. One ping every 30 s is 33,333 frames a second, the same count as the polls, but a few bytes each with no auth and no database.
Long polling sits between. Each client holds one request and the server answers when data arrives or the request times out. RFC 6202 says average latency is close to one network transit, and that 30 s is the safer timeout because proxies cut longer requests (experiments reached 120 s). Notice the catch: with updates every 10 minutes and a 30 s timeout, most requests still end in an empty timeout, so you make about 33,333 requests/s again. Long polling buys latency, not fewer requests, unless you can stretch the timeout; at 120 s it drops to about 8,333 a second.
Compare the change rate with the check rate
If data changes less often than consumers check, push wins, because most checks would find nothing. If data changes faster than consumers can use it (per-second metrics, logs, clickstreams), pull wins: the consumer takes a batch whenever it is ready, and nothing is sent that it can’t absorb.
The same choice at eight layers
Clients and servers: polling, long polling, SSE, WebSockets, mobile push
Short polling is pure pull. Long polling is pull that behaves like push. Server-sent events push over one never-ending HTTP response and resume after a drop: the browser reconnects after a server-tunable retry delay and sends the last event’s ID in a Last-Event-ID header. WebSockets push both ways on one socket. Mobile push through APNs or FCM reaches an app that isn’t even running, but carries at most 4 KB. The pipe-by-pipe detail is in long polling, SSE and WebSockets and the patterns in real-time updates. Here the point is the trade: a live score needs push, while checking an IRCTC-style PNR status once is a perfectly good pull.
Brokers: Kafka consumers pull, RabbitMQ pushes
Kafka’s designers explain their choice directly. In a push system the broker controls the rate, so a consumer slower than the producers is overwhelmed, “a denial of service attack, in essence”; with pull, “the consumer simply falls behind and catches up when it can.” Pull also batches for free, because a consumer takes everything after its offset in one fetch. A naive pull loop busy-waits on an empty topic, so the fetch is a long poll: the broker holds it until fetch.min.bytes of data exists (default 1 byte) or fetch.max.wait.ms passes (default 500 ms). Raise the minimum to 64 KB and you trade up to half a second of latency at low traffic for bigger batches.
RabbitMQ is push-first: registered consumers receive deliveries, and the prefetch count (basic.qos) caps unacknowledged messages, which is push with explicit flow control. See publish/subscribe for push and pull subscriptions, and backpressure for why Kafka lag is the healthy form of overload.
Feeds: fan-out on write versus fan-out on read
Fan-out on write pushes a new post’s ID into every follower’s home timeline, so opening the app is one list read. Fan-out on read pulls at read time: gather recent posts from every account you follow and merge them. The costs land in different places:
- Push pays per follower. A post from an account with 10M followers is 10M timeline writes; at a fleet-wide 1M writes/s that is 10 s of all fan-out capacity for one post.
- Pull pays per view. A reader following 300 accounts costs 300 lookups and a merge per open; at 100,000 feed opens a second that is 30M reads/s.
So real feeds split by author: push for ordinary accounts, pull for the few with huge followings. The news feed design works through the cost model and Design Twitter the read-time merge.
Monitoring: Prometheus scrapes, short jobs push
Prometheus pulls. It discovers targets and fetches each one’s /metrics page every scrape interval (1 minute by default; 15 s is common). Its FAQ gives three reasons: you can start extra monitoring instances, say on a laptop, without touching targets; you can tell more reliably when a target is down; and you can open a target’s metrics in a browser. Every scrape writes up = 1 or up = 0, so a dead target is a visible fact, not a silence. The project is candid that pull is only “slightly better” and not a major point.
The exception is work that may finish before the next scrape. The Pushgateway guidance limits pushing to service-level batch jobs, because the gateway becomes a single point of failure and bottleneck, you lose up, and it keeps pushed series forever unless you delete them. Push-based agents (StatsD over UDP, OpenTelemetry’s OTLP exporters) suit short-lived processes and hosts behind NAT, but the collector must absorb whatever arrives. The arithmetic stays tame: 10,000 hosts scraped every 15 s is 10,000 ÷ 15 ≈ 667 scrapes/s. More in metrics, monitoring and alerting; heartbeats is the same pair for liveness.
Integrations: webhooks versus polling an API
A webhook pushes an event to your HTTPS endpoint the moment it happens. Polling the provider’s API pulls. Webhooks are fast and cheap, but delivery is at-least-once and unordered: Stripe retries for up to three days with exponential backoff, doesn’t guarantee order, may deliver the same event twice, and asks you to return 2xx quickly and process afterwards. So the handler must be idempotent on the event ID, and something must catch what never arrived. Polling works without a public endpoint and is simple, but costs latency and quota. The usual answer is both; see the hybrids below and designing a webhook delivery service for the sender’s side.
Deployment and config: GitOps agents pull
In push-style CD, the pipeline holds cluster credentials and applies changes. In GitOps, an agent inside each cluster pulls desired state from Git and keeps reconciling; the OpenGitOps principles name “pulled automatically” as one of four. You gain no inbound access or cluster credentials in CI, and drift is undone on the next pass. You pay in delay: Argo CD polls Git every three minutes by default, so it accepts a webhook to skip the wait. Its high-availability guide also warns that many applications refreshing on the same timer spike the repo server, and adds jitter. See designing a CI/CD deployment system and control plane vs data plane.
CDNs: push zones and pull zones
A pull CDN fetches from your origin on the first miss at each edge and caches by TTL: nothing to manage, only popular files occupy edges, but first viewers wait and a new release can stampede the origin. A push CDN has you upload files ahead of time: no origin hits and predictable launch-day behaviour, but you manage uploads, pay to store everything, and must delete or overwrite stale copies yourself. See content delivery networks.
Caches: write-through versus cache-aside
Write-through pushes each new value into the cache on write: reads hit and are fresh, but you pay to cache data nobody reads. Cache-aside pulls on a miss: the cache holds only what is read, but first reads are slow, values are stale until a TTL or invalidation, and a hot key can stampede. Details in caching strategies.
Hybrids: how real systems combine them
At scale, pure push and pure pull are rare. The common shape is: push a small signal, pull the data, and run a periodic pull as a safety net.
- Tickle, then fetch. A notification says “order 812 changed”; the app fetches the order. Mobile payloads are capped (4 KB on APNs, 5 KB for VoIP; 4,096 bytes on FCM), and FCM’s docs describe content-free “ping” messages that tell an app to sync. Pushes can also be lost or merged: FCM keeps only four collapse keys per device, and stores at most 100 non-collapsible messages for an offline Android device; past that it discards them all and signals the app to do a full sync. Apple treats background notifications as low priority, doesn’t guarantee delivery and advises no more than two or three an hour. So the app must also pull on open.
- Thin webhooks. Stripe now recommends “thin” events that carry only the event type and object ID; your code fetches the current object. Duplicates and reordering stop mattering, because you always act on the latest state.
- Webhooks plus reconciliation. A job pulls “payments still pending after 5 minutes” from the gateway and fixes any the webhook missed. Stripe’s own recovery advice for an outage is to list events after the last one you processed: a pull.
- Long polls that act like push. Browser long polling and Kafka’s fetch both hold a pull request open until data arrives.
- Streams that resume like pulls. SSE events carry an
id; on reconnect the browser sendsLast-Event-ID, and the server replays from there. - Push to most, pull from a few. The celebrity split in feeds, above.
- Poll, with a push shortcut. Argo CD polls every three minutes and treats a Git webhook as “look now”.
In the interview
One sentence covers most hybrids: “The push is a hint; the pull is the truth.” Then name the safety net: “and a periodic pull reconciles anything the pushes missed.”
Choosing: the decision guide
Ask these six questions, in order:
- How fresh must it be? Seconds point to push or long polling; minutes make plain polling fine.
- Which is faster, the change rate or the check rate? Rare changes, frequent checks: push. Constant changes the consumer samples: pull.
- Can the producer reach the consumer? A public endpoint, an open connection, a phone that may be asleep, a host behind NAT.
- Who must control the rate? If a burst could swamp the consumer, pull, or push with prefetch or credits.
- What happens to a missed update? If it must not be lost, keep it durable and let the consumer pull from its last position.
- How many consumers, and how many per producer? Big fan-out needs a fan-out tier for push, or caching for pull.
| If… | Pick | Example |
|---|---|---|
| Millions need the same update within seconds | Push over SSE or WebSockets via a fan-out tier | Live cricket score to 20M viewers |
| The user must hear about a change while the app is closed | Push a notification as a hint, pull on open | Food order “out for delivery” |
| A partner system must learn about an event | Webhook push plus a reconciliation pull | Payment status from a gateway |
| High-volume consumers work at their own pace and may replay | Pull from a durable log | Log and clickstream pipeline on Kafka |
| You monitor thousands of long-lived hosts | Pull (scrape) | Metrics from 10,000 hosts with Prometheus |
| A job may end before the next scrape | Push | Nightly batch job to a Pushgateway |
| Minutes of staleness are fine | Plain polling, cached | Dashboard refreshed every 5 minutes |
| Thousands of agents must converge on one desired state | Pull with jitter, optionally a push hint | Config rollout to 50,000 agents |
| Typical authors have hundreds of active followers | Push at write time | Home timelines |
| One author has millions of followers | Pull at read time for that author | Celebrity posts in a feed |
| Both sides talk, with low latency | Two-way push on a WebSocket | Chat, a chess move, live driver location |
Pick push for the live score, chat messages in a WhatsApp-style app, the driver’s position during a ride, an OTP or order alert, a payment webhook and a brokerage price ticker.
Pick pull for Kafka consumers in a log pipeline, Prometheus scrapes, GitOps agents, a BI dashboard, an app refreshing its catalogue on launch, a one-off PNR status check and cache-aside product pages.
Use the hybrid for food-order status (tickle plus fetch), feeds (push to most, pull from celebrities), payments (webhook plus reconciliation) and GitOps (poll plus webhook shortcut). This is the default for anything user-facing at scale; the notification system design builds it out.
Going deeper: traps and failure modes
“Push is real-time, so it is always better”
Push is faster only when the consumer is connected and keeping up. It costs held connections, heartbeats, a subscriber registry and a fan-out tier, and it is no help when nobody is watching: pushing every like to 1M idle tabs wastes more than polling once a minute would. The live comments design shows the fan-out tier push needs at scale.
Forgetting the offline client
A push sent to a closed laptop, a phone in a tunnel or a webhook endpoint mid-deploy is lost unless someone stores it. Every push design needs an answer to “what if they weren’t there?”: a durable backlog, a sequence number the client resumes from, and a pull on reconnect.
Push without backpressure
A pusher that ignores the consumer’s pace turns a slow consumer into a crash, then a retry storm. Bound it with prefetch, credit windows or a per-subscriber buffer that drops or disconnects when full; see backpressure and load shedding.
Synchronised polling herds
Pull spreads load only if clients poll at random moments. After a deploy, an outage or a cron schedule, they line up: 50,000 agents polling every 60 s average 833 requests a second, but if all fire on the minute, 50,000 arrive in one second, 60× the average. Add random jitter to every interval and to every retry (backoff with jitter), and return 304 Not Modified via ETags so an unchanged answer is cheap.
Cheat sheet
| Term | Remember it as |
|---|---|
| Push | Producer initiates; lowest latency; must track subscribers and pace them |
| Pull | Consumer initiates; consumer sets rate; easy replay; delay ≈ interval ÷ 2 |
| Polling cost | Clients ÷ interval: 1M every 30 s = 33,333 req/s, ~95% empty if data changes every 10 min |
| Long polling | Pull that acts like push; one held request; ~30 s timeout |
| SSE resume | Event id, then Last-Event-ID on reconnect |
| Kafka fetch | Pull with a long poll: fetch.min.bytes 1 B, fetch.max.wait.ms 500 ms |
| Prometheus | Pulls and records up; Pushgateway only for batch jobs |
| Fan-out | Write pays per follower, read pays per followee; split by author |
| Mobile push | 4 KB payload, may be dropped or throttled; a hint, not a record |
| Default hybrid | Push a signal, pull the data, reconcile with a periodic pull |
How to say it in the interview
“The core question is who starts the transfer. For the live score I’d push over server-sent events through a fan-out tier, because 20 million clients polling every 5 seconds is 4 million requests a second, almost all empty. Each event carries a sequence number, so a reconnecting client resumes with Last-Event-ID. For order status I’d push a small notification and have the app pull the order on open, because notifications can be dropped or throttled. Internally, consumers pull from Kafka, so a slow one builds lag instead of falling over. I pay for push with held connections and heartbeats, which is fine because messages only flow on change.”
Why did Kafka choose pull for consumers?
With push the broker sets the rate, so a consumer slower than the producers is overwhelmed; with pull it falls behind and catches up, and takes everything since its offset as one batch. To avoid busy-waiting, the fetch is a long poll that waits for fetch.min.bytes or fetch.max.wait.ms.
Is long polling push or pull? Mechanically pull: the client sends every request. Behaviourally push: the server answers as soon as data exists, so average latency is about one network transit. It still pays a full HTTP request per delivery or timeout, and its worst case is over three transits.
How does a user whose phone was off for a day see their updates? Not from the push. Notifications are hints that can be collapsed, dropped or throttled. State lives on the server; on open the app pulls everything after its last cursor, or does a full sync if it has been away too long.
10,000 agents poll for config every minute. What goes wrong? After a restart they line up and hit together: 10,000 requests in one second instead of 167 a second on average. Add jitter, serve config from a cache with ETags so unchanged polls are cheap, and push a “new version” hint if you want faster rollouts without polling more often.
Where to go next
- Real-time connections: long polling, SSE and WebSockets: the pipes behind client push.
- Publish/subscribe and message queues: push and pull subscriptions, long polling and prefetch.
- Backpressure and load shedding: what to do when a consumer can’t keep up.
- Design a news feed: the fan-out cost model in full.
- Webhooks and idempotency: safe push between companies.
- Design metrics, monitoring and alerting: pull-based collection at scale.
- Synchronous vs asynchronous communication and batch vs stream processing: the neighbouring trade-offs.
References and further reading
- Apache Kafka documentation: Design, “Push vs. pull”kafka.apache.orgWhy consumers pull: a push broker overwhelms consumers slower than producers, pull falls behind and catches up, batches aggressively, and uses a long poll to avoid busy-waiting.
- Apache Kafka documentation: Consumer configskafka.apache.orgfetch.min.bytes (default 1 byte) and fetch.max.wait.ms (default 500 ms), the two knobs of the fetch long poll.
- Prometheus FAQ: Why do you pull rather than push?prometheus.ioPull lets you start extra monitoring instances, tell reliably when a target is down and inspect targets by hand; the project calls pull only “slightly better”.
- Prometheus: When to use the Pushgatewayprometheus.ioPush only for service-level batch jobs; the Pushgateway is a single point of failure, loses the up metric and never forgets pushed series.
- RFC 6202: Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTPrfc-editor.orgShort polling cost grows as acceptable latency shrinks; long polling averages one network transit, peaks above three, and should time out around 30 seconds.
- WHATWG HTML Standard: Server-sent eventshtml.spec.whatwg.orgThe EventSource reconnection time, the retry field, the Last-Event-ID header for resuming, and a comment line every 15 seconds or so to keep proxies from dropping the stream.
- Firebase Cloud Messaging: Non-collapsible and collapsible messagesfirebase.google.comContent-free “ping” messages that tell an app to sync, four collapse keys per device, and the 100-message offline limit that triggers a full sync.
- Apple Developer: Generating a remote notificationdeveloper.apple.comAPNs rejects payloads over 4 KB (5 KB for VoIP notifications).
- Stripe documentation: Receive Stripe events in your webhook endpointdocs.stripe.comRetries for up to three days with exponential backoff, no ordering guarantee, duplicate deliveries, and returning 2xx quickly.
- Argo CD documentation: Git webhook configurationargo-cd.readthedocs.ioArgo CD polls Git every three minutes by default; a webhook removes the polling delay.