<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: The Byte Mechanics</title>
    <description>The latest articles on DEV Community by The Byte Mechanics (@thebytemechanics).</description>
    <link>https://dev.to/thebytemechanics</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4220565%2F1a4197b1-98e5-49da-9927-d029753b0184.png</url>
      <title>DEV Community: The Byte Mechanics</title>
      <link>https://dev.to/thebytemechanics</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thebytemechanics"/>
    <language>en</language>
    <item>
      <title>Stop Starting With Microservices: The Path Real Systems Actually Follow</title>
      <dc:creator>The Byte Mechanics</dc:creator>
      <pubDate>Sun, 01 Nov 2026 14:56:55 +0000</pubDate>
      <link>https://dev.to/thebytemechanics/stop-starting-with-microservices-the-path-real-systems-actually-follow-1kfc</link>
      <guid>https://dev.to/thebytemechanics/stop-starting-with-microservices-the-path-real-systems-actually-follow-1kfc</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/p/d9b73e1233ae" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A new project kicks off. Someone opens a blank page and says, &lt;em&gt;"Let's build it with microservices."&lt;/em&gt; Everyone nods. It sounds modern. It sounds scalable.&lt;/p&gt;

&lt;p&gt;But here's the surprise: &lt;strong&gt;most high-scale systems don't start with microservices.&lt;/strong&gt; They grow into them, and only when the system demands it.&lt;/p&gt;

&lt;p&gt;This article walks through that growth one stage at a time. At each stage you'll see what breaks, why it breaks, and what teams do about it. If you'd rather watch, there's a 10-minute animated version of this article:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/HKnO1UPimhw" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Start with a monolith
&lt;/h2&gt;

&lt;p&gt;Every system starts somewhere, and the first goal isn't scale. It's &lt;strong&gt;correctness, speed of development and clarity&lt;/strong&gt;. That's why most successful systems begin as a monolith.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;monolith&lt;/strong&gt; is one application that holds every feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One backend&lt;/strong&gt;, the code that runs on the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One deployment&lt;/strong&gt;, so you ship it as a single unit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usually one database&lt;/strong&gt;, where all the data lives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside, each feature is a &lt;strong&gt;module&lt;/strong&gt;, a separate area of the code. Modules call each other directly, as ordinary function calls with no network in between. That keeps two things easy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transactions are simple.&lt;/strong&gt; A transaction is a group of changes that succeed or fail together, and a single database handles it for you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging is easy.&lt;/strong&gt; One process, one log and one stack trace.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwul6nkhr3cvsgb863qhj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwul6nkhr3cvsgb863qhj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This leads to the first rule of architecture:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't add distributed complexity, meaning spreading work across many machines, until the system forces you to.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2. When the monolith feels heavy, fix coupling first
&lt;/h2&gt;

&lt;p&gt;As features and engineers pile up, the monolith starts to feel heavy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Builds slow down.&lt;/li&gt;
&lt;li&gt;Deployments get risky.&lt;/li&gt;
&lt;li&gt;A small change breaks something unrelated.&lt;/li&gt;
&lt;li&gt;Teams collide in the same repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many teams jump straight to microservices here. &lt;strong&gt;That's a mistake&lt;/strong&gt;, because the real problem isn't the monolith. It's &lt;strong&gt;coupling&lt;/strong&gt;: parts that depend too tightly on each other's insides.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;strong&gt;modular monolith&lt;/strong&gt;. It still ships as one unit, but inside it's split into clean modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each module has a &lt;strong&gt;defined interface&lt;/strong&gt;, the official doorway other code must use.&lt;/li&gt;
&lt;li&gt;Each module &lt;strong&gt;owns its data&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;No module &lt;strong&gt;reaches into another's internals&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Each module follows a &lt;strong&gt;domain boundary&lt;/strong&gt;, a business area like orders or payments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsv9d3ps19yt3j4u486kj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsv9d3ps19yt3j4u486kj.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step matters more than it looks. &lt;strong&gt;If you split tightly coupled code into services, the coupling doesn't disappear. It just moves onto the network&lt;/strong&gt;, where it's slower, harder to debug and can fail in new ways. Clean module boundaries now become clean service boundaries later.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. When traffic grows, split into microservices
&lt;/h2&gt;

&lt;p&gt;In a monolith, &lt;strong&gt;scaling means scaling everything&lt;/strong&gt;. You add more running copies of the whole app, called &lt;strong&gt;instances&lt;/strong&gt;. If payments get heavy traffic, the entire system gets more instances, including all the parts that were fine.&lt;/p&gt;

&lt;p&gt;At small scale that's fine. At large scale it's inefficient and expensive, because different parts behave differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read-heavy&lt;/strong&gt; parts mostly fetch data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-heavy&lt;/strong&gt; parts mostly save data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU-heavy&lt;/strong&gt; parts need lots of processing power.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I/O-heavy&lt;/strong&gt; parts spend their time waiting on disks or networks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is usually the moment to move to &lt;strong&gt;microservices&lt;/strong&gt;: small, separate applications, each owning one domain. Each one can &lt;strong&gt;scale, deploy and fail on its own&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fro6how124dfqlctyxkog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fro6how124dfqlctyxkog.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That solves scaling. But there's a catch. &lt;strong&gt;Before, everything lived in one process. Now everything lives on the network.&lt;/strong&gt; Every section below is a consequence of that one sentence.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Services need to find each other: service discovery and the API gateway
&lt;/h2&gt;

