// ── Proto3 Syntax ──
syntax = "proto3";
package users.v1;
// Import other protos
import "google/protobuf/timestamp.proto";
import "google/protobuf/field_mask.proto";
// ── Scalar Types ──
// double, float, int32, int64, uint32, uint64,
// sint32, sint64, fixed32, fixed64,
// sfixed32, sfixed64, bool, string, bytes
// ── Message Definition ──
message User {
string id = 1; // required (default "")
string name = 2;
string email = 3;
int32 age = 4;
repeated string roles = 5; // list
bool active = 6; // default false
google.protobuf.Timestamp created_at = 7;
// Nested message
message Address {
string street = 1;
string city = 2;
string country = 3;
}
Address address = 8;
// OneOf (union type)
oneof contact {
string phone = 9;
string social = 10;
}
// Map type
map<string, string> metadata = 11;
// Reserved fields (cannot be reused)
reserved 12, 15 to 20;
reserved "old_field";
}
// ── Enums ──
enum Status {
STATUS_UNSPECIFIED = 0; // proto3: first must be 0
STATUS_ACTIVE = 1;
STATUS_INACTIVE = 2;
STATUS_BANNED = 3;
}
// ── Repeated / Nested ──
message GetUsersResponse {
repeated User users = 1;
int32 total_count = 2;
string next_page_token = 3;
}
| Proto Type | Go | Java | Python |
|---|
| double | float64 | double | float |
| float | float32 | float | float |
| int32 | int32 | int | int |
| int64 | int64 | long | int |
| uint32 | uint32 | int | int |
| bool | bool | boolean | bool |
| string | string | String | str |
| bytes | []byte | ByteString | bytes |
| Feature | Proto2 | Proto3 |
|---|
| Required/Optional | Yes | No (all optional) |
| Default values | Explicit | Implicit (zero value) |
| Enum first value | Any | Must be 0 |
| Field presence | Has presence | No presence (use wrapper) |
| OneOf | Yes | Yes |
| Maps | No (manual) | Yes (built-in) |
💡Proto3 is simpler and recommended for new services. Use optional keyword (proto3) for explicit null tracking. Field numbers 1-15 use 1 byte encoding, 16-2047 use 2 bytes.
// ── Go Server Streaming Example ──
func (s *server) ListUsers(req *pb.ListUsersRequest,
stream pb.UserService_ListUsersServer) error {
users, err := s.db.ListUsers(ctx, req.PageSize, req.PageToken)
if err != nil {
return status.Error(codes.Internal, err.Error())
}
for _, user := range users {
if err := stream.Send(user); err != nil {
return err // client disconnected
}
}
return nil
}
// ── Go Client Streaming Example ──
func (s *server) UploadLogs(stream pb.LogService_UploadLogsServer) error {
var count int
for {
log, err := stream.Recv()
if err == io.EOF {
return stream.SendAndClose(&pb.UploadResponse{Count: int32(count)})
}
if err != nil {
return err
}
count++
// process log entry
}
}
// ── Error Handling ──
import "google.golang.org/grpc/codes"
import "google.golang.org/grpc/status"
return status.Error(codes.NotFound, "User not found")
return status.Error(codes.PermissionDenied, "Access denied")
return status.Error(codes.InvalidArgument, "Invalid email")
return status.Error(codes.DeadlineExceeded, "Timeout")
return status.Errorf(codes.InvalidArgument,
"Invalid age: %d (must be > 0)", age)
// ── Client: Check errors
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
if err != nil {
st, ok := status.FromError(err)
if ok && st.Code() == codes.NotFound {
// handle not found
}
}
// ── Deadlines & Timeouts ──
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// ── Metadata (headers) ──
// Client sends:
md := metadata.Pairs("authorization", "Bearer "+token)
ctx := metadata.NewOutgoingContext(context.Background(), md)
// Server reads:
md, ok := metadata.FromIncomingContext(ctx)
token := md.Get("authorization")
| Code | HTTP | Meaning |
|---|
| OK | 200 | Success |
| CANCELLED | 499 | Client cancelled |
| UNKNOWN | 500 | Unknown error |
| INVALID_ARGUMENT | 400 | Bad request |
| DEADLINE_EXCEEDED | 504 | Timeout |
| NOT_FOUND | 404 | Resource not found |
| ALREADY_EXISTS | 409 | Conflict |
| PERMISSION_DENIED | 403 | Forbidden |
| RESOURCE_EXHAUSTED | 429 | Rate limited |
| UNAUTHENTICATED | 401 | Not authenticated |
| UNAVAILABLE | 503 | Service unavailable |
| Type | Description |
|---|
| Unary | Intercept unary calls |
| Stream | Intercept streaming calls |
| Client | Client-side middleware |
| Server | Server-side middleware |
⚠️Always set deadlines on client calls to prevent hanging requests. Use context.WithTimeout or grpc.WithTimeout. Servers should propagate deadlines via metadata.
// ── Server Auth Interceptor (Go) ──
func UnaryAuthInterceptor(
ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
token := md.Get("authorization")
if token == "" {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
claims, err := ValidateToken(token)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
// Add user info to context
ctx = context.WithValue(ctx, "user_id", claims.UserID)
ctx = context.WithValue(ctx, "roles", claims.Roles)
return handler(ctx, req)
}
// Register interceptor
server := grpc.NewServer(
grpc.UnaryInterceptor(UnaryAuthInterceptor),
)
pb.RegisterUserServiceServer(server, &userService{})
// ── TLS Configuration ──
creds, err := credentials.NewServerTLSFromFile("server.crt", "server.key")
server := grpc.NewServer(grpc.Creds(creds))
// Client TLS
creds, err := credentials.NewClientTLSFromFile("ca.crt", "example.com")
conn, err := grpc.Dial("example.com:443",
grpc.WithTransportCredentials(creds))
Q: Why gRPC over REST?gRPC uses Protobuf (binary, 3-10x smaller than JSON), HTTP/2 (multiplexing, header compression, server push), built-in streaming, strong typing via code generation, and bidirectional communication. REST is simpler, human-readable, and has broader tool support.
Q: What is Protobuf and why use it?Protocol Buffers is a binary serialization format by Google. Advantages over JSON: smaller payload size, faster serialization/deserialization, strongly typed schema, backward compatibility via field numbers, and built-in code generation for 12+ languages.
Q: How does gRPC handle versioning?Protobuf handles versioning through field numbers. Never reuse a field number. Delete deprecated fields (keep number reserved). Add new fields with new numbers. Old clients ignore unknown fields. This enables backward and forward compatibility without version headers.
Q: Explain gRPC streaming types.Unary: one request, one response. Server streaming: client sends one request, server sends a stream of responses. Client streaming: client sends a stream of requests, server sends one response. Bidirectional: both client and server send independent streams. All use HTTP/2.
Q: What are gRPC interceptors?Interceptors are middleware that wrap RPC handlers. Unary interceptors handle single request/response calls. Stream interceptors handle streaming calls. Use cases: authentication, logging, rate limiting, metrics, error handling, and request validation.
Q: How does gRPC handle errors?gRPC uses status codes (similar to HTTP but different values). Server returns status with code, message, and optional details. Client checks error codes. Common pattern: map domain errors to gRPC status codes. Use google.rpc.status for rich error details.
💡Top gRPC interview topics: Protobuf syntax, service definition (unary/streaming), code generation, error handling with status codes, interceptors/middleware, authentication, deadlines, metadata, HTTP/JSON transcoding, and versioning strategy.