TL;DR: gRPC interviews in 2026 test Protocol Buffers schema evolution, the four RPC types, deadlines, status codes, retries and load balancing over long-lived HTTP/2 connections. Candidates should know that grpc-go replacedDialwithNewClient, and that Protobuf Editions replace the proto2 and proto3 syntax labels.
gRPC 1.84 shipped in September 2026 across the C-core, Go and Java lines, and grpc-go 1.84 renamed its pick-first OpenTelemetry metrics to a new grpc.subchannel.* set. Protobuf itself reached v36.2 the same month.
The core ideas an interviewer checks have not moved: contracts in .proto files, deadlines on every call, and field numbers that are never reused.
- 1By default gRPC sets no deadline, so a client can wait for a response forever unless you set one.
- 2A server rejects keepalive pings more often than every 5 minutes by default and closes the connection with
too_many_pings. - 3Kubernetes has had native gRPC health probes, stable since v1.27, for services that implement the standard health protocol.
- 4Retries are committed once response headers arrive; after that, gRPC hands the call to the application and never retries it.
gRPC Basics
1. What is gRPC, and what does it run on?
gRPC is an RPC framework: you define services and messages in a .proto file, generate client and server code in many languages, and call remote methods like local functions.
It runs over HTTP/2 and uses Protocol Buffers for messages by default.
HTTP/2 matters for three reasons. Many calls share one connection as separate streams. Headers are compressed. And both sides can send messages on a stream at the same time, which is what makes streaming RPCs possible.
The core concepts page covers the model.
2. What are the four kinds of RPC?
Unary, server streaming, client streaming and bidirectional streaming. They differ only in whether each side sends one message or a stream.

In a bidirectional call the two streams are independent. The server can reply after every message, after all of them, or on its own schedule. Ordering is kept within each stream, not across the two.
service Chat {
rpc Send(Message) returns (Ack); // unary
rpc Subscribe(Room) returns (stream Message); // server streaming
rpc Upload(stream Chunk) returns (UploadResult); // client streaming
rpc Connect(stream Message) returns (stream Message); // bidirectional
}
3. When would you choose gRPC over REST with JSON?
Choose gRPC for service-to-service calls inside your own system, where you control both ends and want typed contracts, generated clients and streaming.
Choose REST with JSON for public APIs and browsers, where any HTTP client must be able to call you.
Browsers cannot speak native gRPC because they do not expose HTTP/2 trailers to scripts. gRPC-Web works around this but needs a proxy such as Envoy, per the gRPC-Web tutorial.
A strong candidate also names the costs: binary payloads are harder to inspect, and many L7 tools need extra setup for gRPC.
4. Why is Protobuf smaller and faster than JSON?
A Protobuf message on the wire carries field numbers and compact binary values, not field names and text. An integer is a varint of one to ten bytes, and a missing field costs nothing.
The schema lives in generated code on both sides, so neither side parses keys or guesses types. The trade-off is that you cannot read a message without the schema.
Tools such as grpcurl use server reflection or the .proto files to decode it.
Protocol Buffers
5. Which schema changes are safe, and which break clients?
Adding fields and removing fields are safe; changing or reusing field numbers is not. The binary format identifies each field by its number, so the number is the contract, not the name.