&lt;p&gt;Services now run on different machines, containers or pods. (A &lt;strong&gt;container&lt;/strong&gt; is a packaged, isolated box for running an app, and a &lt;strong&gt;pod&lt;/strong&gt; is a common way of grouping containers, for example in Kubernetes.) These instances restart, autoscale and move around, so &lt;strong&gt;hardcoding addresses is impossible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;service discovery&lt;/strong&gt;. A &lt;strong&gt;registry&lt;/strong&gt; tracks every available instance, and services ask it where to send each request.&lt;/p&gt;

&lt;p&gt;The next problem is which service the app on your phone should call. With one backend there was one API. Now there are many. You don't want login checks or traffic limits copied into every service, and you don't want the frontend knowing your internal layout.&lt;/p&gt;

&lt;p&gt;So you add an &lt;strong&gt;API gateway&lt;/strong&gt;, a single entry point that handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing&lt;/strong&gt; each request to the right service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;, checking who you are&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throttling&lt;/strong&gt;, limiting how fast a client can call&lt;/li&gt;
&lt;li&gt;Sometimes &lt;strong&gt;combining responses&lt;/strong&gt; from several services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jp77hhkljo16qwdt8ws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3jp77hhkljo16qwdt8ws.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The database becomes the bottleneck
&lt;/h2&gt;

&lt;p&gt;As traffic keeps climbing, the first thing to struggle is usually &lt;strong&gt;the database, not the services&lt;/strong&gt;. Services scale horizontally: you just add more machines. Databases are much harder.&lt;/p&gt;

&lt;p&gt;The symptoms are familiar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Queries slow down.&lt;/li&gt;
&lt;li&gt;CPU spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locks&lt;/strong&gt; appear. A lock is a hold on data while it's being changed, which makes everyone else wait.&lt;/li&gt;
&lt;li&gt;Write delays grow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Fix 1: read replicas.&lt;/strong&gt; These are read-only copies of the database. Heavy queries move there, so reads are separated from writes. That works for a while, until the &lt;strong&gt;primary&lt;/strong&gt;, the main copy that accepts writes, becomes the bottleneck itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix 2: sharding.&lt;/strong&gt; The data is split across multiple machines, usually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;By user&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;By tenant&lt;/strong&gt;, meaning a customer organization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;By hash&lt;/strong&gt;, a formula that assigns each record to a bucket&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7h90rto468tsvyps40fv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7h90rto468tsvyps40fv.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Transactions across services: the Saga pattern
&lt;/h2&gt;

&lt;p&gt;In a monolith, one transaction can safely update many tables. That's an &lt;strong&gt;ACID transaction&lt;/strong&gt;: a guarantee that a group of changes happens completely or not at all, and stays correct even when others are working at the same time.&lt;/p&gt;

&lt;p&gt;Now one request, like &lt;em&gt;create an order&lt;/em&gt;, may touch the &lt;strong&gt;order, payment, inventory and notification&lt;/strong&gt; services, each with its own database. There's no easy way to wrap all of them in one ACID transaction. Forcing &lt;strong&gt;strong consistency&lt;/strong&gt;, where everyone sees the same correct data at the same moment, usually hurts performance and reliability.&lt;/p&gt;

&lt;p&gt;So high-scale systems accept &lt;strong&gt;eventual consistency&lt;/strong&gt;: data may be briefly out of sync, but it catches up. They get there with three tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Events&lt;/strong&gt;, to tell other services what happened&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries&lt;/strong&gt;, to recover from temporary failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compensation&lt;/strong&gt;, which means undoing an earlier step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common pattern that combines them is the &lt;strong&gt;Saga&lt;/strong&gt;. Each step succeeds independently. If a later step fails, earlier steps are retried or undone. For example, if stock can't be reserved, the payment is refunded and the order is cancelled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3uamcmz4irrttjiusd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3uamcmz4irrttjiusd9.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's more scalable, but it's also more complex. That trade-off comes up at every stage.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Stop waiting: event-driven design
&lt;/h2&gt;

&lt;p&gt;With a few services, &lt;strong&gt;synchronous calls&lt;/strong&gt; work fine. Synchronous means the caller waits for an answer, usually over HTTP. As services multiply, these call chains become slow and fragile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One slow service delays everything.&lt;/li&gt;
&lt;li&gt;One failure breaks the whole flow.&lt;/li&gt;
&lt;li&gt;Traffic spikes overload the services further down the line.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So high-scale systems switch to &lt;strong&gt;messaging&lt;/strong&gt;. An &lt;strong&gt;event&lt;/strong&gt; is a small message saying &lt;em&gt;something happened&lt;/em&gt;. Instead of calling a service directly, you &lt;strong&gt;publish the event to a queue or stream&lt;/strong&gt;, an ordered line of messages waiting to be processed. &lt;strong&gt;Consumers&lt;/strong&gt;, the services that read events, handle them independently.&lt;/p&gt;

