System Design
What Is Service Discovery?
In a modern system, services are constantly being created, destroyed, and moved - so how does one service ever find another? Service discovery is the answer, and it's the quiet machinery that makes microservices possible.
In a simple system, finding a service is easy: it lives at a fixed address you hardcode and never think about again. But modern systems aren't simple. In a cloud or microservices environment, services are constantly spinning up and shutting down, scaling out and back in, crashing and restarting - each time potentially on a different machine with a different address. The service you need to call might be at one address now and a completely different one a minute from now.
So how does one service reliably find another when addresses are constantly changing? That's the problem service discovery solves. It's the mechanism that lets services locate each other dynamically, without hardcoded addresses - and it's essential, invisible infrastructure underneath almost every microservices system. This guide explains what service discovery is, why dynamic environments make it necessary, the two main approaches, and the tools that implement it.
The problem: addresses won't hold still
To appreciate service discovery, you have to feel the problem it solves. Picture a system with dozens of microservices. The order service needs to call the payment service. In the old world, you'd configure the order service with the payment service's address - payment-service is at 10.0.1.5 - and be done.
But in a modern environment, that breaks constantly:
- Services scale dynamically. When traffic rises, more copies of the payment service spin up - each at a new address. When it falls, copies shut down. The set of addresses is always changing.
- Services move. In container and cloud environments, a service that restarts (after a crash or a deploy) often comes back at a different address. The address you hardcoded is now wrong.
- Services fail. An instance dies. If you're still sending requests to its address, they fail. Something needs to know it's gone and stop routing to it.
Hardcoding addresses simply doesn't work when addresses are ephemeral. You need a dynamic, always-current way to answer the question: "where are the healthy instances of the service I need right now?"
The solution: a service registry
At the heart of service discovery is a service registry - a central, always-current database of which services exist and where they're located. Think of it as a constantly updated phone book for services: instead of looking up a fixed address, a service asks the registry "where is the payment service?" and gets back the current, healthy addresses.
The registry works through a continuous cycle:
- Registration. When a service instance starts up, it registers itself with the registry - announcing "I'm an instance of the payment service, and I'm at this address." When it shuts down, it deregisters.
- Health checking. The registry continually checks whether registered instances are still alive and healthy (often via heartbeats or health-check probes). If an instance stops responding, the registry removes it, so dead instances aren't handed out.
- Discovery. When a service needs to call another, it queries the registry to get the current list of healthy addresses for that service, and picks one to send its request to.
sequenceDiagram
participant P as Payment Service (new instance)
participant R as Service Registry
participant O as Order Service
P->>R: register "payment-service @ 10.0.1.7"
R->>P: health check (heartbeat)
P->>R: still alive ✓
O->>R: where is payment-service?
R->>O: healthy instances: [10.0.1.7, 10.0.1.9]
O->>P: send request to 10.0.1.7
This sequence diagram shows service discovery in action. A new payment service instance registers itself with the service registry, announcing its address. The registry health-checks it via heartbeat and confirms it's alive. Later, the order service asks the registry where the payment service is, and the registry returns the list of currently healthy instances. The order service then sends its request to one of them.
The registry is the single source of truth for "what's running and where," kept current by registration and health checks. Everything else in service discovery is about how services register and how they query.
Two approaches: client-side vs server-side discovery
There are two main patterns for how the discovery actually happens, and the difference is who does the work of looking up and choosing an instance.
Client-side discovery
In client-side discovery, the calling service does the work itself. It queries the registry directly, gets the list of healthy instances, and chooses one (applying a load-balancing algorithm like round robin) - then sends the request straight to that instance.
- How it works: the client asks the registry, gets the addresses, picks one, and calls it directly.
- Pros: simple and efficient - there's no extra hop, the client talks straight to the chosen instance. The client has full control over how it picks an instance.
- Cons: each client must contain the discovery and load-balancing logic, which couples every client to the registry and means that logic is duplicated across services (and across languages, if your services use different ones).
Server-side discovery
In server-side discovery, the client doesn't deal with the registry at all. It simply sends its request to a fixed point - a load balancer or router - and that component queries the registry, picks a healthy instance, and forwards the request.
- How it works: the client calls a load balancer at a known address; the load balancer consults the registry, chooses an instance, and routes the request to it.
- Pros: clients stay simple - they don't need any discovery logic, just a single address to call. The discovery and load-balancing logic lives in one place (the load balancer), not duplicated everywhere.
- Cons: the load balancer is another component to run and maintain, and it adds a network hop. It must itself be made reliable so it's not a single point of failure.
| Client-Side Discovery | Server-Side Discovery | |
|---|---|---|
| Who queries the registry | The calling service | A load balancer / router |
| Client complexity | Higher (needs discovery logic) | Lower (just calls one address) |
| Network hops | Direct (fewer) | Extra hop through the balancer |
| Logic location | Duplicated in each client | Centralized in the balancer |
| Best for | Performance-sensitive, controlled environments | Simplicity, polyglot systems |
Server-side discovery is common in cloud and container platforms because it keeps services simple and centralizes the logic - but client-side discovery is efficient and gives clients more control. Many systems use a mix.
How services register: self vs third-party
There's also a choice in how instances get into the registry:
- Self-registration: each service instance registers itself directly when it starts and deregisters when it stops. Simple, but it couples each service to the registry and relies on every service implementing registration correctly.
- Third-party registration: a separate component (a "registrar") watches for services starting and stopping and handles registering them, so the services themselves don't have to. This keeps services free of registration logic, at the cost of running the extra registrar component.
In many modern platforms, the orchestration system (the thing that runs and schedules your containers) handles registration automatically - services come and go, and the platform keeps the registry current without the services doing anything. This is part of why container orchestration and service discovery are so tightly linked.
Common tools
Service discovery is usually provided by a dedicated tool rather than built from scratch. The well-known ones:
- Consul - a popular service discovery and configuration tool with built-in health checking.
- etcd - a distributed key-value store often used as a registry (and the backbone of some orchestration platforms).
- ZooKeeper - a mature distributed coordination service used for service registries, among other coordination tasks.
- Eureka - a service discovery tool designed for client-side discovery in microservices environments.
- DNS-based discovery - using DNS itself as a lightweight registry, where service names resolve to current instances. Simple and built on familiar infrastructure, though DNS caching can slow propagation of changes.
In many container platforms, service discovery is built in - the platform automatically registers services and lets others find them by name, so much of this happens without you configuring a separate tool. Whichever tool is used, the underlying concept is the same: a current registry, kept fresh by registration and health checks, that services query to find each other.
A worked example: a request finds its way
To see service discovery in action, trace a single call in a microservices system. The order service needs to charge a customer, so it must call the payment service - but it doesn't know where the payment service is, because instances are constantly changing.
With client-side discovery, here's what happens: the order service asks the service registry, "where is the payment service?" The registry, kept current by each payment instance registering itself and passing health checks, responds with the current healthy addresses - say two instances. The order service applies a load-balancing choice (round robin, perhaps), picks one, and sends its request directly to that instance.
A minute later, traffic spikes and three more payment instances start up. Each registers itself with the registry on startup. Now when the order service next asks, it gets five addresses instead of two - automatically, with no configuration change anywhere. And if one of those instances crashes, it fails its health check, the registry drops it, and the order service simply stops being handed that dead address. The whole system adapts to instances appearing and disappearing without anyone touching a config file. That continuous, automatic adaptation is exactly what service discovery provides.
Why DNS alone isn't always enough
You might wonder: isn't this just DNS? DNS does translate names to addresses, and DNS-based discovery is a real, lightweight option. But plain DNS has limitations in a fast-changing environment. DNS responses are cached - by clients and resolvers - for a period (the TTL), so when an instance dies or a new one appears, it can take time for that change to propagate. In an environment where instances come and go in seconds, that lag can mean traffic is sent to dead instances or new instances sit unused. Dedicated service discovery tools react faster and integrate health checking directly, so the registry reflects reality more quickly than DNS caching allows. DNS-based discovery works well when changes are infrequent; purpose-built discovery shines when instances are highly dynamic.
The registry must be reliable
A subtle but critical point: the service registry is itself a piece of infrastructure that everything depends on - so if it fails, services can't find each other and the whole system can grind to a halt. This makes the registry a potential single point of failure, which is why it's run as a distributed, replicated system rather than a single instance. Tools like etcd and ZooKeeper are built precisely for this: they maintain a consistent, replicated store across multiple nodes so the registry survives individual failures. The registry's reliability is foundational - a discovery system is only as dependable as the registry at its heart.
Service mesh: the modern evolution
In large microservices systems, service discovery has evolved into a broader pattern called a service mesh. Instead of each service handling discovery, load balancing, and retries itself, a lightweight proxy (a "sidecar") runs alongside each service and handles all of that network communication transparently. The mesh manages service discovery, load balancing, health checking, retries, and security between services - taking that logic out of the application code entirely. The services just make calls as if to local addresses, and the mesh figures out where everything actually is. A service mesh is, in essence, service discovery and traffic management elevated into dedicated infrastructure, which is increasingly how large systems handle the problem at scale.
The challenges discovery introduces
Like every building block, service discovery solves a problem but adds considerations of its own. The registry must be highly available and consistent - a stale or unavailable registry breaks discovery. Health checks must be tuned: too aggressive and healthy-but-slow instances get dropped, too lenient and dead instances linger (the same trade-off heartbeats face). And there's a propagation delay between an instance's state changing and every client learning about it, which systems must tolerate gracefully. None of these is a reason to avoid service discovery - it's essential in dynamic environments - but they're the realities you design around, which is why mature platforms invest so heavily in making the registry fast, consistent, and reliable.
Where it fits with other building blocks
Service discovery doesn't work alone - it interlocks with several other components:
- Health checks keep the registry accurate by detecting and removing dead instances, so discovery only ever returns healthy addresses.
- Load balancing is intertwined with discovery: once you've discovered the healthy instances, you load-balance across them (in client-side discovery the client does this; in server-side discovery the load balancer does).
- The coordination service (like ZooKeeper or etcd) often is the registry, providing the consistent, reliable storage the registry needs.
Together these make a microservices system navigable: services can find and call each other reliably even as instances constantly appear, disappear, and move.
Interview: If a system design discussion involves microservices, mentioning service discovery shows you understand how the pieces actually communicate. A strong point: "Since instances are dynamic, I'd use a service registry with health checks so services can find healthy instances by name rather than hardcoded addresses - either client-side discovery for efficiency or server-side through a load balancer to keep services simple." Recognizing that hardcoded addresses don't work in a dynamic environment, and naming the registry-plus-health-check solution, signals real understanding of how distributed systems are wired together.
The bottom line
Service discovery is the mechanism that lets services find each other in a dynamic environment where addresses are constantly changing. At its core is a service registry - a current directory of which services exist and where, kept accurate through registration and health checks. Services find each other either through client-side discovery (the client queries the registry and picks an instance itself) or server-side discovery (a load balancer does it on the client's behalf), and registration happens via self-registration or a third-party registrar - often automatically in modern container platforms.
It's invisible infrastructure, but it's what makes microservices viable: without it, dynamic, scaling, failing services couldn't reliably find one another. We cover how it fits into a complete architecture across System Design Fundamentals.
Go deeper
- Service discovery (Wikipedia) - the concept and its mechanisms.
- Client-side discovery pattern (microservices.io) - the client-side approach in depth.
- Server-side discovery pattern (microservices.io) - the server-side approach in depth.
- Amazon Builders' Library - how discovery and routing are handled at production scale.