HTTP/2 server push let a server send a response the client never asked for, announced with a PUSH_PROMISE frame. Explain how it worked and why browsers such as Chrome ended up removing support for it.
answer
- PUSH_PROMISE reserves an even stream ID
- server can't see the client's cache → wasted bytes
- pushed bytes steal congestion window from the HTML
- RST_STREAM CANCEL arrives an RTT too late
- removed in Chrome 106 (2022); 103 Early Hints replaces it
basics
~20 sThe server sent PUSH_PROMISE reserving an even stream ID, then delivered a response the client had not requested. Servers could not know what the client already had cached, so most pushes were wasted bytes competing with the HTML for bandwidth. Chrome removed it in 2022.
solid answer
~60 sPush worked like this: while serving `/index.html`, the server sends a **PUSH_PROMISE** frame on the request's stream containing the request headers it is synthesizing (`GET /app.css`), which reserves an even-numbered stream, then sends the response on that stream. The client can decline with RST_STREAM (CANCEL), and pushed responses go into the HTTP cache. It failed for one core reason and several practical ones. The core reason: **the server does not know the client's cache**, so it pushes assets the browser already has. Attempts to fix that (cache digests) never shipped. Practically, pushed bytes compete with the HTML itself for a shared congestion window, so eager pushing often *delayed* first paint; the RST_STREAM cancellation arrives at least a round trip late, so the bytes are already on the wire; and it was hard to configure correctly. Measurements at scale showed most pushes unused and net effects near zero or negative. Chrome removed push in version 106 (2022); Firefox followed. **103 Early Hints with `Link: rel=preload`** is the replacement: the server tells the client *what to fetch*, and the client decides, consulting its own cache.
code
http · 9 linesC->S HEADERS stream=1 GET /index.html
S->C PUSH_PROMISE stream=1 promised_stream=2
:method: GET
:scheme: https
:authority: example.com
:path: /app.css
S->C HEADERS stream=2 200 OK content-type: text/css
S->C DATA stream=2 <bytes already in flight>
C->S RST_STREAM stream=2 error=CANCEL (too late: bytes sent)go deeper
Know that push sent resources the client never requested, that it is gone from browsers, and that Early Hints and preload replaced it.
Describe PUSH_PROMISE and the reserved even stream, and give the cache-awareness and bandwidth-contention reasons for removal.
Add the operational evidence — mostly unused pushes, first-paint regressions, late cancellation — and explain how Early Hints restores the round-trip saving without the waste.
Draw the general lesson: never place a decision with the party lacking the information; contrast push and Early Hints as an ownership-of-decision change, and weigh the residual cases where push-like behaviour is still valid.
## The mechanism HTTP/2 server push was designed to remove one round trip: instead of the browser parsing the HTML, discovering `/app.css`, and requesting it, the server would send it unprompted. The wire flow: 1. Client sends `GET /index.html` on stream 1. 2. Before or alongside the response, the server sends **PUSH_PROMISE** on stream 1. The frame carries a *promised stream ID* (even, server-initiated) and the complete request header block the server is synthesizing on the client's behalf — method, scheme, authority, path. 3. The server sends HEADERS and DATA for that response on the promised stream. 4. The client may reject the promise with **RST_STREAM (CANCEL)** on the promised stream, or refuse all pushes by setting `SETTINGS_ENABLE_PUSH = 0`. Only safe, cacheable requests (GET, HEAD, no body) could be pushed, and the server had to be authoritative for the pushed origin. Pushed responses entered the browser's HTTP cache, so they had to carry sane cache headers to be reusable. ## Why it did not work **1. The cache-awareness problem (fatal).** The server has no idea what is already in the client's cache. On a repeat visit, nearly everything it pushes is redundant — pure waste on the user's connection and the operator's bandwidth bill. The proposed fix, *cache digests* (a compact Bloom-filter-like summary of the client's cache sent up front), was drafted but never shipped in browsers. **2. Bandwidth contention.** Pushed streams share the connection's congestion window with the HTML being pushed alongside them. Push too much and you starve the very document whose parsing unblocks everything else — measurable first-paint regressions. Getting it right required knowing the exact bandwidth and RTT at that moment, which nobody does. **3. Cancellation is too late.** A client that already has the resource cancels with RST_STREAM, but that decision reaches the server at least one round trip after the push began. On a 100 ms RTT with a warm connection, tens of kilobytes are already committed. Cancellation limits the damage; it does not prevent it. **4. Timing and layering.** Push worked best if issued *before* the origin generated the HTML, but most origins only knew what to push while rendering. CDNs added link-header-driven push at the edge, which then pushed the same things regardless of the visitor's cache state, compounding problem 1. **5. Complexity and interop.** Push interacted awkwardly with prioritization, credentialed versus uncredentialed fetches, coalesced connections, and service workers. Debugging "why did this pushed resource not get used?" was notoriously painful — a push could be received but never matched to a request. ## The verdict Large-scale measurements from CDNs and browser telemetry converged on the same finding: most pushed bytes were never used, and median performance effects were neutral to negative. Chrome disabled push by default and **removed it in Chrome 106 (2022)**; Firefox removed support as well. Servers may still implement it (HTTP/3 defines a push mechanism too), but with the major browsers gone the feature is effectively dead on the public web. It survives only in controlled environments where the server genuinely knows the client's cache state. ## The replacement **103 Early Hints** (RFC 8297) inverts the control. Instead of sending bytes, the server sends an informational `103` response containing `Link: </app.css>; rel=preload; as=style` header fields *before* the final response, while the origin is still thinking. The client then decides whether to fetch — checking its own cache first, applying its own priorities, and choosing its own timing. That resolves the cache-awareness problem completely and costs only a few hundred bytes when the hint is wrong. Other pieces of the same replacement toolkit: `Link: rel=preload` header fields or `<link rel=preload>` tags in the final response (no extra round trip saved, but no waste either), `rel=preconnect` to warm a connection to a third-party origin, and inlining genuinely tiny critical resources into the HTML. ## What to say when asked The interview answer worth giving is the *why*: push moved a decision (what does this client still need?) to the party that lacks the information (the server). Early Hints keeps the round-trip savings while leaving the decision with the party that has the cache. That framing generalizes far beyond HTTP.
- Could a client stop unwanted pushes before they cost bandwidth?Only bluntly. Setting SETTINGS_ENABLE_PUSH to 0 disables push for the whole connection, which is what browsers ended up doing. Per-resource refusal used RST_STREAM with CANCEL, but that decision reaches the server a round trip after the push started, so a good fraction of the bytes are already on the wire.
- Why is 103 Early Hints not just push with extra steps?Early Hints sends URLs, not bodies, so a wrong hint costs a few hundred bytes instead of a full resource. The client checks its own cache, applies its own priorities, and can ignore the hint entirely. The round-trip saving is preserved because the hint goes out while the origin is still generating the final response.
Push is a shop mailing you products it guesses you need; Early Hints is a text message saying "we have these in stock" so you can check your own cupboard first.
saying these in an interview costs you the question
- Recommending server push as a current web performance technique
- Claiming cancellation made push cheap, ignoring the round-trip delay before RST_STREAM lands
- Thinking push failed because it was slow, rather than because the server cannot see the client's cache
- Confusing push with preload — preload is a hint the client acts on, push is unsolicited bytes