&lt;p&gt;What you gain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Services become &lt;strong&gt;loosely coupled&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spikes are absorbed&lt;/strong&gt; by the queue.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failures can be retried.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Work happens &lt;strong&gt;asynchronously&lt;/strong&gt;, so no one waits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3chgmc7d7fn6wgxl1lnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3chgmc7d7fn6wgxl1lnz.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's why large systems lean on &lt;strong&gt;Kafka&lt;/strong&gt;, a popular event-streaming platform, as well as queues and &lt;strong&gt;pub-sub&lt;/strong&gt;, where one sender reaches many listeners.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Retries are dangerous without idempotency
&lt;/h2&gt;

&lt;p&gt;In distributed systems, &lt;strong&gt;failures are normal&lt;/strong&gt;. Messages get retried. Requests time out, giving up after waiting too long. A consumer can crash &lt;strong&gt;after doing the work but before acknowledging it&lt;/strong&gt;, that is, before telling the queue &lt;em&gt;"I'm done."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So &lt;strong&gt;the same request may run more than once&lt;/strong&gt;. For payments, orders, wallet updates or inventory changes, that's dangerous. Picture a customer who pays \$20 once and gets charged \$40.&lt;/p&gt;

&lt;p&gt;The answer is &lt;strong&gt;idempotency&lt;/strong&gt;: &lt;em&gt;the same request produces the same result, even if it runs multiple times.&lt;/em&gt; Systems achieve it with two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency keys&lt;/strong&gt; (or unique request IDs), labels attached to each request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplication tables&lt;/strong&gt;, a record of which requests have already been handled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a request arrives, the consumer checks its key. If the key isn't in the table, it processes the request and records the key. If the key is already there, it skips the work and returns the earlier result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiati1ijk6xk7tqli2iyp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiati1ijk6xk7tqli2iyp.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At scale, &lt;strong&gt;idempotency isn't an optimization. It's a requirement.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Designing to fail safely: rate limits and backpressure
&lt;/h2&gt;

&lt;p&gt;At high scale, traffic isn't steady. Requests can suddenly spike, and a system that tries to handle everything at once can crash. So it has to &lt;strong&gt;control the flow&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; restricts how much traffic is allowed in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backpressure&lt;/strong&gt; slows down producers, the senders, when consumers can't keep up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queues&lt;/strong&gt; buffer short spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead-letter queues&lt;/strong&gt; capture messages that keep failing, so they can be inspected instead of blocking everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes the right move is to &lt;strong&gt;reject a request&lt;/strong&gt; with a polite "try again later" (HTTP 429) instead of crashing the whole service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50t3etvexn9kbkhlz7to.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F50t3etvexn9kbkhlz7to.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;High-scale systems aren't designed to never fail. &lt;strong&gt;They're designed to fail safely.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  10. Observability and the CAP trade-off
&lt;/h2&gt;

&lt;p&gt;In a monolith, logs are usually enough. In a distributed system, &lt;strong&gt;one request can pass through many services&lt;/strong&gt;, and you need three more tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tracing&lt;/strong&gt; follows one request's path, so you can see which service is slow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics&lt;/strong&gt; are numbers tracked over time, so you can see where load is growing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alerts&lt;/strong&gt; tell you about problems &lt;em&gt;before&lt;/em&gt; users complain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together these make up &lt;strong&gt;observability&lt;/strong&gt;, the ability to see inside a running system. At this scale it has to be part of the design, not an afterthought.&lt;/p&gt;

&lt;p&gt;Then comes the biggest trade-off, known as &lt;strong&gt;CAP&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; everyone sees the same latest data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability:&lt;/strong&gt; the system always answers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partition tolerance:&lt;/strong&gt; the system keeps working when the network splits parts apart.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Networks &lt;em&gt;do&lt;/em&gt; split in distributed systems, so partition tolerance isn't optional. When a split happens, you have to choose: &lt;strong&gt;keep answering, even with slightly stale data (availability), or refuse until the data is correct (consistency).&lt;/strong&gt; You can't have all three, and different parts of the same product choose differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Payments and wallets&lt;/strong&gt; favor &lt;strong&gt;consistency&lt;/strong&gt;. A wrong balance is worse than a short wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feeds and search&lt;/strong&gt; favor &lt;strong&gt;availability&lt;/strong&gt;. A slightly stale feed beats an error page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analytics&lt;/strong&gt; accepts &lt;strong&gt;eventual consistency&lt;/strong&gt;. Numbers that catch up in a minute are fine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodsqc6kqm65efljjrtoc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fodsqc6kqm65efljjrtoc.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The path, in five steps
&lt;/h2&gt;

