<?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: Younes Merzouka</title>
    <description>The latest articles on DEV Community by Younes Merzouka (@ymerzouka).</description>
    <link>https://dev.to/ymerzouka</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%2F3778424%2F9fa7b890-b180-45e6-ab7b-f06c8fbdd6a3.png</url>
      <title>DEV Community: Younes Merzouka</title>
      <link>https://dev.to/ymerzouka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ymerzouka"/>
    <language>en</language>
    <item>
      <title>KCX - DevLog #1</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Wed, 12 Aug 2026 22:55:35 +0000</pubDate>
      <link>https://dev.to/ymerzouka/kcx-devlog-1-3a9f</link>
      <guid>https://dev.to/ymerzouka/kcx-devlog-1-3a9f</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello!!!&lt;/p&gt;

&lt;p&gt;So I decided to learn a bit more about CI/CD pipelines, and what better way to learn about them than to create your own!!! However, I also wanted to add my own spin to it: integrating it with Kubernetes and maybe Docker, to build a Kubernetes-native CI/CD tool. I'm not sure if the idea already exists out there, but I think it's worthwhile exploring for learning purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Name
&lt;/h2&gt;

&lt;p&gt;The goal is to make use of what Kubernetes natively provides and build on top of it to create a more "Kubernetes-native" solution to CI/CD. Hence the first letter in &lt;code&gt;KCX&lt;/code&gt; is &lt;strong&gt;K&lt;/strong&gt;ubernetes.&lt;/p&gt;

&lt;p&gt;Since this is a CI/CD tool, I reduced both into &lt;strong&gt;CX&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So in a sense, it's named &lt;strong&gt;Kubernetes CI/CD&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This DevLog
&lt;/h2&gt;

&lt;p&gt;The goal behind this devlog is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Share my progress&lt;/li&gt;
&lt;li&gt;Share any interesting ideas I run into while working on the tool&lt;/li&gt;
&lt;li&gt;Share my experience working with Go&lt;/li&gt;
&lt;li&gt;Engage with the community and listen for feedback on things I can improve, which is always welcome&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Will I Use to Build It?
&lt;/h2&gt;

&lt;p&gt;Since many tools in the cloud space already use Go, I decided to follow their lead. This also helps me get more familiar with the language, since I've wanted to learn it for a while.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any Progress?
&lt;/h2&gt;

&lt;p&gt;So far I've started exploring a bit around the core ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cloning a repository and executing a simple build script&lt;/li&gt;
&lt;li&gt;Creating a webhook and wiring it up with GitHub&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I also tried out a distributed work queue setup — multiple workers wired to an API service through a RabbitMQ broker. After testing it, I decided to keep the tool minimal for now, with workers running as local threads rather than their own services. Eventually, I plan to have the tool support a distributed setup too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Will I Use LLMs?
&lt;/h2&gt;

&lt;p&gt;Not as of yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Will These DevLogs Be Structured?
&lt;/h2&gt;

&lt;p&gt;I'll aim for weekly. Whether I actually stick to that depends on what else I have going on — we'll see!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next up, I want to get a full local build running end-to-end, with support for both public and private repositories. I also want to test this across multiple platforms — GitLab, Codeberg, and GitHub — to make sure the tool isn't tied to just one ecosystem.&lt;/p&gt;

</description>
      <category>sideprojects</category>
      <category>go</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Microservice Data Handling — Saga Pattern</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Wed, 05 Aug 2026 15:47:50 +0000</pubDate>
      <link>https://dev.to/ymerzouka/microservice-data-handling-saga-pattern-3hfk</link>
      <guid>https://dev.to/ymerzouka/microservice-data-handling-saga-pattern-3hfk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In a microservice architecture, it is often recommended that each service has its own separate &lt;br&gt;
data storage. This is because the nature of microservices — &lt;em&gt;and why they are created&lt;/em&gt; — &lt;br&gt;
mandates such design.&lt;/p&gt;

