Client-server architecture splits a system into two roles. Clients start every conversation by sending a request; servers wait for requests, do the work and send back a response. The server holds the shared data and the rules, so thousands of clients see one consistent truth. Nearly everything you design in an interview is client-server, so the real questions are what sits behind the server, where state lives, how far away the client is, and what happens when the network fails.
Picture it first: a bank branch
You walk into a bank branch and ask to withdraw ₹20,000. You start the conversation; the teller never phones you to ask what you need. Before paying, the teller checks the core banking system, so for that moment the teller is the customer. Any free teller can serve you, because your balance lives in the core system, not in one teller’s memory, and you show your passbook each time. When the queue grows, the bank opens more counters; when the core system is down, every counter stops. And the teller checks your signature: the bank never takes your word for your balance.
Map it back: you are the client, the teller is the server, and the core banking system is the server’s server (a database or another service). The passbook is the token or cookie the client carries, interchangeable tellers are stateless servers behind a load balancer, the core system going down is a single point of failure, and the signature check is never trusting the client.
Roles, not machines
Roy Fielding’s 2000 dissertation describes the style simply: a server offers services and listens for requests; a client that wants a service sends a request; the server performs or rejects it and responds. The client is the triggering process and acts at times of its choosing; the server is reactive, usually never terminates, and serves many clients.
The roles are not machines. RFC 9110, which defines HTTP’s semantics, says the terms client and server refer only to the roles a program plays on a particular connection: the same program can be a client on some connections and a server on others. Your booking API is a server to a million phones and a client of the payments service and the database. A partner company’s server calling your API is your client.
What makes the style useful is centralisation. The server holds the authoritative data and enforces the rules, so a seat is sold once, a price is computed one way and a bug is fixed with one deploy. The cost is the mirror image: the server is a bottleneck and a single point of failure, and every client pays network latency to reach it. Between the two ends sit intermediaries. RFC 9110 names proxies (chosen by the client), gateways, also called reverse proxies (load balancers, CDNs, API gateways that look like the server to the client), and tunnels.
In peer-to-peer systems every node is both client and server, and there is no central authority; BitTorrent and the media leg of a WebRTC call work this way (peer-to-peer architecture).
The constraints, and what they buy
Fielding derives REST, the Web’s architecture, by starting from no constraints and adding them one at a time, asking what each buys and what it costs. The client-server ones are the core of every design you will draw:
| Constraint | What it buys | What it costs |
|---|---|---|
| Client-server: separate the user interface from the data and rules | Portable clients, a simpler server that scales, and two sides that evolve independently as long as the interface holds | A network hop between them |
| Stateless: every request carries everything needed to understand it | Visibility (a monitor sees the whole request), reliability (easy recovery after partial failure), scalability (the server frees resources at once) | Repeated data on every request, and less control over behaviour across client versions |
| Cache: responses are labelled cacheable or not | Some interactions disappear, so lower average latency and more scale | Stale data when the cache disagrees with the server |
| Uniform interface: the same methods and conventions for every resource | Simpler, more visible interactions, decoupled implementations | Efficiency: a standard shape, not one tailored to each app |
| Layered system: each component sees only the next layer | Room for load balancers, shared caches and firewalls, and bounded complexity | Latency for each layer, partly won back by shared caches |
| Code on demand (optional): the server sends code to run on the client | Clients gain features after deployment (today, JavaScript) | Less visibility into what the client does |
Fielding also names the alternatives. Remote session keeps all application state on the server, as Telnet and FTP do, which helps generic clients but hurts scalability. Remote data access has the client send SQL to a database server, which is the classic two-tier desktop app.
In the interview
When you draw a client and a server, say what the split buys: “The server owns data and rules, so clients can be thin and change on their own release cycle. I keep the server stateless so any instance can serve any request, which is what lets me scale out behind a load balancer.”
One request, counted in round trips
The client is far away, and distance sets a floor no server can beat. Azure’s published round-trip times between its own regions (median, July 2026) give a feel: 53 ms from Central India (Pune) to Southeast Asia (Singapore), 139 ms to West Europe and 201 ms to East US. A phone on mobile data adds its own last-mile delay on top.
A cold HTTPS request from a new client runs four steps before the first byte of the answer arrives:
- DNS: turn the name into an address, usually from a nearby resolver or a cache (DNS).
- TCP handshake: one round trip (RTT) to open the connection (TCP vs UDP).
- TLS 1.3 handshake: one more RTT to agree keys and check the server’s certificate.
- The HTTP request and response: one RTT plus the server’s own time.
That is about 3 RTTs plus DNS. To East US: 3 × 201 ms ≈ 603 ms before any server work; to Singapore, 3 × 53 ms ≈ 159 ms. On a reused connection it is 1 RTT: 201 ms or 53 ms.
Four tools cut the cost (HTTP and HTTPS):
- Reuse connections. HTTP/1.1 keeps connections open, so only the first request pays the handshakes. Pool connections in apps and between services too.
- HTTP/2 multiplexes many requests on one connection. But RFC 9114 notes that TCP can’t see those streams, so one lost packet stalls all of them.
- HTTP/3 over QUIC runs on UDP, combines the transport and TLS handshakes into one (2 RTTs cold instead of 3), loses only the affected stream on packet loss, and lets a phone keep its connection while switching from Wi-Fi to 4G.
- Terminate near the user. A CDN edge in Mumbai does the handshakes a few milliseconds away and forwards over connections it keeps warm to your origin, so the user pays the long haul once, not three times.
0-RTT is fast, not free
TLS 1.3 and QUIC let a returning client send its request in the very first flight. RFC 8446 warns that this early data is not forward secret and can be replayed by an attacker, so allow it only for safe, idempotent requests such as a GET for the train timetable, never for “book this seat”.
Two tiers, three tiers, N tiers
A layer is a logical slice of code (presentation, business logic, data access); a tier is a physically separate process or machine. Fielding notes that layered client-server systems are what the information-systems world calls two-tier, three-tier or multi-tier.
Two-tier. A thick desktop client holds the screens and the business rules and sends SQL straight to a shared database: Fielding’s remote data access style, and how many 1990s branch and billing systems were built. On a LAN with 50 trusted users it works. On the internet it breaks: every install carries database credentials, the database is exposed to the world, every client holds its own connection (a database sized for hundreds of connections now faces tens of thousands), and a schema change or a rule change means upgrading every machine.
Three-tier. Presentation (browser or app), application (business rules on servers you control) and data (the database). Only the middle tier talks to the database, rules live in one place, and each tier scales on its own. This is the default shape of a web application.
N-tier. Azure’s guide adds more tiers where they earn their place: a web application firewall and load balancer in front, separate middle tiers for different areas of functionality, caches and queues between them, and a replicated data tier. Its advice is concrete: keep web and business tiers stateless so any VM can serve any request, put each tier in its own subnet as a security boundary, and let only the middle tier reach the database. It also names the trap: a middle tier that only passes CRUD calls through adds latency without value. In a strict (closed) model a request crosses every tier in turn; a relaxed (open) model lets it skip tiers, with lower latency but more coupling.
| 2-tier | 3-tier | N-tier | |
|---|---|---|---|
| Business rules | In every client | On the app servers | Split across middle tiers |
| Who reaches the database | Every client | Only the app tier | Only the tier that owns it |
| Scaling | Limited by database connections | Add app servers | Scale each tier separately |
| Security | Credentials on every install | Database hidden behind the app | Subnets and a firewall per tier |
| Changing a rule | Upgrade every install | Deploy the server | Deploy one tier |
| Cost | One hop | One extra hop | A hop per tier, more to run |
| Fits | Small trusted LANs, legacy desktop apps | Most web and mobile apps | Large or migrated enterprise apps |
Thin clients, thick clients
How much work the client does is a choice with consequences:
- Server-rendered pages (thin). The server builds HTML; the browser displays it. Fast first paint on cheap phones, one place to change logic, but a round trip for every interaction.
- Single-page apps (thicker). The server sends JavaScript once (Fielding’s code on demand) and then JSON; the browser renders. Snappier after the first load, but a larger download and more logic you can’t fully trust.
- Native mobile apps (thick). Installed code with local storage, which lets them work offline. A field-sales app in a village with patchy coverage writes orders to a local database and syncs when the signal returns; then you need conflict rules for two edits made offline.
Wherever the logic lives, the server validates everything. Client-side checks are for user experience (an instant “PIN must be 6 digits”), never for safety, because anyone can call your API without your app. When web, Android and partner clients need very different response shapes, a backend for frontend (BFF), one thin server layer per client type, shapes the responses so each client stays simple (API composition and BFF).
Stateless servers, and where the state goes
A stateful server remembers each user between requests, typically a session object in its memory. A stateless one keeps nothing between requests that another instance couldn’t also see; per-user state lives in the request, a token, or a shared store (stateful vs stateless design). HTTP itself is stateless, and RFC 9110 points out that this is exactly what lets intermediaries reuse connections and spread requests across servers. Cookies make it “stateless but not sessionless”, as MDN puts it: the browser carries an identifier, and the server looks the session up.
Where that session lives decides how you scale:
- In the server’s memory, with sticky sessions. The load balancer pins each user to one server with a cookie (load balancing). Easy, but load skews, a crash or deploy logs out everyone on that box, and scaling in drops sessions. The Twelve-Factor App says flatly that sticky sessions should never be relied on.
- In a shared store. Sessions go to Redis or Memcached with an expiry, and any server reads them. One small lookup per request buys interchangeable servers.
- In a signed token. The client carries a signed token such as a JWT, and any server can verify it without a lookup. Revoking it early is hard, so keep it short-lived and pair it with a refresh token (authentication: sessions, tokens and JWT).
Some servers are stateful by nature, and that is fine when it is deliberate: databases, WebSocket gateways that hold open connections, multiplayer game servers. Keep the state in as few, well-understood places as you can, and keep the request-handling tier stateless.
How the server talks back
Client-server has an asymmetry: only the client can open a connection. Most phones and home routers sit behind NAT and firewalls with no public address, so your server can’t dial them. Every “push” is therefore a client-opened channel (real-time connections):
- Request-response for everything the user asks for: REST, gRPC or GraphQL (REST vs GraphQL vs gRPC).
- Polling: ask every N seconds. Simple and wasteful.
- Long polling: the server holds the request until there is news.
- Server-Sent Events: one long response that streams events down.
- WebSockets: an upgraded connection both sides can write to.
- Mobile push: the phone’s OS keeps one connection to Apple’s or Google’s push service, which wakes your app. Even that is client-server.
Choosing among them, and fanning one event out to millions of connections, is the subject of real-time updates.
Scaling the server side
Because the server is central, it is where load concentrates. The usual path, one step at a time (vertical vs horizontal scaling):
- One server runs the app and the database.
- A bigger server (scale up): simple, until you hit the largest machine or its price.
- Separate the database and run N stateless app servers behind a load balancer (scale out). This is where statelessness pays.
- Cache and CDN: serve repeated reads from memory and static files from the edge (CDN, scaling reads).
- Read replicas, then shards when the database becomes the limit (scaling writes).
A worked estimate for a train-booking site in the style of IRCTC (the inputs are assumptions): 10 million daily users × 20 requests each = 200 million requests a day ÷ 86,400 s ≈ 2,300 requests a second on average. At 10:00, when tatkal booking opens, assume 10× that: 23,000 a second. If one stateless app server spends about 8 ms of CPU per request on 8 cores, it handles 8 × 1,000 ÷ 8 = 1,000 requests a second; run it at 60% for headroom and you need 23,000 ÷ 600 ≈ 39 servers, plus spares for failures and deploys. If 90% of requests are reads and a cache answers 80% of those, the database sees 23,000 × 0.9 × 0.2 ≈ 4,100 reads and 2,300 writes a second: replicas take the reads, and the seat inventory becomes a write hotspot (contention and double booking).
Going deeper: failure modes
The server is a single point of failure and a bottleneck. Centralisation means one outage stops every client. Run at least two instances in different zones behind a health-checked load balancer, replicate the data tier, and know what fails over and how fast (single point of failure).
The network is unreliable. A request can be lost on the way in, the response on the way out, and from the client the two look the same. Give every call a timeout, retry only with backoff and jitter, and make writes safe to repeat with an idempotency key, so a retried “book this seat” doesn’t book two (idempotency, timeouts, retries and backoff).
Clients move as a herd. After an outage or a deploy, two million connected apps that reconnect at once send two million requests in the first second. Spread over a random 30-second window, that is about 67,000 a second. Jitter on reconnects and retries is what keeps a recovering server alive (retry storm).
You can’t update the clients. You deploy the server; users update the app when they feel like it, and some never do. Keep APIs backward compatible: add fields, never repurpose or remove them without a long deprecation, version when you must break, and ship a minimum-version check that can ask very old apps to update (serialization and schema evolution).
The client is outside your trust boundary. Anyone can call your API with a script instead of your app.
Never trust the client
Recompute prices, availability and permissions on the server from your own data. If the app sends {"seat": "B4-32", "price": 1}, ignore the price, check the user may book, and rate-limit the endpoint. Client-side checks improve the experience; only server-side checks protect the system.
Client-server or peer-to-peer
| Client-server | Peer-to-peer | |
|---|---|---|
| Who holds data and rules | The server: one authority | Every peer holds a share; no central authority |
| Scaling | You add server capacity as clients grow | Capacity grows as peers join, since each also serves |
| Cost | You pay for servers and bandwidth | Users’ machines and bandwidth carry much of it |
| Reliability | The server is a single point of failure unless replicated | No single point, but peers come and go |
| Security and trust | One place to authenticate, authorise and audit | Hard: any peer may lie |
| Reaching each other | Clients find a known address | Discovery and NAT traversal needed |
| Examples | Web apps, mobile backends, databases, email | BitTorrent, blockchains, WebRTC media |
Cheat sheet
| Idea | Remember it as |
|---|---|
| Definition | Clients start requests, servers respond; roles are per connection |
| Why it works | Separation of concerns: server owns data and rules, both sides evolve independently |
| Stateless server | Request carries what’s needed; buys visibility, reliability, scalability |
| Cold HTTPS | DNS + TCP (1 RTT) + TLS 1.3 (1 RTT) + request (1 RTT) ≈ 3 RTTs |
| Distance | Pune → Singapore 53 ms, → US East 201 ms (Azure P50) |
| Fewer round trips | Reuse connections, HTTP/2, HTTP/3 (2 RTTs cold), CDN edge; 0-RTT only for idempotent requests |
| Tiers | 2-tier: client to DB. 3-tier: UI, app, data. N-tier: more, each a hop |
| Sessions | Shared store or signed token; avoid sticky sessions |
| Server push | Client opens the channel: polling, long polling, SSE, WebSockets, mobile push |
| Capacity | Users × requests ÷ 86,400 × peak factor ÷ (per-server rate × utilisation) |
| Never | Trust the client, retry without jitter, break old app versions |
How to say it in the interview
“It’s a client-server design: phones and browsers call one HTTPS API through a CDN and a load balancer. The API tier is stateless: sessions are short-lived signed tokens, so any of the 40 or so servers can take any request, and I add servers when CPU or latency rises. Behind it, Redis caches hot reads and PostgreSQL holds bookings, with read replicas for searches. Users are in India, so I host in Mumbai and terminate TLS at the edge; one cold request is three round trips, so connection reuse matters. Updates reach the app over SSE or push notifications, every write takes an idempotency key, and the server recomputes prices rather than trusting the app.”
Why keep servers stateless? Any instance can serve any request, so you scale by adding instances, deploy by replacing them, and lose nothing when one dies. The cost is a session lookup or a token check per request, and some state moves to a shared store.
Thin or thick client? Thin (server-rendered) for reach and fast first loads on cheap phones; thick (SPA or native) for rich interaction and offline use. Either way the server validates everything, and a BFF can shape responses per client.
How does the server push to the client? It can’t open a connection, because clients sit behind NAT. The client keeps one open: SSE for one-way updates, WebSockets for two-way, long polling as a fallback, and the phone’s push service when the app is closed.
What happens between typing a URL and seeing the page? DNS resolves the name; the browser opens TCP and TLS (two round trips, or one with QUIC); it sends a GET, often answered by a CDN; it parses the HTML and fetches CSS, JavaScript and images over the same connection; then it renders. Most of the time goes on round trips, not on the server (DNS, HTTP and HTTPS).
Client-server or peer-to-peer? Client-server when you need one authority over data, simple security and control; peer-to-peer when bandwidth costs dominate and peers can share the load, as in file distribution or call media. Most products mix them.
Where to go next
- Peer-to-peer architecture: the other basic style.
- HTTP and HTTPS and DNS: the protocols under every request.
- Stateful vs stateless design: where state can safely live.
- Load balancing: L4 vs L7: spreading clients across servers.
- Real-time updates: pushing to millions of clients.
- Back-of-envelope estimation: more capacity arithmetic.
- Monolith vs microservices: what goes inside the server tier.
- Choosing an architecture style: where client-server fits among the styles.
References and further reading
- Architectural Styles and the Design of Network-based Software Architectures, Chapter 3 (Roy Fielding, 2000)ics.uci.eduThe client-server style (clients trigger, servers react), separation of concerns, layered-client-server as 2-, 3- and multi-tier, client-stateless-server, remote session and remote data access.
- Architectural Styles and the Design of Network-based Software Architectures, Chapter 5: REST (Roy Fielding, 2000)ics.uci.eduREST derived by adding constraints (client-server, stateless, cache, uniform interface, layered system, optional code-on-demand), with the properties each induces and what each costs.
- RFC 9110: HTTP Semanticsrfc-editor.orgClient and server as roles per connection, HTTP as a stateless protocol that enables load balancing, and intermediaries: proxies, gateways (reverse proxies) and tunnels.
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3rfc-editor.orgThe 1-RTT full handshake and the 0-RTT mode for resumed connections, whose early data is not forward secret and can be replayed.
- RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transportrfc-editor.orgA handshake that combines transport and cryptographic negotiation, optional 0-RTT data, streams over UDP, and connection migration.
- RFC 9114: HTTP/3rfc-editor.orgWhy HTTP/1.1 opens several TCP connections, why HTTP/2’s multiplexing stalls every stream on one lost packet, and how QUIC’s per-stream delivery fixes it.
- Azure Architecture Center: N-tier architecture stylelearn.microsoft.comLayers versus tiers, strict and relaxed tier models, closed and open layers, stateless web and business tiers, subnets as security boundaries, and when N-tier fits.
- The Twelve-Factor App: VI. Processes12factor.netStateless, share-nothing processes, persistent data in backing services, and why sticky sessions should never be relied on.
- Azure network round-trip latency statisticslearn.microsoft.comP50 round-trip times between Azure regions (July 2026): Central India to Southeast Asia 53 ms, to West Europe 139 ms and to East US 201 ms.
- MDN Web Docs: Overview of HTTPdeveloper.mozilla.orgHTTP as a client-server protocol with proxies in between, stateless but not sessionless thanks to cookies, and connection reuse.