&lt;p&gt;Most successful systems follow the same path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Start simple.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Add structure.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Split when needed.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Distribute when required.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Optimize when scale demands it.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Three takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopyvgq2f8n0t1bp91qi8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fopyvgq2f8n0t1bp91qi8.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Start with a monolith.&lt;/strong&gt; Don't add distributed complexity until the system forces you to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fix coupling first.&lt;/strong&gt; Use a modular monolith before you split into services. Otherwise you'll just move the coupling onto the network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Microservices aren't a starting point.&lt;/strong&gt; They're a response to scale, and every stage brings new problems to solve: discovery, data, transactions, retries, traffic and visibility.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Good architecture isn't the most advanced design. &lt;strong&gt;It's the right design for today, plus knowing when it's time to evolve.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;▶️ Watch the animated version:&lt;/strong&gt; &lt;a href="https://www.youtube.com/watch?v=HKnO1UPimhw" rel="noopener noreferrer"&gt;Stop Starting With Microservices (YouTube)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; why one bad request can crash every user on a server, and how processes and threads decide the blast radius. &lt;a href="https://youtu.be/QNoIC4uyFh8" rel="noopener noreferrer"&gt;Watch Processes vs Threads&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this helped, follow The Byte Mechanics here for a new visual deep dive every week.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;▶️ &lt;strong&gt;YouTube:&lt;/strong&gt; &lt;a href="https://www.youtube.com/@thebytemechanics" rel="noopener noreferrer"&gt;youtube.com/@thebytemechanics&lt;/a&gt;, full animated videos&lt;/li&gt;
&lt;li&gt;📸 &lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/thebytemechanics/" rel="noopener noreferrer"&gt;instagram.com/thebytemechanics&lt;/a&gt;, 60-second Reels&lt;/li&gt;
&lt;li&gt;📘 &lt;strong&gt;Facebook:&lt;/strong&gt; &lt;a href="https://www.facebook.com/thebytemechanics/" rel="noopener noreferrer"&gt;facebook.com/thebytemechanics&lt;/a&gt;, Reels and updates&lt;/li&gt;
&lt;li&gt;𝕏 &lt;strong&gt;X:&lt;/strong&gt; &lt;a href="https://x.com/thebytemechanic" rel="noopener noreferrer"&gt;x.com/thebytemechanic&lt;/a&gt;, quick takes and diagrams&lt;/li&gt;
&lt;li&gt;✍️ &lt;strong&gt;Medium:&lt;/strong&gt; &lt;a href="https://medium.com/@thebytemechanics" rel="noopener noreferrer"&gt;medium.com/@thebytemechanics&lt;/a&gt;, the written deep dives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;About:&lt;/strong&gt; &lt;em&gt;The Byte Mechanics turns how software really works into short animated explainers: system design, backend and operating systems, one diagram at a time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>backend</category>
    </item>
    <item>
      <title>One Bad Request Crashed Everyone: Processes vs Threads</title>
      <dc:creator>The Byte Mechanics</dc:creator>
      <pubDate>Sun, 01 Nov 2026 14:56:31 +0000</pubDate>
      <link>https://dev.to/thebytemechanics/one-bad-request-crashed-everyone-processes-vs-threads-14ai</link>
      <guid>https://dev.to/thebytemechanics/one-bad-request-crashed-everyone-processes-vs-threads-14ai</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@thebytemechanics/one-bad-request-crashed-everyone-processes-vs-threads-4803683bf947?sharedUserId=thebytemechanics" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A user uploads one corrupted image. Your server crashes. And every other user on it goes down too: their uploads, their checkouts, their sessions, all gone.&lt;/p&gt;

&lt;p&gt;One bad request shouldn't be able to do that. But on a lot of servers, it can. Whether it does comes down to one choice: &lt;strong&gt;processes or threads&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article explains both, the traps that come with each, and how real systems combine them. If you'd rather watch, here's the 8-minute animated version:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/QNoIC4uyFh8" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The problem: many requests at once
&lt;/h2&gt;

&lt;p&gt;Hundreds of requests hit a server at the same time: uploads, logins, checkouts. Handle them one by one, and everyone waits, while the CPU sits idle every time a request waits on the disk or the database.&lt;/p&gt;

&lt;p&gt;So the operating system gives us two ways to work on many requests at once: &lt;strong&gt;processes&lt;/strong&gt; and &lt;strong&gt;threads&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A process is a house
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;process&lt;/strong&gt; is a running program with its own private world. The operating system gives it its own memory (code, data, heap and stack), its own ID, its own open files and its own permissions.&lt;/p&gt;

&lt;p&gt;Think of a process as a house. Everything inside belongs to that house, and the neighbours can't walk in and move your furniture.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8ki4sg5f080coicp5eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8ki4sg5f080coicp5eq.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A process is a house: its own code, heap, stack, files and PID, walled off from neighbouring processes&lt;/p&gt;

&lt;p&gt;This isolation is enforced by hardware. Each process sees its own &lt;strong&gt;virtual addresses&lt;/strong&gt;, and the CPU translates them to real memory. Process A literally cannot point at Process B's memory. So one process can corrupt its own memory, freeze or crash, and the house next door doesn't notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Threads are the people inside
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;thread&lt;/strong&gt; lives inside a process. It's one path of execution, a sequence of instructions the CPU runs. Every process starts with one thread and can create more.&lt;/p&gt;