&lt;p&gt;Implementing such architecture would allow for many things, such as: separate schema updates,&lt;br&gt;
the use of proper storage technologies for the task the microservice is trying to achieve, ...&lt;br&gt;
But how can we handle data operations (retrieval/update/...) that span multiple services? For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A transaction requires multiple updates: &lt;em&gt;update balance and place order&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;A transaction requires joins across different services: 
&lt;em&gt;what is a customer's most commonly ordered items?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These data operations can fall under one of two categories: either we are trying to retrieve data&lt;br&gt;
from multiple services and aggregate them, or we are trying to make modifications/deletions/insertions&lt;br&gt;
that span multiple services.&lt;/p&gt;

&lt;p&gt;In this post, we will focus on the latter — or to be more specific: &lt;br&gt;
&lt;strong&gt;how do we handle cross-service transactions?&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Transactions
&lt;/h2&gt;

&lt;p&gt;Before we look into how to solve cross-service transactions, we first need to understand: &lt;br&gt;
&lt;strong&gt;&lt;em&gt;what are transactions?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose you want to update a single row in a database: a customer's address. You would need to &lt;br&gt;
execute a single instruction/operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'***'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'***'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is a single operation, it would either succeed or fail. This is what we call an &lt;br&gt;
&lt;strong&gt;atomic&lt;/strong&gt; operation since it satisfies the &lt;strong&gt;all-or-nothing&lt;/strong&gt; behaviour, meaning it either &lt;br&gt;
succeeds in its entirety or fails in its entirety.&lt;/p&gt;

&lt;p&gt;Now suppose it is our product's anniversary and we are feeling generous. We want to give our loyal &lt;br&gt;
customers (those that stayed for at least 10 years) a staggering &lt;strong&gt;1 dollar!!!&lt;/strong&gt; as in-app credits.&lt;br&gt;
The SQL for that is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;***&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose now that we have this amounts to 1000 updates, and the database server fails mid-operation.&lt;br&gt;
This would leave the system in an inconsistent state: some of the customers wouldn't receive our &lt;br&gt;
generous offer. This is an example of a &lt;strong&gt;non-atomic&lt;/strong&gt; operation. In such situation, it is more &lt;br&gt;
desirable to at least go back to the previous state and retry later.&lt;/p&gt;

&lt;p&gt;Transactions aim to address the problem of &lt;strong&gt;atomicity&lt;/strong&gt;. They allow you to execute multiple &lt;br&gt;
instructions that either succeed all together, or &lt;strong&gt;rollback&lt;/strong&gt; — allowing the preservation of the &lt;br&gt;
consistency of the database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transactions in distributed systems
&lt;/h2&gt;

&lt;p&gt;Now that we know what transactions are, how do they look when the operations need to happen across &lt;br&gt;
different nodes connected through a network? Or more formally: &lt;em&gt;how do they work in distributed systems?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But wait... what do distributed systems have to do with microservices?&lt;/em&gt; In some sense, &lt;br&gt;
microservices are a kind of distributed system where nodes are services connected through a network.&lt;/p&gt;

&lt;p&gt;Let us consider a scenario as an example to understand why distributed transactions differ from &lt;br&gt;
traditional ones: a customer makes a purchase, and as such we need to insert a new row and update his &lt;br&gt;
balance in an atomic manner. Why atomic, you might say? Suppose that we are able to insert a &lt;br&gt;
purchase row, but the update to the customer balance fails; then it is as if the customer has &lt;br&gt;
made a free purchase.&lt;/p&gt;

&lt;p&gt;For a single database, the atomicity of the operation can be insured using the traditional transaction we talked &lt;br&gt;
about earlier, that either succeeds or rolls back. But what if the &lt;code&gt;customers&lt;/code&gt; and &lt;code&gt;purchases&lt;/code&gt; &lt;br&gt;
table live on different nodes? How can we coordinate the two nodes to ensure the atomicity of the operation?&lt;/p&gt;

