---
# === IDENTITY ===
id: software/patterns/grpc-service-design/2026
canonical_question: "How do I design gRPC services?"
aliases:
  - "gRPC API design"
  - "gRPC service definition"
  - "protobuf service design"
  - "gRPC streaming patterns"
  - "gRPC best practices"
  - "designing gRPC microservices"
  - "gRPC proto file structure"
  - "gRPC vs REST API design"
entity_type: software_reference
domain: software > patterns > grpc_service_design
region: global
jurisdiction: global
temporal_scope: 2015-2026

# === VERIFICATION ===
last_verified: 2026-02-24
confidence: 0.90
version: 1.0
first_published: 2026-02-24

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "gRPC v1.60+ requires explicit keepalive enforcement policy on servers; proto3 optional fields reintroduced in protoc 3.15+"
  next_review: 2026-08-23
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "MUST use proto3 syntax for new services — proto2 lacks first-class support for optional fields and has legacy baggage"
  - "NEVER reuse or reassign proto field numbers after deletion — use reserved to prevent accidental reuse"
  - "NEVER remove or rename fields in production protos — deprecate with [deprecated=true] and reserve the field number"
  - "Always set deadlines on RPCs — unbounded RPCs accumulate server resources and can cascade failures"
  - "gRPC requires HTTP/2 — browsers cannot call gRPC directly without gRPC-Web or a transcoding proxy"
  - "Default max message size is 4 MB — override explicitly for large payloads or use streaming"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Building a public-facing API consumed primarily by browsers or third-party developers"
    use_instead: "software/patterns/rest-pagination/2026"
  - condition: "Need flexible client-driven queries with nested field selection"
    use_instead: "software/patterns/graphql-schema-patterns/2026"
  - condition: "Simple webhook or callback-style integration between two services"
    use_instead: "software/system-design/webhooks-system/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "streaming_type"
    question: "What communication pattern does your service need?"
    type: choice
    options: ["unary (request-response)", "server streaming", "client streaming", "bidirectional streaming"]
  - key: "language"
    question: "What language is the service implemented in?"
    type: choice
    options: ["Go", "Java", "Python", "Node.js", "C++", "Rust", "C#"]
  - key: "load_balancing"
    question: "Do you need client-side load balancing or proxy-based?"
    type: choice
    options: ["proxy (Envoy, nginx)", "client-side (grpc-lb)", "service mesh (Istio, Linkerd)", "not sure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/patterns/grpc-service-design/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-02-24)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/patterns/graphql-schema-patterns/2026"
      label: "GraphQL Schema Design Patterns"
    - id: "software/patterns/api-versioning/2026"
      label: "API Versioning Strategies"
    - id: "software/patterns/rest-pagination/2026"
      label: "REST Pagination Patterns"
    - id: "software/patterns/polling-sse-websocket/2026"
      label: "Polling vs SSE vs WebSocket"
  solves:
    - id: "software/system-design/api-gateway/2026"
      label: "API Gateway Design"
  alternative_to:
    - id: "software/patterns/graphql-schema-patterns/2026"
      label: "GraphQL Schema Design Patterns"
  often_confused_with:
    - id: "software/patterns/rest-pagination/2026"
      label: "REST Pagination Patterns"

# === SOURCES (8 authoritative sources) ===
sources:
  - id: src1
    title: "Core concepts, architecture and lifecycle"
    author: gRPC Authors
    url: https://grpc.io/docs/what-is-grpc/core-concepts/
    type: official_docs
    published: 2024-01-15
    reliability: authoritative
  - id: src2
    title: "API Design Guide"
    author: Google Cloud
    url: https://cloud.google.com/apis/design
    type: official_docs
    published: 2023-06-01
    reliability: authoritative
  - id: src3
    title: "gRPC Performance Best Practices"
    author: gRPC Authors
    url: https://grpc.io/docs/guides/performance/
    type: official_docs
    published: 2024-03-10
    reliability: authoritative
  - id: src4
    title: "Interceptors"
    author: gRPC Authors
    url: https://grpc.io/docs/guides/interceptors/
    type: official_docs
    published: 2024-02-20
    reliability: authoritative
  - id: src5
    title: "Language Guide (proto3)"
    author: Google
    url: https://protobuf.dev/programming-guides/proto3/
    type: official_docs
    published: 2024-01-01
    reliability: authoritative
  - id: src6
    title: "gRPC Motivation and Design Principles"
    author: gRPC Authors
    url: https://grpc.io/blog/principles/
    type: official_docs
    published: 2015-09-08
    reliability: high
  - id: src7
    title: "go-grpc-middleware: Interceptor chaining, auth, logging, retries"
    author: gRPC Ecosystem
    url: https://github.com/grpc-ecosystem/go-grpc-middleware
    type: community_resource
    published: 2024-06-15
    reliability: high
  - id: src8
    title: "gRPC Error Handling"
    author: gRPC Authors
    url: https://grpc.io/docs/guides/error/
    type: official_docs
    published: 2024-01-15
    reliability: authoritative
---

# gRPC Service Design: Patterns, Streaming, and Best Practices

## TL;DR

- **Bottom line**: Design gRPC services protobuf-first with strongly-typed contracts, use streaming for real-time data flows, and enforce deadlines on every RPC to prevent cascading failures.
- **Key tool/command**: `protoc --go_out=. --go-grpc_out=. service.proto` (or language-specific plugin)
- **Watch out for**: Breaking backward compatibility by reusing or removing proto field numbers — always use `reserved` to retire fields.
- **Works with**: Any HTTP/2 environment. Go, Java, Python, Node.js, C++, Rust, C# all have official or well-maintained gRPC libraries. Requires gRPC-Web or Envoy transcoding for browser clients.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- MUST use proto3 syntax for new services — proto2 lacks first-class support for optional fields and has legacy baggage
- NEVER reuse or reassign proto field numbers after deletion — use `reserved` to prevent accidental reuse
- NEVER remove or rename fields in production protos — deprecate with `[deprecated=true]` and reserve the field number
- Always set deadlines on RPCs — unbounded RPCs accumulate server resources and can cascade failures
- gRPC requires HTTP/2 — browsers cannot call gRPC directly without gRPC-Web or a transcoding proxy
- Default max message size is 4 MB — override explicitly for large payloads or prefer streaming

## Quick Reference

| RPC Type | Proto Syntax | Use Case | Complexity | Error Handling | Flow Control |
|---|---|---|---|---|---|
| Unary | `rpc Get(Req) returns (Resp)` | CRUD, lookups, commands | Low | Single status code + details | N/A |
| Server streaming | `rpc List(Req) returns (stream Resp)` | Feed updates, large result sets, log tailing | Medium | Status on stream close; per-message errors via oneof | Backpressure built-in |
| Client streaming | `rpc Upload(stream Req) returns (Resp)` | File upload, batch ingest, telemetry | Medium | Server responds after all messages; early cancel possible | Client controls send rate |
| Bidirectional streaming | `rpc Chat(stream Req) returns (stream Resp)` | Real-time chat, multiplayer sync, live dashboards | High | Both sides can error independently; need app-level acks | Independent read/write streams |

**gRPC Status Codes Quick Lookup:**

| Code | Constant | When to Use |
|---|---|---|
| 0 | `OK` | Success |
| 3 | `INVALID_ARGUMENT` | Client sent malformed request |
| 5 | `NOT_FOUND` | Requested resource does not exist |
| 7 | `PERMISSION_DENIED` | Caller lacks permission (authenticated but unauthorized) |
| 8 | `RESOURCE_EXHAUSTED` | Rate limit hit or quota exceeded |
| 13 | `INTERNAL` | Unexpected server error (do not expose details to client) |
| 14 | `UNAVAILABLE` | Transient failure — client should retry with backoff |
| 16 | `UNAUTHENTICATED` | Missing or invalid auth credentials |

## Decision Tree

> Full script: [decision-tree.txt](scripts/decision-tree.txt) (31 lines)

```
START: What kind of API communication do you need?
├── Browser clients calling your API directly?
│   ├── YES → Use REST or GraphQL; gRPC requires gRPC-Web proxy
│   └── NO ↓
├── Internal microservice-to-microservice communication?
# ... (see full script)
```

## Step-by-Step Guide

### 1. Define your proto service contract

Start with the `.proto` file. This is the single source of truth for your API. Follow Google's AIP naming conventions: services are `PascalCase`, methods are `VerbNoun`, messages use `PascalCase` with `snake_case` fields. [src2]

```protobuf
syntax = "proto3";

package myapp.orders.v1;

option go_package = "github.com/myorg/myapp/gen/orders/v1";

import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";

service OrderService {
  // Unary: create a new order
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  // Unary: get a single order
  rpc GetOrder(GetOrderRequest) returns (Order);
  // Server streaming: watch order status changes
  rpc WatchOrders(WatchOrdersRequest) returns (stream OrderEvent);
}

message Order {
  string id = 1;
  string customer_id = 2;
  repeated OrderItem items = 3;
  OrderStatus status = 4;
  google.protobuf.Timestamp created_at = 5;
  google.protobuf.Timestamp updated_at = 6;
}

message OrderItem {
  string product_id = 1;
  int32 quantity = 2;
  int64 price_cents = 3;  // Always use cents to avoid float precision issues
}

enum OrderStatus {
  ORDER_STATUS_UNSPECIFIED = 0;  // Proto3 requires 0 = unspecified
  ORDER_STATUS_PENDING = 1;
  ORDER_STATUS_CONFIRMED = 2;
  ORDER_STATUS_SHIPPED = 3;
  ORDER_STATUS_DELIVERED = 4;
  ORDER_STATUS_CANCELLED = 5;
}

message CreateOrderRequest {
  string customer_id = 1;
  repeated OrderItem items = 2;
}

message GetOrderRequest {
  string id = 1;
}

message WatchOrdersRequest {
  string customer_id = 1;
}

message OrderEvent {
  Order order = 1;
  string event_type = 2;
  google.protobuf.Timestamp occurred_at = 3;
}
```

**Verify**: `protoc --lint_out=. service.proto` or use `buf lint` → no warnings

### 2. Generate language-specific stubs

Use `protoc` with language-specific plugins, or use Buf for a more modern workflow. [src1]

```bash
# Go
protoc --go_out=. --go-grpc_out=. \
  --go_opt=paths=source_relative \
  --go-grpc_opt=paths=source_relative \
  proto/orders/v1/service.proto

# Python
python -m grpc_tools.protoc \
  -I proto \
  --python_out=gen \
  --grpc_python_out=gen \
  proto/orders/v1/service.proto

# Node.js (with grpc-tools)
grpc_tools_node_protoc \
  --js_out=import_style=commonjs,binary:gen \
  --grpc_out=grpc_js:gen \
  proto/orders/v1/service.proto

# Or use Buf (recommended for multi-language repos)
buf generate
```

**Verify**: Generated files appear in output directory with correct package names

### 3. Implement server with interceptors

Add interceptors for cross-cutting concerns: logging, auth, recovery, and metrics. Order matters — recovery should be outermost so panics don't crash the process. [src4] [src7]

```go
// Go server setup with interceptor chain
package main

import (
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/keepalive"
    "google.golang.org/grpc/reflection"
    "time"
)

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    server := grpc.NewServer(
        // Interceptor chain: outermost first
        grpc.ChainUnaryInterceptor(
            recoveryInterceptor,   // Catch panics first
            metricsInterceptor,    // Track all requests
            loggingInterceptor,    // Log all requests
            authInterceptor,       // Validate tokens
        ),
        grpc.ChainStreamInterceptor(
            streamRecoveryInterceptor,
            streamLoggingInterceptor,
        ),
        // Keepalive settings
        grpc.KeepaliveParams(keepalive.ServerParameters{
            MaxConnectionIdle: 5 * time.Minute,
            Time:              2 * time.Hour,
            Timeout:           20 * time.Second,
        }),
        grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
            MinTime:             5 * time.Second,
            PermitWithoutStream: true,
        }),
    )

    // Register your service
    // ordersv1.RegisterOrderServiceServer(server, &orderServer{})

    // Enable server reflection for grpcurl debugging
    reflection.Register(server)

    log.Printf("gRPC server listening on :50051")
    if err := server.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
```

**Verify**: `grpcurl -plaintext localhost:50051 list` → shows registered services

### 4. Implement proper error handling

Return rich error details using gRPC status codes and the errdetails package. Never expose internal errors to clients. [src8]

```go
import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    "google.golang.org/genproto/googleapis/rpc/errdetails"
)

func (s *orderServer) GetOrder(ctx context.Context, req *GetOrderRequest) (*Order, error) {
    if req.Id == "" {
        st := status.New(codes.InvalidArgument, "order ID is required")
        st, _ = st.WithDetails(&errdetails.BadRequest{
            FieldViolations: []*errdetails.BadRequest_FieldViolation{
                {Field: "id", Description: "must be non-empty"},
            },
        })
        return nil, st.Err()
    }

    order, err := s.repo.FindByID(ctx, req.Id)
    if err != nil {
        // Log internal error, return safe message
        log.Printf("internal error fetching order %s: %v", req.Id, err)
        return nil, status.Error(codes.Internal, "failed to retrieve order")
    }
    if order == nil {
        return nil, status.Errorf(codes.NotFound, "order %s not found", req.Id)
    }
    return order, nil
}
```

**Verify**: `grpcurl -d '{"id":""}' localhost:50051 myapp.orders.v1.OrderService/GetOrder` → returns `INVALID_ARGUMENT` with field violation details

### 5. Implement client with deadlines and retries

Every RPC call must have a deadline. Configure retry policies declaratively via service config. [src3]

```go
import (
    "context"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
)

func newOrderClient() (ordersv1.OrderServiceClient, error) {
    // Service config with retry policy
    retryPolicy := `{
        "methodConfig": [{
            "name": [{"service": "myapp.orders.v1.OrderService"}],
            "waitForReady": true,
            "retryPolicy": {
                "MaxAttempts": 3,
                "InitialBackoff": "0.1s",
                "MaxBackoff": "1s",
                "BackoffMultiplier": 2,
                "RetryableStatusCodes": ["UNAVAILABLE", "RESOURCE_EXHAUSTED"]
            }
        }]
    }`

    conn, err := grpc.NewClient("dns:///orders.svc:50051",
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithDefaultServiceConfig(retryPolicy),
    )
    if err != nil {
        return nil, err
    }

    return ordersv1.NewOrderServiceClient(conn), nil
}

func getOrder(client ordersv1.OrderServiceClient, orderID string) (*Order, error) {
    // Always set a deadline
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    return client.GetOrder(ctx, &GetOrderRequest{Id: orderID})
}
```

**Verify**: Call with an unreachable server and confirm retry + timeout behavior

## Code Examples

### Go: Server Streaming

```go
// Input:  WatchOrdersRequest with customer_id
// Output: Stream of OrderEvent messages until client cancels

func (s *orderServer) WatchOrders(
    req *WatchOrdersRequest,
    stream OrderService_WatchOrdersServer,
) error {
    ch := s.eventBus.Subscribe(req.CustomerId)
    defer s.eventBus.Unsubscribe(ch)

    for {
        select {
        case event := <-ch:
            if err := stream.Send(event); err != nil {
                return status.Errorf(codes.Internal,
                    "stream send failed: %v", err)
            }
        case <-stream.Context().Done():
            return status.FromContextError(
                stream.Context().Err()).Err()
        }
    }
}
```

### Python: Unary Client with Error Handling

```python
# Input:  order_id string
# Output: Order object or raises appropriate exception
# Requires: grpcio>=1.68.0, grpcio-tools>=1.68.0

import grpc
from orders.v1 import service_pb2, service_pb2_grpc

def get_order(order_id: str) -> service_pb2.Order:
    channel = grpc.insecure_channel("localhost:50051")
    stub = service_pb2_grpc.OrderServiceStub(channel)
    try:
        response = stub.GetOrder(
            service_pb2.GetOrderRequest(id=order_id),
            timeout=5.0,  # Always set timeout
        )
        return response
    except grpc.RpcError as e:
        if e.code() == grpc.StatusCode.NOT_FOUND:
            print(f"Order {order_id} not found")
        elif e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
            print("Request timed out")
        else:
            print(f"RPC failed: {e.code()} {e.details()}")
        raise
```

### Node.js: Bidirectional Streaming

> Full script: [node-js-bidirectional-streaming.js](scripts/node-js-bidirectional-streaming.js) (26 lines)

```javascript
// Input:  bidirectional stream of ChatMessage
// Output: bidirectional stream of ChatMessage
// Requires: @grpc/grpc-js@^1.12.0, @grpc/proto-loader@^0.7.0
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
# ... (see full script)
```

## Anti-Patterns

### Wrong: REST-style resource URLs in gRPC methods

```protobuf
// BAD — gRPC methods are not HTTP endpoints; don't mimic REST paths
service OrderService {
  rpc GetOrdersSlashOrderIdSlashItems(GetRequest) returns (Response);
  rpc PostOrders(CreateRequest) returns (Response);
}
```

### Correct: Use clear VerbNoun method names

```protobuf
// GOOD — follow Google AIP naming: VerbNoun with domain-specific nouns
service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc ListOrders(ListOrdersRequest) returns (ListOrdersResponse);
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc ListOrderItems(ListOrderItemsRequest) returns (ListOrderItemsResponse);
}
```

### Wrong: Reusing deleted field numbers

```protobuf
// BAD — field 3 was deleted, now reused with different type.
// Old clients with cached protos will decode corrupted data.
message Order {
  string id = 1;
  string name = 2;
  // was: string old_field = 3; (deleted)
  int64 total = 3;  // WRONG — reuses field number 3!
}
```

### Correct: Reserve deleted field numbers

```protobuf
// GOOD — reserved prevents accidental reuse of field 3
message Order {
  string id = 1;
  string name = 2;
  reserved 3;
  reserved "old_field";
  int64 total = 4;  // Uses new field number
}
```

### Wrong: No deadline on RPC calls

```go
// BAD — no deadline means the call can hang forever,
// holding server resources and causing cascading failures
resp, err := client.GetOrder(context.Background(), req)
```

### Correct: Always set a deadline

```go
// GOOD — deadline ensures call fails fast if server is slow
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.GetOrder(ctx, req)
```

### Wrong: Using generic Request/Response messages

```protobuf
// BAD — single Request/Response for all RPCs prevents independent evolution
message Request {
  string type = 1;
  bytes payload = 2;
}
service MyService {
  rpc DoSomething(Request) returns (Response);
  rpc DoOtherThing(Request) returns (Response);
}
```

### Correct: Dedicated request/response per RPC

```protobuf
// GOOD — each RPC gets its own messages, enabling independent evolution
service MyService {
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message CreateUserRequest { string name = 1; string email = 2; }
message CreateUserResponse { User user = 1; }
message GetUserRequest { string user_id = 1; }
message GetUserResponse { User user = 1; }
```

## Common Pitfalls

- **Not setting enum zero value to UNSPECIFIED**: Proto3 defaults unset enums to 0. If 0 maps to a real value (e.g., `ACTIVE`), you cannot distinguish "not set" from "intentionally ACTIVE". Fix: Always define `ENUM_NAME_UNSPECIFIED = 0;` as the first value. [src5]
- **Streaming where unary suffices**: Streaming adds complexity (flow control, reconnection logic, harder load balancing). Fix: Use unary RPCs for simple request-response; only stream when data is genuinely continuous or too large for a single message. [src3]
- **Not enabling server reflection**: Without reflection, debugging tools like `grpcurl` cannot introspect services. Fix: Add `reflection.Register(server)` in Go, or `add_reflection` in Python. [src1]
- **Ignoring backpressure in streams**: Sending faster than the receiver can process causes memory exhaustion. Fix: Check `Send()` return values and respect `context.Done()` for cancellation. [src3]
- **Using float/double for monetary values**: IEEE 754 floating point causes rounding errors. Fix: Use `int64` cents/mills or `string` for decimal values. Use `google.type.Money` from googleapis if available. [src5]
- **Exposing internal errors to clients**: Returning raw database or internal errors leaks implementation details. Fix: Log the full error server-side, return a sanitized `status.Error(codes.Internal, "internal error")` to the client. [src8]
- **Not versioning the proto package**: Package changes break generated code imports. Fix: Include version in package name from day one: `package myapp.orders.v1;`. [src2]

## Diagnostic Commands

```bash
# List all services on a running gRPC server (requires reflection enabled)
grpcurl -plaintext localhost:50051 list

# Describe a specific service and its methods
grpcurl -plaintext localhost:50051 describe myapp.orders.v1.OrderService

# Call a unary RPC with JSON payload
grpcurl -plaintext -d '{"id": "order-123"}' \
  localhost:50051 myapp.orders.v1.OrderService/GetOrder

# Call a server-streaming RPC
grpcurl -plaintext -d '{"customer_id": "cust-1"}' \
  localhost:50051 myapp.orders.v1.OrderService/WatchOrders

# Health check (requires grpc-health-probe or health service)
grpc_health_probe -addr=localhost:50051

# Check connectivity with verbose logging
GRPC_VERBOSITY=DEBUG GRPC_TRACE=all grpcurl -plaintext localhost:50051 list

# Lint proto files with Buf
buf lint proto/

# Detect breaking changes against previous version
buf breaking proto/ --against .git#branch=main

# Generate dependency graph for proto imports
buf dep graph
```

## Version History & Compatibility

| Version | Status | Breaking Changes | Migration Notes |
|---|---|---|---|
| gRPC 1.60+ (2024) | Current | Strict keepalive enforcement policy required on servers | Add `KeepaliveEnforcementPolicy` to server options |
| protoc 3.21+ / protobuf-go v2 | Current | Go module path changed to `google.golang.org/protobuf` | Migrate from `github.com/golang/protobuf` |
| protoc 3.15+ (2021) | Stable | `optional` keyword reintroduced in proto3 | Can use `optional` for explicit presence tracking |
| gRPC-Go 1.57+ (2023) | Current | `grpc.Dial` deprecated in favor of `grpc.NewClient` | Replace `grpc.Dial` with `grpc.NewClient` |
| gRPC-Web 1.5+ | Current | None | Required for browser clients calling gRPC |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Internal microservice communication | Public APIs consumed by browsers | REST + OpenAPI |
| Strict contract enforcement needed across teams | Rapid prototyping or frequently changing schemas | REST or GraphQL |
| Low-latency, high-throughput RPC | Simple CRUD with minimal client diversity | REST |
| Real-time bidirectional data (chat, sync, telemetry) | Occasional webhook-style callbacks | Webhooks or SSE |
| Polyglot services needing shared contract | Team unfamiliar with protobuf tooling | REST + JSON Schema |
| Streaming large datasets (logs, events, feeds) | Small payloads with aggressive HTTP caching needs | REST with Cache-Control |
| Mobile-to-backend with bandwidth constraints | IoT devices on constrained networks without HTTP/2 | MQTT or CoAP |

## Important Caveats

- gRPC over HTTP/2 means a single TCP connection multiplexes all RPCs — hitting the concurrent stream limit (default 100) causes queuing. Create separate channels for high-throughput paths. [src3]
- Long-lived streaming RPCs cannot be load-balanced mid-stream by L4 proxies — use L7 proxies (Envoy) or client-side balancing. Streams pin to a single backend for their lifetime. [src3]
- Proto3 fields are always optional by default (zero values are not serialized on the wire). If you need to distinguish between "field not set" and "field set to zero/empty", use `optional` keyword (protoc 3.15+) or wrapper types like `google.protobuf.StringValue`. [src5]
- gRPC health checking is a separate service (`grpc.health.v1.Health`) — implement it for Kubernetes readiness/liveness probes and load balancer health checks. [src1]
- Channel/stub reuse is critical for performance — creating a new channel per RPC adds connection setup overhead. Share channels across goroutines/threads. [src3]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [GraphQL Schema Design Patterns](/software/patterns/graphql-schema-patterns/2026)
- [API Versioning Strategies](/software/patterns/api-versioning/2026)
- [REST Pagination Patterns](/software/patterns/rest-pagination/2026)
- [Polling vs SSE vs WebSocket](/software/patterns/polling-sse-websocket/2026)
- [API Gateway Design](/software/system-design/api-gateway/2026)