&lt;p&gt;If the process is the house, threads are the people living in it. Each person has their own to-do list and remembers where they are in it: that's the thread's own &lt;strong&gt;stack and registers&lt;/strong&gt;. But they share everything else: the same heap, the same global variables, the same open files.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;That sharing is the whole point of threads. It's also the whole problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Why threads became the default
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cheaper to create.&lt;/strong&gt; A new process needs a whole new memory space. Copy-on-write makes that cheaper, but it's still real work. A new thread only needs its own stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheaper to switch.&lt;/strong&gt; Switching between threads of the same process keeps the same memory map. Switching between processes changes the map, and the CPU's address cache goes cold. One switch costs microseconds, and thousands per second add up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier to share data.&lt;/strong&gt; Processes must send messages through pipes, sockets or queues, and every message is copied. One thread can simply write a variable that another thread reads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fast, simple, no copying. But "any thread can change anything, at any time" is a dangerous sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The trap: race conditions
&lt;/h2&gt;

&lt;p&gt;Two threads each add one to a shared counter that starts at 100. You expect 102. But &lt;code&gt;count = count + 1&lt;/code&gt; isn't one step. It's three: read, add, write.&lt;/p&gt;

&lt;p&gt;Thread A reads 100. Before it writes, the CPU switches. Thread B also reads 100, adds one and writes 101. Thread A resumes, adds one to its stale 100, and also writes 101. Two increments happened. One got lost.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplszl1fonyyrhsi4polq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fplszl1fonyyrhsi4polq.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Race condition: two threads read a shared counter of 100, both add one and write 101, so one update is lost&lt;/p&gt;

&lt;p&gt;That's a &lt;strong&gt;race condition&lt;/strong&gt;. It only shows up under load, disappears when you add a print statement, and never reproduces on your laptop. Now imagine the counter is an account balance, or the number of concert tickets left.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The fix, and the next trap
&lt;/h2&gt;

&lt;p&gt;The standard fix is a &lt;strong&gt;lock&lt;/strong&gt; (a mutex). A thread takes the lock before touching the counter, and everyone else waits until it's released. Read, add and write now happen as one unit.&lt;/p&gt;

&lt;p&gt;But locks bring their own trap. Thread A holds lock 1 and waits for lock 2. Thread B holds lock 2 and waits for lock 1. Neither will ever let go.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3noah73ggmpbm94i064j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3noah73ggmpbm94i064j.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Deadlock: thread A holds lock 1 and waits for lock 2 while thread B holds lock 2 and waits for lock 1, so everything stops&lt;/p&gt;

&lt;p&gt;That's a &lt;strong&gt;deadlock&lt;/strong&gt;. Nothing crashes. Everything just stops. Shared memory is fast, but every shared thing needs rules. Get them wrong, and you get lost updates or a frozen system.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Back to the corrupted image
&lt;/h2&gt;

&lt;p&gt;Image decoders are often written in C. A broken file can make one touch invalid memory. When a thread does that, the operating system doesn't kill just that thread. It kills the &lt;strong&gt;whole process&lt;/strong&gt;, and every other thread dies with it. That's how one upload took down 500 users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijhhpewg9k7fb509s67q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fijhhpewg9k7fb509s67q.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shared memory, shared fate: one thread touches invalid memory, the OS sends a segfault, and every thread in the server process dies&lt;/p&gt;

&lt;p&gt;If each request were handled by a separate &lt;strong&gt;worker process&lt;/strong&gt;, only that worker would die. A supervisor starts a fresh one, and nobody else notices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbt0h64jm4zjkhku9yuks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbt0h64jm4zjkhku9yuks.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Separate worker processes: the bad photo.jpg crashes only worker 3 while workers 1, 2 and 4 keep running&lt;/p&gt;

&lt;p&gt;Chrome made this choice on purpose. Each site gets its own renderer process, so a crashing page only kills its own tab. It's a security win too: a malicious site in one process can't read your banking tab's memory. The price is RAM, which is why Chrome uses so much of it. &lt;strong&gt;Isolation costs resources.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Your workload and language matter
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;I/O-bound work&lt;/strong&gt; (mostly waiting on databases, networks or disks): threads are great. While one waits, another runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU-bound work&lt;/strong&gt; (image processing, number crunching): you want real parallel execution on every core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python:&lt;/strong&gt; in standard CPython, the Global Interpreter Lock lets only one thread run Python code at a time. For CPU-heavy Python, use multiple processes, one per core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; your JavaScript runs on a single thread with an event loop, and heavy work goes to worker threads or separate processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Real systems use both
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; starts a process per connection. A failed query stays inside its own process, but every backend also shares one block of memory for cached data. So if one backend crashes outright, the postmaster restarts all of them to be safe. Processes are also heavy, which is why people put a connection pooler in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL&lt;/strong&gt; handles each connection with a thread inside one server process. Lighter per connection, with shared memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; runs a few worker processes, usually one per core, each with an event loop juggling thousands of connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A typical Python web app&lt;/strong&gt; runs several worker processes with threads or async inside each.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft0bxo1j1qrl8fqeo2y4r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft0bxo1j1qrl8fqeo2y4r.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern used by PostgreSQL, MySQL, Nginx and Python web apps: processes for isolation and every core, threads or async for cheap concurrency&lt;/p&gt;

