skip to content

In gRPC, what is an interceptor and where does it run relative to the service method it wraps?

level: juniorimportance: must knowfreq 60%

answer

  1. cross-cutting work, one place
  2. a wrapper around every call
  3. two independent surfaces
  4. runs before and after the method
  5. sees metadata and the final status

basics

~20 s

An interceptor is a wrapper around a gRPC call that runs before and after the service method, on the client side or the server side, so cross-cutting work like authentication, audit logging, timing and tracing lives in one place.

solid answer

~40 s

An interceptor is a wrapper the gRPC machinery installs around every call, so cross-cutting work runs once in one place instead of at the top of every method. There are two separate surfaces. A **client-side** interceptor wraps an outgoing call and is where a caller attaches a credential or stamps a correlation key into the call's metadata. A **server-side** interceptor wraps an incoming call and is where you authenticate it, write an audit record, time it and count its outcome. Both run *inside* the call: the server-side one gets control after the request headers arrive and before the service method runs, and again after the method produces a result but before the call's trailing status is written. Registering one on the client does nothing on the server — the two sides are configured independently.

code

pseudocode · 11 lines
pseudocode
function auditInterceptor(call, request, nextHandler):
    started = now()
    outcome = nextHandler(call, request)          # the service method runs here
    writeAuditRecord(
        method  = call.method,
        filer   = call.metadata["filer-id"],
        status  = outcome.status,                 # OK (0) or the failure status
        elapsed = now() - started)
    return outcome                                # forwarded unchanged, failures included

server.install(auditInterceptor)                  # applies to every method

go deeper

for a junior

Be able to say what an interceptor is — a wrapper the framework runs around every call — and name two jobs that belong in one, such as authenticating an incoming call and recording that it happened.

for a middle

Explain the two surfaces separately: what a client-side wrapper can attach to an outgoing call's metadata, and what a server-side wrapper can read, reject or rewrite before the method runs and after it returns.

for a senior

Show where the boundary sits in production: coarse authentication and audit in the wrapper, per-record authorisation in the handler, no blocking work in either, and no blanket payload logging that drags personal data into a log store.

for a principal

The tradeoff is uniformity against opacity. A mandatory wrapper guarantees every call is attributed and audited, but it hides behaviour from the call site and becomes a shared dependency that every team has to upgrade on someone else's schedule.

## What an interceptor actually is A gRPC call does not go straight from the generated stub to the service method. On each side the library runs the call through a short pipeline, and an **interceptor** is a function you insert into that pipeline. It receives everything the next stage would have received, plus a handle to that next stage, so it can do work, invoke the next stage, and then do more work with whatever came back. Registering one applies it to **every method of every service** on that side. That is the whole point of the pattern, and it is what separates it from a helper function you have to remember to call at the top of each method: a method written next month is covered on the day it is written, by nobody's discipline in particular. The useful mental picture is layers. The service method is the innermost layer; each interceptor is a layer wrapped around it; a call enters from the outside, reaches the method, and the outcome unwinds back out through the same layers in reverse. ## Two surfaces, configured independently "Interceptor" names two different things depending on which side you install it on, and they are not two halves of one object — each is registered in its own process, by whoever owns that process. | | client-side interceptor | server-side interceptor | |---|---|---| | wraps | an outgoing call the stub is about to make | an incoming call, before the service method | | sees first | the request the caller built and the call's metadata | the request headers and metadata as they arrived | | can add | a credential, a correlation key, its own metadata entries | metadata on the response and on the trailing status | | sees last | the terminal status the call ended with | the status the method produced, before it is written | | typical job | attach a credential, stamp a correlation key, measure caller-observed latency | authenticate, audit, time the call, count outcomes, open and close a trace span | Installing one on the client does nothing on the server. A key a client-side wrapper stamps into the call's metadata does travel with the call, but unless the server side also has a wrapper that reads it, nothing on the far side reacts to it. ## Where it sits in the call's life On the server the wrapper gets control **after** the request headers have been parsed and **before** the service method is invoked; it gets control again **after** the method produces a result and **before** the call's trailing status is written. That second window is why a server-side wrapper can not only observe an outcome but rewrite it — and it is why a wrapper that quietly completes a failed call normally hands the caller a success. Both windows are **inside the call**. The deadline the caller set is already running by the time a wrapper gets control, so everything it does is spent out of that budget. A wrapper is not free time before the clock starts. ## The jobs that belong here 1. **Attribution and audit.** Record that a call arrived, which method it was, who it was attributed to, and how it ended. In a court records e-filing service, where an unlogged submission is a legal defect rather than a missing log line, that cannot be left to the discipline of each handler's author. 2. **Coarse authentication.** Reject a call that carries no usable credential before any service method runs. 3. **Correlation.** Read a correlation key from the call's metadata, or mint one when it is absent, and make it available for the life of the call so everything the handler emits carries it. 4. **Measurement.** Time the call and count its outcomes by status — on either side, for different reasons: the server sees what it produced, the client sees what it actually experienced. ## The jobs that do not - Anything that needs the request's **business fields** to decide. Whether this clerk may amend this particular case file depends on that record and that person; the wrapper knows the method name, not the relationship. - Anything **blocking**. A client library typically invokes the wrapper on the thread that drives the connection, and one connection carries many concurrent calls. - Anything that **logs a payload unexamined**. A filing body is exactly the kind of content a blanket "log the request" wrapper leaks into a log store that was never scoped to hold personal data. - Anything that **hides a failure**. A wrapper that catches an error, logs it and completes the call normally publishes a success to the caller. ## Why not simply call a helper in every method - One registration replaces N call sites, and N grows every time somebody adds a method. - The rule becomes auditable: you can read the server's wiring and say what happens to every call, rather than grepping handlers. - Coverage is not a review checklist item any more. The cost is real and worth stating in an interview: behaviour that is invisible at the call site, a shared dependency every team upgrades together, and a stack trace that now runs through three wrappers before it reaches the code somebody actually wrote.

  • Can a client-side gRPC interceptor see the status the call ended with?
    Yes. The outcome reaches the client side as a terminal status once the call closes, and the wrapper observes it in its completion callback. That is the right place for client-side latency and outcome counters, because the status arrives after the response message rather than with it.
  • What work does not belong in an interceptor?
    Anything that needs the request's business fields to decide — whether this clerk may amend this particular case file is a handler decision, not a wrapper one. Anything blocking stays out too, because the wrapper runs on the thread driving the connection and inside the caller's deadline.
  • If both sides install a wrapper, which one runs first?
    The client-side one, entirely. It finishes its pre-work and the request goes on the wire before the server-side wrapper exists in the story at all. On the way back the order reverses: the server-side wrapper sees the outcome first, the client-side one sees it last.

saying these in an interview costs you the question

  • Says interceptors exist only on the server side
  • Thinks registering one on the client also covers the server
  • Claims an interceptor sees the request but never the outcome
  • Believes a wrapper replaces per-record authorisation in the handler
  • Confuses an interceptor with a reverse proxy in front of the server
  • Assumes a wrapper's work happens before the caller's deadline starts