The proto3 guide adds a middle category: "wire-compatible" changes such as int32 to int64. Old readers truncate large values, so these need a careful rollout.
Renaming a field is binary-safe but breaks anyone who stores messages as ProtoJSON, where names are the keys.
6. How do you delete a field safely?
Remove it and mark its number and name as reserved, so nobody can reuse them.
message User {
reserved 4, 7;
reserved "legacy_email";
string id = 1;
string name = 2;
}
If someone later reuses field 4 with a different type, old binaries still in production will parse the new data as the old field. That produces corrupt values, not errors, which is why it is so hard to debug.
7. How does field presence work in proto3?
By default a proto3 scalar field has no presence: a value of zero, an empty string or false is not sent, and the reader cannot tell it from "not set". Marking the field optional adds explicit presence, with a has_ method or a pointer in Go.
This matters for updates. Say discount = 0 must mean "set the discount to zero" rather than "leave it alone". Then the field needs presence, or the API needs a FieldMask listing the fields to change.
Message-typed fields always have presence.
8. What is a oneof, and when do you use one?
A oneof is a group of fields where at most one can be set at a time; setting one clears the others. Use it for "exactly one of these shapes", such as a payment that is either a card or a bank transfer.
Generated code gives you a way to check which member is set, such as a type switch in Go. Evolution rules are strict here: adding a new member is fine, but moving an existing field into an existing oneof is not safe.
9. How do you stop breaking changes from reaching production?
Check every .proto change against the last released version in CI. Buf's breaking change detection is the common tool; it fails the build on changes such as a reused field number or a renamed RPC.
Keep the schemas in one repository or registry that every service pulls from, and version packages, as in acme.billing.v1. A new major version gets a new package, and both versions run side by side until clients move.
Reliability
10. Why must every call have a deadline?
Because gRPC sets none by default, so a stuck server can hold a client's call, and its resources, forever. The deadlines guide says to always set a realistic deadline explicitly.
A deadline is a point in time, not a duration, so it can be passed down a call chain. If service A has 2 seconds left and calls B, B should get the remaining time, not a fresh 2 seconds.
The guide notes that Go and Java propagate deadlines by default, while C++ needs it enabled. Servers should also check whether the call was cancelled and stop work that nobody is waiting for.
ctx, cancel := context.WithTimeout(ctx, 300*time.Millisecond)
defer cancel()
resp, err := client.GetUser(ctx, &pb.GetUserRequest{Id: id})
if status.Code(err) == codes.DeadlineExceeded {
// fall back or fail fast
}
11. Which status codes should a server return?
Return the code that tells the client what to do next. INVALID_ARGUMENT means fix the request. NOT_FOUND and ALREADY_EXISTS are about the resource. FAILED_PRECONDITION means the system is not in the right state.
UNAVAILABLE means try again later.
Two are often misused. INTERNAL is reserved for serious errors where an invariant broke, per the status codes guide. UNKNOWN is what a client sees when a server throws an unmapped exception.
A server that returns UNKNOWN for validation errors leaves clients unable to tell a bug from bad input.
12. How do you send structured error details?
Use the richer error model: attach protobuf messages from google.rpc, such as BadRequest with field violations or RetryInfo, to the status. They travel in the trailers alongside the code and message.
The error handling guide notes this model only works when Protobuf is the data format, and support differs by language. Clients must still handle a bare status, because a proxy or an older server may not send details.
13. How do retries work, and when are they unsafe?
gRPC retries in the client, per method, from a retryPolicy in the service config: a maximum number of attempts, exponential backoff and a list of retryable codes, usually just UNAVAILABLE.
"retryPolicy": {
"maxAttempts": 4,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
Per the retry guide, gRPC adds 20% jitter and stops retrying once response headers arrive. Retrying is only safe for idempotent methods.
A CreatePayment call that timed out may have succeeded, so it needs an idempotency key before it can be retried. Hedging, which sends extra copies before the first fails, has the same limit.
14. What does wait-for-ready change?
It changes what a call does while the channel has no ready connection. By default the call fails fast with UNAVAILABLE. With wait-for-ready set, it waits for a connection until its deadline.
Use it for calls made during startup, when a dependency may still be booting, or for batch jobs that would rather wait than fail. It only makes sense with a deadline; otherwise the call can wait forever.
15. What problem do keepalives solve, and how do they cause outages?
Keepalive pings detect dead connections that TCP would not notice for a long time, such as when a load balancer silently drops an idle flow. They matter most for long-lived streams.
The outage pattern is a client that pings too often. The keepalive guide lists a server default of 5 minutes as the minimum ping interval.
A client pinging every 10 seconds gets a GOAWAY with too_many_pings, and its connections keep dropping. Client and server settings must be agreed together.
Scaling and Security
16. Why does a normal L4 load balancer spread gRPC traffic badly?
Because a gRPC client keeps one long-lived HTTP/2 connection and sends every call over it. An L4 balancer picks a backend per connection, so all calls from that client land on one server, even after new servers are added.
The fixes, from the gRPC load balancing post: use an L7 proxy such as Envoy that balances per call, or client-side balancing.
For the client side, resolve every backend address, for example through a Kubernetes headless service, and set the round_robin policy. The default policy, pick_first, sends everything to one address.
17. What are interceptors used for?
Interceptors wrap every call on a client or server, like middleware, for concerns that apply across methods: auth checks, logging, metrics, tracing and panic recovery. Most languages have separate unary and stream interceptors.
func authUnary(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
if err := checkToken(ctx); err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
return handler(ctx, req)
}
A common bug is adding only the unary interceptor, which leaves every streaming method without auth.
18. How do you secure gRPC between services?
Use TLS on every connection, and mutual TLS when the server must know which service is calling. Add per-call credentials, such as a JWT in metadata, when you need the end user's identity too.
A service mesh can provide mTLS without code changes. Inside the app, authorization belongs in an interceptor that checks the method name from the call info against the caller's identity.
Insecure credentials in production code are a red flag in review.
19. How do health checks work in gRPC?
Services implement the standard grpc.health.v1.Health service, which reports SERVING or NOT_SERVING for the server or for each named service. Load balancers, meshes and orchestrators call it.
Kubernetes can probe it directly with a grpc probe, stable since v1.27 per the Kubernetes probe docs. During a graceful shutdown, set the status to NOT_SERVING first so traffic drains before the server stops.
20. How do you handle backpressure in a stream?
HTTP/2 flow control gives it to you if you let it work. Each stream has a window, and a sender that fills it must wait until the receiver reads. In Go, a server's Send blocks when the client is slow.
The bug is buffering around it: reading from the stream into an unbounded channel or list defeats flow control and moves the memory problem into your process.
Read at the pace you can process, and cancel the stream when the consumer gives up.
What Changed Recently
grpc.NewClient introducedDial and DialContext deprecatedgrpc.subchannel.*21. Why did grpc-go deprecate Dial in favour of NewClient?
grpc.NewClient, added in grpc-go 1.63.0, creates the channel in idle mode and uses dns as the default resolver. Dial and DialContext were deprecated in 1.64.0 but stay supported throughout 1.x.
The behaviour differs in two ways a candidate should know. NewClient does not connect until the first RPC, so options such as WithBlock that waited for a connection are ignored and deprecated.
Code that used WithBlock to wait for a connection at startup now learns about a bad target only on the first call. And targets without a scheme now go through DNS rather than being passed through as-is.
conn, err := grpc.NewClient("dns:///users.internal:443",
grpc.WithTransportCredentials(creds))
22. What are Protobuf Editions?
Editions replace the syntax = "proto2" and syntax = "proto3" lines with edition = "2023", and express the old differences as named features. Editions were officially released in Protobuf 27.0 in May 2024.
Presence is the clearest example. Instead of proto2's required or proto3's implicit presence, a file or field sets features.field_presence to EXPLICIT, IMPLICIT or LEGACY_REQUIRED.
The Editions overview says editions do not change the binary, text or JSON format, so a file can migrate without breaking peers.
23. What did edition 2024 change?
Edition 2024, enabled in protoc and most language generators in Protobuf 32.0 in August 2025, tightened imports and visibility. According to the Editions overview:
import optionimports only custom options, without generating code for the imported file. It replacesimport weak, which edition 2024 removed.exportandlocalkeywords control which symbols other files can use; nested symbols are local by default.- The
ctypefield option is gone; use thestring_typefeature instead.
The practical point: moving a file to edition 2024 can break the build. Another file may use a nested message that is now local.
24. What is the Go Protobuf Opaque API?
It is a new style of generated Go code, released on December 16, 2024. Message fields are hidden and accessed only through getters and setters. The Go blog announcement says the old open-struct API stays supported; nothing was removed.
Hiding the fields lets the runtime change the memory layout. Presence is stored in bit fields instead of pointers, which uses less memory, and submessages can be decoded lazily on first access.
Existing code keeps working, and the blog describes a Hybrid API and an open2opaque tool for migrating gradually.
- Exported fields:
m.Name = "x" - Pointers for presence in proto2
- Still supported
- Hidden fields:
m.SetName("x") HasandClearmethods for presence- Allows lazy decoding
25. How do you get metrics out of gRPC today?
Through the official OpenTelemetry plugin, which replaced the OpenCensus support that has been sunset. The OpenTelemetry metrics guide lists stable per-call and per-attempt instruments for clients and servers.
Per-attempt data shows how many retries and hedges a call really took.
Instrument names still move. grpc-go 1.84.0 removed the grpc.lb.pick_first.* metrics and replaced them with grpc.subchannel.* metrics.
Dashboards built on the old names go blank after the upgrade, so pin versions and read the behaviour changes in each release.
Signs of a Strong Answer
- They set a deadline on every outgoing call and pass the remaining time downstream, not a fresh timeout.
- They reserve deleted field numbers without being asked, and have a CI check for breaking schema changes.
- They can explain why one client's calls all hit one pod behind an L4 balancer, and how to fix it.
- They pick status codes by what the caller should do next, and never return
UNKNOWNon purpose. - They only retry idempotent methods, or add idempotency keys first.
- They know what changed in their own stack, such as
NewClientin Go or Editions in their.protofiles.
Hiring gRPC Developers
gRPC skills sit inside back-end and platform roles rather than on their own, usually alongside Go, Java or C++.
Second Talent matches companies with pre-vetted back-end engineers who build gRPC and Protobuf services, screened with questions like these.
Tell us the stack and we send a shortlist within 24 hours. Start hiring, or see our microservices and Go interview guides.