&lt;p&gt;The pattern is the same everywhere: &lt;strong&gt;processes for isolation and to use every core, threads or async inside them for cheap concurrency.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. How to choose
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qzdb9jd057izdzbh8bq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qzdb9jd057izdzbh8bq.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How to choose: use processes when crashes must stay contained or you need security boundaries; use threads when tasks share lots of data&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reach for processes&lt;/strong&gt; when a crash must stay contained, when you need security boundaries, or when your language can't run threads in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reach for threads&lt;/strong&gt; when tasks share a lot of data, when you need many lightweight workers, and when you're willing to protect shared state with care.&lt;/p&gt;

&lt;p&gt;In production, expect both: a few processes, with many threads or async tasks inside. With every request in threads under one roof, one corrupted image took everyone down. With separate worker processes, one worker dies, restarts, and nobody else notices.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;▶️ Watch the animated version:&lt;/strong&gt; &lt;a href="https://youtu.be/QNoIC4uyFh8" rel="noopener noreferrer"&gt;One Bad Request Crashed Everyone: Processes vs Threads (YouTube)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; how async and event loops handle thousands of connections with a single thread. &lt;a href="https://youtu.be/zYGXjMid7HY" rel="noopener noreferrer"&gt;Watch One Thread, 10,000 Users&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this helped, follow The Byte Mechanics here for a new visual deep dive every week.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;▶️ &lt;strong&gt;YouTube:&lt;/strong&gt; &lt;a href="https://www.youtube.com/@thebytemechanics" rel="noopener noreferrer"&gt;youtube.com/@thebytemechanics&lt;/a&gt;, full animated videos&lt;/li&gt;
&lt;li&gt;📸 &lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/thebytemechanics/" rel="noopener noreferrer"&gt;instagram.com/thebytemechanics&lt;/a&gt;, 60-second Reels&lt;/li&gt;
&lt;li&gt;📘 &lt;strong&gt;Facebook:&lt;/strong&gt; &lt;a href="https://www.facebook.com/thebytemechanics/" rel="noopener noreferrer"&gt;facebook.com/thebytemechanics&lt;/a&gt;, Reels and updates&lt;/li&gt;
&lt;li&gt;𝕏 &lt;strong&gt;X:&lt;/strong&gt; &lt;a href="https://x.com/thebytemechanic" rel="noopener noreferrer"&gt;x.com/thebytemechanic&lt;/a&gt;, quick takes and diagrams&lt;/li&gt;
&lt;li&gt;✍️ &lt;strong&gt;Medium:&lt;/strong&gt; &lt;a href="https://medium.com/@thebytemechanics" rel="noopener noreferrer"&gt;medium.com/@thebytemechanics&lt;/a&gt;, the written deep dives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;About:&lt;/strong&gt; &lt;em&gt;The Byte Mechanics turns how software really works into short animated explainers: system design, backend and operating systems, one diagram at a time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>programming</category>
      <category>computerscience</category>
      <category>backend</category>
    </item>
    <item>
      <title>One Thread, 10,000 Users: Async and Event Loops Explained</title>
      <dc:creator>The Byte Mechanics</dc:creator>
      <pubDate>Sun, 01 Nov 2026 14:55:41 +0000</pubDate>
      <link>https://dev.to/thebytemechanics/one-thread-10000-users-async-and-event-loops-explained-33ck</link>
      <guid>https://dev.to/thebytemechanics/one-thread-10000-users-async-and-event-loops-explained-33ck</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@thebytemechanics/one-thread-10-000-users-async-and-event-loops-explained-6771e101b577" rel="noopener noreferrer"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This server has one thread. Ten thousand people are using it right now, and none of them are waiting.&lt;/p&gt;

&lt;p&gt;Give each of those users their own thread instead, and the same server runs out of memory and falls over.&lt;/p&gt;

&lt;p&gt;So how does one thread beat ten thousand? This article walks through the event loop, async/await, and the one harmless-looking line of code that can freeze every user at once. If you'd rather watch, here's the 8-minute animated version:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/zYGXjMid7HY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The secret is waiting
&lt;/h2&gt;

&lt;p&gt;Take one web request. It reads the request, asks the database for some rows, maybe calls another API, and sends a response.&lt;/p&gt;

&lt;p&gt;Now time it. The CPU actually works for about two milliseconds. The other ninety-eight milliseconds are spent waiting: for the database, for the network, for the disk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F15bbg8zo8wyc5me9wtx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F15bbg8zo8wyc5me9wtx1.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A 100 ms request spends only 2 ms on the CPU and the rest waiting on the database, network and disk. Your server isn't busy, it's bored&lt;/p&gt;

&lt;p&gt;So your server isn't busy. It's bored. Most of every request is just waiting. And in the classic design, a waiting thread does nothing at all while still holding on to everything it owns.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why one thread per user runs out
&lt;/h2&gt;

&lt;p&gt;The classic answer is one thread per connection. A user connects, you give them a thread, and that thread waits for them. It's simple, and it works for a few hundred users.&lt;/p&gt;

&lt;p&gt;But every thread has its own stack, often around a megabyte reserved. Ten thousand threads means gigabytes of memory, just to wait. And the operating system has to keep switching between all of them, so more and more time goes into switching instead of real work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56tyjtg67z4vaec4vxf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F56tyjtg67z4vaec4vxf7.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thread per connection: a grid of 5,322 threads using 5.3 GB of RAM just to wait&lt;/p&gt;

