100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Protocol Buffers

Bidirectional Streaming RPC

A gRPC pattern where client and server each send an independent, ordered stream of messages over the same connection at the same time.

Service TypesAdvanced10 min readJul 10, 2026
Analogies

What Is a Bidirectional Streaming RPC?

Bidirectional streaming opens a single HTTP/2 stream over which both the client and the server can send messages independently and in any order relative to each other, with each side reading and writing on its own schedule until both close their respective send directions. Unlike client or server streaming, neither side waits for the other to finish before sending its own messages, which makes this pattern the closest gRPC gets to a full-duplex, persistent connection — well suited to chat systems, collaborative editing, and real-time multiplayer state synchronization.

🏏

Cricket analogy: It is like a stump microphone feed during a live broadcast: the umpire's calls and the players' chatter are captured continuously in both directions at once, not waiting for one party to finish talking before the other starts.

Message Ordering and Stream Lifecycle

Within each direction, gRPC guarantees ordered delivery — messages the client sends arrive at the server in the order sent, and the same holds for server-to-client — but there is no guaranteed ordering between the two directions relative to each other. The stream ends only once both sides have finished: the client half-closes by ending its send loop, the server independently finishes sending and returns a final status, and the RPC completes when both conditions are satisfied, which means either side can also end the call early by returning an error status or cancelling.

🏏

Cricket analogy: It is like a scorer's book and a commentator's transcript both being kept in strict chronological order individually, even though you can't precisely align which commentary line corresponds to which scorebook entry down to the millisecond.

Defining and Implementing a Bidirectional Streaming RPC

protobuf
syntax = "proto3";

service ChatService {
  // Bidirectional streaming: independent streams both ways.
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}

message ChatMessage {
  string room_id = 1;
  string sender = 2;
  string text = 3;
  int64 sent_at_ms = 4;
}
go
func (s *chatServer) Chat(stream pb.ChatService_ChatServer) error {
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            return nil // client finished sending
        }
        if err != nil {
            return err
        }
        // Broadcast to every other subscriber in the room concurrently.
        s.rooms.Broadcast(msg.RoomId, msg)

        // Send is called independently, not in lockstep with Recv.
        if err := stream.Send(msg); err != nil {
            return err
        }
    }
}

Reading (Recv) and writing (Send) on the same stream must each be single-threaded per direction — most gRPC implementations are not safe to call Send concurrently from multiple goroutines or threads on the same stream. Use one dedicated writer loop, often fed by a channel, if multiple producers need to send on the same bidirectional stream.

Designing Bidirectional Protocols Well

Because both sides can send at any time, well-designed bidirectional protocols usually include an explicit message-type or oneof field so the receiver can distinguish, say, a heartbeat from a data update from a control command, rather than assuming every message on the stream means the same thing. It's also common to build a lightweight application-level acknowledgment or sequence number scheme on top of the raw stream when the business logic needs stronger guarantees than gRPC's per-direction ordering, such as detecting a gap after a brief reconnect.

🏏

Cricket analogy: It is like a stadium's radio-comms protocol distinguishing umpire calls, security alerts, and groundstaff chatter on separate labeled channels, rather than mixing every voice into one undifferentiated feed.

Bidirectional streams can be synchronous (client and server strictly alternate, effectively a ping-pong pattern) or fully asynchronous (either side sends freely at any time). Decide which model your protocol needs up front — it changes how you structure the read and write loops on both ends.

  • Bidirectional streaming lets client and server each send an independent, ordered message stream on one connection.
  • Ordering is guaranteed within each direction but not across the two directions relative to each other.
  • The RPC completes only once both sides have finished sending and a final status is returned.
  • The proto definition marks both the request and response types with 'stream'.
  • Send calls on a single stream must not be issued concurrently from multiple goroutines or threads.
  • Well-designed protocols tag messages with an explicit type so receivers can dispatch correctly.
  • This pattern fits chat, collaborative editing, and real-time multiplayer state sync best.

Practice what you learned

Was this page helpful?

Topics covered

#ProtocolBuffers#GRPCStudyNotes#WebDevelopment#BidirectionalStreamingRPC#Bidirectional#Streaming#RPC#Message#StudyNotes#SkillVeris