Skip to content

Streaming

Every gRPC call runs over a single HTTP/2 stream, and because that stream can carry a sequence of messages in either direction, gRPC offers four call types — not four transports, just four usage patterns of the same stream.

flowchart TB
  subgraph u["Unary"]
    direction LR
    uc["client"] -- "1 request" --> us["server"]
    us -- "1 response" --> uc
  end
  subgraph ss["Server streaming"]
    direction LR
    sc["client"] -- "1 request" --> sss["server"]
    sss -- "many responses" --> sc
  end
  subgraph cs["Client streaming"]
    direction LR
    csc["client"] -- "many requests" --> css["server"]
    css -- "1 response" --> csc
  end
  subgraph bd["Bidirectional"]
    direction LR
    bc["client"] <-- "many both ways" --> bs["server"]
  end
The four gRPC call types

You already know unary — one request, one response, the everyday RPC. This module covers the other three, plus the flow control that keeps them healthy.

LessonShapeGood for
Server streaming1 request → N responseslive feeds, large result sets, progress updates
Client streamingN requests → 1 responseuploads, batching, metric ingestion
Bidirectional streamingN ↔ Nchat, multiplayer, interactive sessions
Flow control & backpressurekeeping a fast producer from drowning a slow consumer

Reach for streaming when:

  • The result set is large or unbounded — sending 10,000 rows as one stream avoids buffering them all in memory, and the client can start processing the first row before the last is produced.
  • Data arrives over time — prices, events, logs. A stream lets the server push each item the moment it exists instead of the client polling.
  • You want one long call instead of many setups — a stream amortizes the per-call overhead across many messages.

Streaming is not free, and reaching for it reflexively is a common mistake:

  • A single request/response is simpler. If you have one input and one output, use unary. A stream adds lifecycle, error-at-any-point handling, and harder retries.
  • Streams are stateful and long-lived, which complicates load balancing — a stream is pinned to one backend for its whole life, so it can’t be rebalanced mid-flight.
  • Retries get harder. A unary call is trivially retryable; a half-consumed stream is not.
What do the four gRPC call types actually share?
Which is the strongest reason to choose streaming over repeated unary calls?
Why can streaming complicate load balancing?