&lt;p&gt;This problem is not new, and databases have come up with multiple solutions for what is called &lt;br&gt;
&lt;strong&gt;&lt;em&gt;distributed transactions&lt;/em&gt;&lt;/strong&gt;. A common example is &lt;strong&gt;&lt;em&gt;Two-Phase Commit&lt;/em&gt;&lt;/strong&gt; (&lt;em&gt;or **2PC&lt;/em&gt;* for short*),&lt;br&gt;
but it is out of the scope of this blog post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Ok then, problem solved!!!"&lt;/em&gt; you might say... but there are a few things that prevent us from &lt;br&gt;
using this solution directly when it comes to microservice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Even though some databases already have support for distributed transactions, in a microservice 
architecture, different services might use different storage systems (for example, MongoDB and Postgres),
which prevents implementing them at the database layer. Using a single storage backend across all 
services is also not an option, since the whole point of the architecture is to allow agility and 
the use of the proper tool for the job.&lt;/li&gt;
&lt;li&gt;Storage systems sit behind the services themselves, which prevents direct access and requires 
that we handle such transactions at the level of the services rather than leave it out for storage systems.&lt;/li&gt;
&lt;li&gt;Implementing a fully blown &lt;em&gt;2PC&lt;/em&gt; distributed transaction is complex even for a single service,
let alone across dozens or hundreds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So to solve for this, we need a different method that allows for the &lt;em&gt;all-or-nothing&lt;/em&gt; capability of &lt;br&gt;
transactions, but that is simpler and implementable at the service level.&lt;/p&gt;

&lt;p&gt;This is where the &lt;strong&gt;Saga pattern&lt;/strong&gt; comes in...&lt;/p&gt;




&lt;h2&gt;
  
  
  The Saga Pattern
&lt;/h2&gt;