&lt;p&gt;In 1999, Dan Kegel gave this wall a name: &lt;strong&gt;the C10K problem&lt;/strong&gt;. How do you handle ten thousand connections on one machine? The answer didn't come from adding more threads.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The waiter
&lt;/h2&gt;

&lt;p&gt;Picture a restaurant that hires one waiter per table. The waiter takes your order, walks to the kitchen, and stands there until the food is ready. A hundred tables means a hundred waiters, mostly standing around.&lt;/p&gt;

&lt;p&gt;Now picture a restaurant with one waiter. They take an order, hand it to the kitchen, and go straight to the next table. When the kitchen rings the bell, they pick up the food and deliver it. One waiter, a whole room of tables, and nobody waits on the waiter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F84ulpe63ihua64uxxaml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F84ulpe63ihua64uxxaml.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Restaurant analogy: one waiter per table means 100 waiters mostly standing around, versus one waiter serving every table&lt;/p&gt;

&lt;p&gt;That waiter has a name in software: &lt;strong&gt;the event loop&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The event loop
&lt;/h2&gt;

&lt;p&gt;An event loop is one thread running one simple cycle, over and over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check what's ready:&lt;/strong&gt; a new connection, a database reply, a timer that just went off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the code&lt;/strong&gt; for each ready thing, quickly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hand off the waits.&lt;/strong&gt; Whenever that code needs to wait, it doesn't. The wait goes to the operating system, and the loop moves on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then it goes back to step one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqa3jfz0iabkdxz3evg2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqa3jfz0iabkdxz3evg2j.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The event loop serving 10,000 connections: check what's ready, run the code quickly, hand off the waits to the OS&lt;/p&gt;

&lt;p&gt;Because the thread never stops to wait, it can juggle thousands of connections. It only spends time on the small slice that's real work. But notice the rule hiding in there:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every step has to be quick.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Remember that rule. It's the key to the line that freezes everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The operating system does the watching
&lt;/h2&gt;

&lt;p&gt;How does the loop know when something is ready? The operating system tells it.&lt;/p&gt;

&lt;p&gt;Sockets can be put in &lt;strong&gt;non-blocking mode&lt;/strong&gt;. Instead of sleeping until data arrives, a read returns straight away and says: nothing yet. Then the loop hands the operating system its whole list of sockets and says: wake me up when any of these are ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5j84pkzkl0gbixmem6rn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5j84pkzkl0gbixmem6rn.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The kernel watches every socket and wakes the event loop only when some of them are ready&lt;/p&gt;

&lt;p&gt;On Linux that's &lt;strong&gt;epoll&lt;/strong&gt;. On macOS and BSD it's &lt;strong&gt;kqueue&lt;/strong&gt;. On Windows, &lt;strong&gt;I/O completion ports&lt;/strong&gt;. Older calls like &lt;code&gt;select&lt;/code&gt; scanned every socket on every call. epoll just hands back the ready ones, so ten thousand idle connections cost almost nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. From callbacks to async/await
&lt;/h2&gt;

&lt;p&gt;In the early days, you gave the loop a function to call later: a &lt;strong&gt;callback&lt;/strong&gt;. Read the file, and when you're done, call this. Chain a few of those and the code drifts to the right, deeper and deeper. People called it callback hell.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Promises&lt;/strong&gt; flattened it out. A promise is a placeholder for a value that isn't ready yet. Then &lt;strong&gt;async and await&lt;/strong&gt; made it look like normal code again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgh621mdz2lpp9nc8z127.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgh621mdz2lpp9nc8z127.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Blocking code and async code look almost the same, but they are not: only the await version gives the loop back&lt;/p&gt;

&lt;p&gt;It reads top to bottom, exactly like blocking code. But it isn't blocking code, and the difference is the whole trick.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What await really does
&lt;/h2&gt;

&lt;p&gt;When your code hits &lt;code&gt;await&lt;/code&gt;, your function doesn't sit there waiting. It pauses, saves its place, and hands control back to the loop. The loop serves other requests. When the database replies, the loop picks your function up exactly where it left off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpentb94fklqmb93ti5gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpentb94fklqmb93ti5gp.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An async handler awaiting getUser and getOrders while the loop serves other requests in between&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;await&lt;/code&gt; means: &lt;em&gt;I'm going to wait, so go help someone else.&lt;/em&gt; This is called &lt;strong&gt;cooperative multitasking&lt;/strong&gt;. Every function promises to give the loop back quickly.&lt;/p&gt;

&lt;p&gt;Which raises a question. What happens if one function doesn't?&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The trap: one line freezes everyone
&lt;/h2&gt;

&lt;p&gt;The line that freezes everyone looks completely harmless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thumb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resizeImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 40 MB&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No await. Just two seconds of pure CPU work on the only thread you have. The same goes for hashing a password, parsing a huge file, a synchronous file read, a &lt;code&gt;sleep&lt;/code&gt;, or an old blocking HTTP library called from async code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65lvz3w1qxob3aicdb6r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F65lvz3w1qxob3aicdb6r.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The trap: resizeImage on a 40 MB file holds the only thread for 0.7 seconds and freezes every user&lt;/p&gt;