&lt;p&gt;Let us take a simple example of a banking application:&lt;br&gt;
We have two services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;transactions service&lt;/strong&gt; to handle all transactions done by customers.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;customers service&lt;/strong&gt; which stores information about customers and their banking information — 
most importantly, their account balance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now suppose in our example that the customer makes a purchase to buy an AI subscription &lt;br&gt;
(&lt;em&gt;because of all of what he has being hearing about its productivity benefits&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The bank's backend receives the request for the purchase. In a normal monolithic application, &lt;br&gt;
the process is simple: you would insert a new row in the transactions table referencing the customer,&lt;br&gt;
update the customer's balance, wrapping everything in a transaction to make sure this is applied atomically.&lt;/p&gt;

&lt;p&gt;But in a microservice architecture, this isn't so straightforward as discussed earlier. &lt;/p&gt;

&lt;p&gt;Let us consider the happy path. The transactions service inserts a new row with a &lt;code&gt;PENDING&lt;/code&gt; status. &lt;br&gt;
It then synchronizes with the customers service, which handles the update to the customer row. &lt;br&gt;
Upon confirmation of the update, the transactions row would update its own newly inserted row to &lt;code&gt;COMPLETE&lt;/code&gt;,&lt;br&gt;
for example.&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%2F6j51c1vhnyzvoelplt7n.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%2F6j51c1vhnyzvoelplt7n.png" alt="Saga Pattern: Happy Path Sequence" width="719" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let us suppose that the transactions service fails to synchronize the update with the customers service,&lt;br&gt;
or the customers service fails to inform the transactions service about its successful update.&lt;br&gt;
This is where the rollback part of the saga comes in, where each saga step has a&lt;br&gt;
(&lt;em&gt;compensating transaction&lt;/em&gt;) which works by rolling back all previous steps if a single one fails.&lt;br&gt;
This is in part similar to how a database does transactions, but only ensures &lt;strong&gt;eventual consistency&lt;/strong&gt;&lt;br&gt;
rather than the atomicity insured by the database's ACID transactions.&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%2Frj40bi6yvcvplzf85g49.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%2Frj40bi6yvcvplzf85g49.png" alt="Saga Pattern: Rollback &amp;amp; Compensation Sequence" width="719" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two ways of implementing Sagas and coordinating services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Choreography:&lt;/strong&gt; a service processes its part of the saga and publishes events that trigger updates to other services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration:&lt;/strong&gt; a central saga orchestrator manages the full saga, saving its state and rolling back appropriately on failure.&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%2Finrs4ze1mq7s0m89c9mb.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%2Finrs4ze1mq7s0m89c9mb.png" alt="Saga Choreography: Event-Driven Architecture" width="800" height="99"&gt;&lt;/a&gt;&lt;br&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%2Fsu0o5uufl9ikn9lygjm0.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%2Fsu0o5uufl9ikn9lygjm0.png" alt="Saga Orchestration: Central Coordinator Architecture" width="644" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One important caveat about Sagas is that the local write and the network call need to be atomic &lt;br&gt;
— &lt;em&gt;often resolved using the **Transactional Outbox Pattern&lt;/em&gt;** — or else the system &lt;br&gt;
will become stuck in an inconsistent state.&lt;/p&gt;

&lt;p&gt;We also ignored one part, which is the client waiting for his transaction to end (&lt;em&gt;to start his Agentic life&lt;/em&gt;).&lt;br&gt;
The most plausible way of solving this — &lt;em&gt;other than keeping him waiting&lt;/em&gt; — would be to provide a &lt;br&gt;
&lt;code&gt;transactionId&lt;/code&gt; to the customer and a special endpoint for him to poll to see if his purchase succeeded or not.&lt;/p&gt;




&lt;p&gt;Do you know of any other patterns?&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>software</category>
      <category>architecture</category>
      <category>data</category>
    </item>
    <item>
      <title>Picking a Package Manager for Your CI/CD Pipeline</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Sun, 15 Mar 2026 22:12:22 +0000</pubDate>
      <link>https://dev.to/ymerzouka/picking-a-package-manager-for-your-cicd-pipeline-59lc</link>
      <guid>https://dev.to/ymerzouka/picking-a-package-manager-for-your-cicd-pipeline-59lc</guid>
      <description>&lt;p&gt;If you want to ship fast then CI/CD is the most important thing for you to consider. But have you ever thought "Does the package manager I use really matter for CI/CD?". This might not be an issue for other languages like Python, but JavaScript does have a few options to choose from: some claiming to be the fastest, others prioritizing developer experience and disk usage, and then there's good old NPM.&lt;/p&gt;

&lt;p&gt;In this blog post we'll look into different package managers, examples of how to use each, and how well each performs when it comes to &lt;strong&gt;CI/CD&lt;/strong&gt; pipelines. Of course, with everybody's favorite: &lt;strong&gt;Benchmarks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The package managers we will discuss are &lt;strong&gt;PNPM&lt;/strong&gt;, &lt;strong&gt;NPM&lt;/strong&gt;, &lt;strong&gt;Yarn&lt;/strong&gt; and &lt;strong&gt;Bun&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;The application used is the standard &lt;strong&gt;NestJS&lt;/strong&gt; starter app, running on a GitLab CI pipeline. Below are the &lt;code&gt;Dockerfile&lt;/code&gt; for each of the package managers with a brief explanation of the flags used and some extra info.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We will not focus on securing the images for now, so the examples don't include any security measures. Distroless is used to minimize the size of the final container image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  NPM
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;ci&lt;/code&gt; option ensures that &lt;code&gt;package-lock.json&lt;/code&gt; is used, preventing any accidental changes to dependency versions. &lt;code&gt;--omit=dev&lt;/code&gt; ensures only production dependencies are installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.22.1-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/nodejs24-debian13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules/&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "/app/dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PNPM
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;--frozen-lockfile&lt;/code&gt; has the same effect as &lt;code&gt;npm ci&lt;/code&gt; — it locks the install to what's in the lockfile. The PNPM global store is placed at &lt;code&gt;/pnpm&lt;/code&gt; inside the Docker image, configured via &lt;code&gt;PNPM_HOME&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.22.1-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PNPM_HOME="/pnpm"&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="$PNPM_HOME:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./pnpm-lock.yaml ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./package.json ./pnpm-lock.yaml ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt; &lt;span class="nt"&gt;--prod&lt;/span&gt;


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/nodejs24-debian13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/node_modules ./node_modules/&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Yarn
&lt;/h3&gt;

&lt;p&gt;The default Yarn configuration that comes with NestJS uses the old classic version rather than the modern one with &lt;strong&gt;Plug'n'Play&lt;/strong&gt;. To upgrade, run &lt;code&gt;yarn set version stable&lt;/code&gt;, then enable PnP with &lt;code&gt;yarn config set nodeLinker pnp&lt;/code&gt; — this skips &lt;code&gt;node_modules&lt;/code&gt; entirely. Then install with &lt;code&gt;yarn install&lt;/code&gt;. For the rest of this post, "Yarn" refers to this latest Plug'n'Play version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22.22.1-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock .yarnrc.yml ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--immutable&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;deps&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json yarn.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;corepack &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; yarn workspaces focus &lt;span class="nt"&gt;--production&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;gcr.io/distroless/nodejs24-debian13:debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /root/.yarn/berry/cache /root/.yarn/berry/cache&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/.pnp.cjs /app/.pnp.loader.mjs ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=deps /app/.yarn ./.yarn&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "--require", "./.pnp.cjs", "dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bun
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker.io/oven/bun:1.3-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./bun.lock ./package.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bun &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--frozen-lockfile&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bun run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker.io/oven/bun:1.3-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;prod&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /usr/src/app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=install /usr/src/app/node_modules ./node_modules&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; [ "bun", "dist/main.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How Package Managers Work — and Why It Matters for CI
&lt;/h2&gt;

&lt;p&gt;Before jumping to the benchmarks, it's worth understanding what actually differentiates these package managers under the hood, since their design choices directly affect both install speed and image size.&lt;/p&gt;

&lt;h3&gt;
  
  
  PNPM
&lt;/h3&gt;

&lt;p&gt;PNPM's main goal is solving the disk space problem that &lt;code&gt;node_modules&lt;/code&gt; are notorious for. On a typical machine, every project gets its own full copy of every dependency — meaning if you have ten projects all using React, you have ten copies of React on disk.&lt;/p&gt;

&lt;p&gt;PNPM solves this by maintaining a single global content-addressable store (pointed to by &lt;code&gt;$PNPM_HOME&lt;/code&gt;), where every package file is stored exactly once. Each project's &lt;code&gt;node_modules&lt;/code&gt; then contains &lt;strong&gt;hard links&lt;/strong&gt; pointing back to that central store rather than actual copies of the files. To keep the store lean, PNPM also tracks file-level changes between package versions using hashes — only storing the differing files, not a full new copy of the package.&lt;/p&gt;

&lt;p&gt;Beyond disk savings, PNPM also addresses a subtle but important issue called &lt;strong&gt;phantom packages&lt;/strong&gt;. Here's the problem: NPM has historically &lt;strong&gt;hoisted&lt;/strong&gt; dependencies — meaning if your dependency &lt;code&gt;express&lt;/code&gt; depends on &lt;code&gt;lodash&lt;/code&gt;, NPM would move &lt;code&gt;lodash&lt;/code&gt; up into the root &lt;code&gt;node_modules&lt;/code&gt; folder, making it accidentally importable in your own code. Your project would work even though you never listed &lt;code&gt;lodash&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;. If &lt;code&gt;express&lt;/code&gt; later drops it or changes its version, your code silently breaks.&lt;/p&gt;

&lt;p&gt;PNPM avoids this by keeping each package's dependencies isolated under &lt;code&gt;node_modules/.pnpm/package@version/node_modules/&lt;/code&gt;, and exposing only your direct dependencies as &lt;strong&gt;symlinks&lt;/strong&gt; at the root of &lt;code&gt;node_modules&lt;/code&gt;. This makes the dependency tree accurate and predictable.&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.amazonaws.com%2Fuploads%2Farticles%2Fvqlzhlwlohw2uq0tqi68.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.amazonaws.com%2Fuploads%2Farticles%2Fvqlzhlwlohw2uq0tqi68.png" alt="PNPM Store and Links Illustration" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  What this means for Docker
&lt;/h4&gt;

&lt;p&gt;While PNPM's central store is extremely useful during development — especially in monorepos — it doesn't give you the same benefit inside a Docker container. Each container is its own isolated environment with its own dependencies, so the global store never gets reused across builds the way it would on a developer machine. In practice, PNPM ends up behaving similarly to NPM in a container context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bun
&lt;/h3&gt;

&lt;p&gt;Bun takes a fundamentally different approach to performance: it treats package installation as a &lt;strong&gt;systems problem&lt;/strong&gt; rather than a JavaScript problem.&lt;/p&gt;

&lt;p&gt;The core of this is Bun being written in &lt;strong&gt;Zig&lt;/strong&gt;, a compiled systems language, rather than JavaScript. This alone removes a significant layer of overhead. But Bun also targets three specific bottlenecks that slow down Node.js-based package managers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System calls:&lt;/strong&gt; Node.js uses &lt;code&gt;libuv&lt;/code&gt;, a C library, to make OS-level calls for things like reading files and managing threads. Each call involves some compatibility overhead, and Node.js adds further overhead managing its thread pools. Bun calls the OS more directly and with fewer round-trips. You can see this concretely in the &lt;code&gt;strace&lt;/code&gt; output below, from &lt;a href="https://bun.com/blog/behind-the-scenes-of-bun-install" rel="noopener noreferrer"&gt;Bun's blog&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Benchmark 1: strace -c -f npm install
    Time (mean ± σ):  37.245 s ±  2.134 s [User: 8.432 s, System: 4.821 s]
    Range (min … max):   34.891 s … 41.203 s    10 runs

    System calls: 996,978 total (108,775 errors)
    Top syscalls: futex (663,158),  write (109,412), epoll_pwait (54,496)

  Benchmark 2: strace -c -f bun install
    Time (mean ± σ):      5.612 s ±  0.287 s [User: 2.134 s, System: 1.892 s]
    Range (min … max):    5.238 s …  6.102 s    10 runs

    System calls: 165,743 total (3,131 errors)
    Top syscalls: openat(45,348), futex (762), epoll_pwait2 (298)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nearly 6× fewer system calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manifest parsing:&lt;/strong&gt; NPM reads package metadata from human-readable formats like JSON and YAML, which need to be parsed on every install. Bun stores this metadata in a binary format that can be loaded directly without parsing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decompression:&lt;/strong&gt; When downloading packages (which are compressed tarballs), most tools decompress on the fly, guessing at buffer sizes and reallocating memory as data streams in. Bun instead downloads the full compressed file first, determines the exact buffer size needed using the gzip format, allocates it once, and then decompresses — eliminating unnecessary memory copies.&lt;/p&gt;

&lt;p&gt;Bun also starts DNS lookups while reading &lt;code&gt;package.json&lt;/code&gt; rather than waiting until after, trimming latency at the very start of the install process. There are a few more optimizations covered in the references if you want to go deeper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Yarn
&lt;/h3&gt;

&lt;p&gt;Yarn's Plug'n'Play mode works differently from both NPM and PNPM. Rather than populating a &lt;code&gt;node_modules&lt;/code&gt; folder at all, Yarn downloads packages as &lt;code&gt;.zip&lt;/code&gt; archives and stores them in a central cache folder (viewable via &lt;code&gt;yarn config get cacheFolder&lt;/code&gt; — in our container, this is &lt;code&gt;/root/.yarn/berry/cache&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;At runtime, instead of Node.js resolving modules from &lt;code&gt;node_modules&lt;/code&gt;, Yarn injects a custom loader (&lt;code&gt;.pnp.cjs&lt;/code&gt;) that intercepts &lt;code&gt;require()&lt;/code&gt; calls and loads modules directly from the &lt;code&gt;.zip&lt;/code&gt; archives. The current install state is saved in &lt;code&gt;.yarn/install-state.gz&lt;/code&gt;, and the package cache lives in &lt;code&gt;.yarn/cache&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This design means no &lt;code&gt;node_modules&lt;/code&gt; directory is created when you run &lt;code&gt;yarn install&lt;/code&gt; — which also means no hoisting, no phantom packages, and significantly less I/O.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why Yarn performed so well
&lt;/h4&gt;

&lt;p&gt;The architecture above translates directly into install speed. Yarn only needs to download and archive packages, then generate the loader — no creating thousands of symlinks or hard links per file, simpler hoisting logic, no repeated I/O across a deep directory tree, and proper tracking of your project's dependencies. For a fresh Docker build where none of those steps can be cached, that's a meaningful advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package Manager&lt;/th&gt;
&lt;th&gt;Run 1 (f813c0c5)&lt;/th&gt;
&lt;th&gt;Run 2 (ecb90ce2)&lt;/th&gt;
&lt;th&gt;Run 3 (0938ee84)&lt;/th&gt;
&lt;th&gt;Average Time&lt;/th&gt;
&lt;th&gt;Average Size (MiB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;yarn&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:20&lt;/td&gt;
&lt;td&gt;1:23&lt;/td&gt;
&lt;td&gt;1:14&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1:19&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58.26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;npm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:49&lt;/td&gt;
&lt;td&gt;2:06&lt;/td&gt;
&lt;td&gt;1:51&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1:55&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;57.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;pnpm&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1:55&lt;/td&gt;
&lt;td&gt;1:54&lt;/td&gt;
&lt;td&gt;2:12&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2:00&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;57.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;bun&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2:06&lt;/td&gt;
&lt;td&gt;2:01&lt;/td&gt;
&lt;td&gt;2:14&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2:07&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;72.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even though I personally expected Bun to win this, it ended up performing on par with NPM — and the extra few MiB don't really justify the switch. So the winner here is Yarn, by a clear margin.&lt;/p&gt;

&lt;p&gt;I did some digging trying to explain why Bun underperformed, but couldn't find a satisfying answer. I'd be very interested in hearing your thoughts in the comments, and depending on the discussion, a deeper dive might be worth its own post.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://yarnpkg.com/features/pnp" rel="noopener noreferrer"&gt;Yarn Plug'n'Play&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bun.com/blog/behind-the-scenes-of-bun-install" rel="noopener noreferrer"&gt;Bun's performance improvements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pnpm.io/motivation" rel="noopener noreferrer"&gt;How pnpm works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pnpm.io/blog/2020/05/27/flat-node-modules-is-not-the-only-way" rel="noopener noreferrer"&gt;How pnpm does hoisting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/5inchiroca/nestjs-yarn-v4-installation-and-deployment-kjd"&gt;Setting up Yarn for NestJS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/younes-merzouka-blog/package-managers-and-ci.git" rel="noopener noreferrer"&gt;Source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>performance</category>
      <category>javascript</category>
    </item>
    <item>
      <title>What Rust Does Differently: A Beginner's Perspective</title>
      <dc:creator>Younes Merzouka</dc:creator>
      <pubDate>Sat, 07 Mar 2026 22:11:29 +0000</pubDate>
      <link>https://dev.to/ymerzouka/what-rust-does-differently-a-beginners-perspective-9h2</link>
      <guid>https://dev.to/ymerzouka/what-rust-does-differently-a-beginners-perspective-9h2</guid>
      <description>&lt;p&gt;Rust makes a lot of interesting decisions about how things work — what you can and cannot do, and how you do certain things. Coming from a mostly web development background, three concepts in particular caught my attention as a beginner: Mutability, Ownership, and Handling Null.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mutability
&lt;/h2&gt;

&lt;p&gt;Rust variables are immutable by default. So code like this in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...wouldn't work in Rust:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// compiler error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To mutate a variable, you need to add the &lt;code&gt;mut&lt;/code&gt; keyword:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason is safety. This mechanism forces you to explicitly opt into mutability, which makes your code more deterministic and avoids problems — especially with concurrency, where two threads might otherwise modify the same variable without you realising it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mutable References
&lt;/h3&gt;

&lt;p&gt;One thing that tripped me up early on was mutable references. Suppose we want to read from &lt;code&gt;stdin&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.read_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// passing a reference to s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks reasonable — similar to how you'd do it in C. But it actually results in a compiler error telling you that &lt;code&gt;read_line&lt;/code&gt; expects a &lt;em&gt;mutable&lt;/em&gt; reference. The correct version is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.read_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You have to explicitly tell the compiler that the reference itself is mutable. Why this matters will become clearer once we talk about ownership.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ownership
&lt;/h2&gt;

&lt;p&gt;Managing allocated memory is a well-known challenge in systems programming, and there are a few common approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Garbage collection&lt;/strong&gt; — many high-level languages manage memory for you through a GC. This simplifies things for developers but can impact performance through "GC pauses", where the entire application halts while unused memory is freed. Not ideal for systems software.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manual memory management&lt;/strong&gt; — the approach C takes. You allocate memory and you're responsible for freeing it. This has historically led to many bugs and security vulnerabilities: null pointer dereferences, buffer overflows, use-after-free, and so on.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rust takes a different approach through &lt;strong&gt;ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// frees the heap memory&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// compiler error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function &lt;code&gt;add_suffix&lt;/code&gt; receives a &lt;code&gt;String&lt;/code&gt; allocated on the heap, drops it (equivalent to &lt;code&gt;free&lt;/code&gt; in C), and then &lt;code&gt;main&lt;/code&gt; tries to access that freed memory — a classic use-after-free bug. Rust won't let this compile.&lt;/p&gt;

&lt;p&gt;You might think the fix is simply to remove the &lt;code&gt;drop&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// still a compiler error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this still doesn't work. Passing &lt;code&gt;s&lt;/code&gt; to &lt;code&gt;add_suffix&lt;/code&gt; as an argument &lt;strong&gt;transfers ownership&lt;/strong&gt; of &lt;code&gt;s&lt;/code&gt; to that function.&lt;/p&gt;

&lt;p&gt;So what does &lt;em&gt;transferring ownership&lt;/em&gt; actually mean? In Rust, each heap-allocated value is owned by the scope in which it was created. When that scope ends, the memory is automatically freed. So when &lt;code&gt;add_suffix&lt;/code&gt; returns, &lt;code&gt;s&lt;/code&gt; is freed — and trying to use it in &lt;code&gt;println!&lt;/code&gt; afterwards is accessing a dead memory region.&lt;/p&gt;

&lt;p&gt;Here's another example of the same idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// s is dropped here, memory is freed&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// compiler error: p points to freed memory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct way to achieve what we originally wanted is through &lt;strong&gt;borrowing&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// takes a mutable reference&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.push_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;add_suffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// lends s to the function&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// s is still valid here&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By passing a reference (&lt;code&gt;&amp;amp;mut s&lt;/code&gt;), &lt;code&gt;main&lt;/code&gt; remains the owner of the memory. The function simply &lt;em&gt;borrows&lt;/em&gt; it temporarily. When the function returns, the borrow ends — but the memory isn't freed, because &lt;code&gt;main&lt;/code&gt; still owns it.&lt;/p&gt;

&lt;p&gt;This same principle applies even without mutation. If you don't need to mutate the value, you use &lt;code&gt;&amp;amp;&lt;/code&gt; instead of &lt;code&gt;&amp;amp;mut&lt;/code&gt;. Now the reason you need &lt;code&gt;&amp;amp;mut&lt;/code&gt; for mutable references (as we saw with &lt;code&gt;read_line&lt;/code&gt;) should make a lot more sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Option
&lt;/h2&gt;

&lt;p&gt;Rust doesn't have &lt;code&gt;null&lt;/code&gt;. But there are situations where you genuinely want to express: &lt;em&gt;"this function either returns a result, or nothing — not because of an error, but because there's simply nothing to return."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Consider this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.char_indices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// what do we return if the character isn't found?&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;null&lt;/code&gt; might seem useful here. For exactly these cases, Rust has &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;needle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.char_indices&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;needle&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;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; special is that it addresses the classic problem with &lt;code&gt;null&lt;/code&gt;: forgetting to handle it. &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; forces you to explicitly deal with both cases. For example, this won't compile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello, there"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// compiler error: expected &amp;amp;str, found Option&amp;lt;&amp;amp;str&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct approach is to use &lt;code&gt;match&lt;/code&gt; (similar to a switch statement) to handle both possibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nf"&gt;get_slice_by_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello, there"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;None&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;panic!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Character not found"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler won't let you use the value inside an &lt;code&gt;Option&lt;/code&gt; without first unpacking it — which means you can never accidentally forget to handle the "nothing" case.&lt;/p&gt;




&lt;p&gt;These three concepts — mutability, ownership, and &lt;code&gt;Option&lt;/code&gt; — felt strange to me at first coming from other languages. But once they click, you start to see how they all work together to make Rust code safe without needing a garbage collector. Pretty elegant, honestly.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>beginners</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