&lt;p&gt;For those two seconds, the loop is stuck. Every other request sits in the queue. Health checks time out, and your load balancer decides the server is dead. One request, and ten thousand users frozen.&lt;/p&gt;

&lt;p&gt;If you read the last article, this should sound familiar. There, one bad request &lt;em&gt;crashed&lt;/em&gt; everyone. Here, one slow request &lt;em&gt;freezes&lt;/em&gt; everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. The one rule
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The event loop coordinates. It never does the heavy lifting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send CPU-heavy work to a pool&lt;/strong&gt; of worker threads or processes, and await the result. In Python that's &lt;code&gt;await asyncio.to_thread(resize, img)&lt;/code&gt; (or a process pool for CPU-heavy Python). In Node it's &lt;code&gt;worker_threads&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node already does some of this for you.&lt;/strong&gt; File system calls, DNS lookups and some crypto run on libuv's small hidden thread pool (four threads by default), so they don't touch the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use async versions of everything that waits:&lt;/strong&gt; async database drivers, async HTTP clients. &lt;code&gt;requests.get()&lt;/code&gt; or &lt;code&gt;readFileSync()&lt;/code&gt; inside async code blocks the loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If a job is huge, cut it into chunks&lt;/strong&gt; so the loop can breathe in between.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvbbl7od5z6o54eqpcx21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvbbl7od5z6o54eqpcx21.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moving heavy work off the loop: Node's hidden libuv thread pool and a worker pool for CPU-bound tasks&lt;/p&gt;

&lt;h2&gt;
  
  
  10. How the giants do it
&lt;/h2&gt;

&lt;p&gt;In production you almost never see just one loop. You see one per CPU core.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; was built for exactly the C10K problem. It starts one worker process per core, and each worker runs its own event loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt; apps scale the same way, with one process per core using cluster mode or a process manager like PM2.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt; web apps run several Uvicorn workers, each with its own asyncio loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; executes commands on a single thread and still handles massive traffic, because each command is tiny and the loop never waits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt; hides all of it. You write plain blocking code in goroutines, and the runtime's network poller runs an event loop underneath for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Processes for cores, loops for waiting: four processes, one per core, each running its own event loop&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2m8bt3kewdcuq9sne06.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft2m8bt3kewdcuq9sne06.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pattern is the one from the last article: &lt;strong&gt;processes for cores and isolation, and inside each one, a loop for the waiting.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  11. The cheat sheet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use async&lt;/strong&gt; when your work is mostly waiting: web servers, APIs, chat apps, proxies, anything with lots of connections doing a little work each. Its risk is blocking the loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use threads or processes&lt;/strong&gt; for CPU-heavy work. Async won't speed up computation; for that you need more cores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In production, use both:&lt;/strong&gt; one loop per core for the waiting, and a worker pool for the heavy lifting.&lt;/p&gt;

&lt;p&gt;Ten thousand users, one thread. It works, as long as that thread never waits, and never does the heavy lifting itself.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;▶️ Watch the animated version:&lt;/strong&gt; &lt;a href="https://youtu.be/zYGXjMid7HY" rel="noopener noreferrer"&gt;Event Loop Explained: How One Thread Handles 10,000 Users (YouTube)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next up:&lt;/strong&gt; most of that 98% waiting is the database. Next time: how one index turns a 10-second query into 10 milliseconds.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this helped, follow The Byte Mechanics here for a new visual deep dive every week.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;▶️ &lt;strong&gt;YouTube:&lt;/strong&gt; &lt;a href="https://www.youtube.com/@thebytemechanics" rel="noopener noreferrer"&gt;youtube.com/@thebytemechanics&lt;/a&gt;, full animated videos&lt;/li&gt;
&lt;li&gt;📸 &lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a href="https://www.instagram.com/thebytemechanics/" rel="noopener noreferrer"&gt;instagram.com/thebytemechanics&lt;/a&gt;, 60-second Reels&lt;/li&gt;
&lt;li&gt;📘 &lt;strong&gt;Facebook:&lt;/strong&gt; &lt;a href="https://www.facebook.com/thebytemechanics/" rel="noopener noreferrer"&gt;facebook.com/thebytemechanics&lt;/a&gt;, Reels and updates&lt;/li&gt;
&lt;li&gt;𝕏 &lt;strong&gt;X:&lt;/strong&gt; &lt;a href="https://x.com/thebytemechanic" rel="noopener noreferrer"&gt;x.com/thebytemechanic&lt;/a&gt;, quick takes and diagrams&lt;/li&gt;
&lt;li&gt;✍️ &lt;strong&gt;Medium:&lt;/strong&gt; &lt;a href="https://medium.com/@thebytemechanics" rel="noopener noreferrer"&gt;medium.com/@thebytemechanics&lt;/a&gt;, the written deep dives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;About:&lt;/strong&gt; &lt;em&gt;The Byte Mechanics turns how software really works into short animated explainers: system design, backend and operating systems, one diagram at a time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>python</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
