<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ software development - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ software development - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 26 May 2026 22:47:00 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/software-development/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Learn Command Line Interface (CLI) Development with Dart: From Zero to a Fully Published Developer Tool ]]>
                </title>
                <description>
                    <![CDATA[ Most developers spend a significant portion of their day in the terminal. They run flutter build, push with git, manage packages with dart pub, and orchestrate pipelines from the command line. Every o ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-command-line-interface-cli-development-with-dart-from-zero-to-a-fully-published-developer-tool/</link>
                <guid isPermaLink="false">69fe3149f239332df4fdfd46</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mobile Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Fri, 08 May 2026 18:54:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/a4c564c2-f5f3-4824-b4e7-d103b5fc488e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most developers spend a significant portion of their day in the terminal. They run <code>flutter build</code>, push with <code>git</code>, manage packages with <code>dart pub</code>, and orchestrate pipelines from the command line. Every one of those tools is a CLI, or command line interface: a program that lives in the terminal and responds to text commands.</p>
<p>Yet most developers have never built one.</p>
<p>That's a missed opportunity. CLI tools are one of the most practical things a developer can ship. They automate repetitive workflows, standardise processes across teams, and, when published, become tangible artifacts that the developer community can discover, install, and use.</p>
<p>In this handbook, you'll go from zero to building a fully distributed Dart CLI tool. We'll start with the fundamentals – how CLIs work, how Dart receives and processes terminal input, and the core syntax you need to know. Then we'll build three progressively complex CLIs, starting with the basics and finishing with a real-world API request runner. Finally, we will cover every distribution path available, from <code>pub.dev</code> to compiled binaries, Homebrew taps, Docker, and local team activation.</p>
<p>By the end of the guide, you'll understand both how to build a CLI tool in Dart as well as how to ship it so other developers can actually use it.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-is-a-cli-and-why-should-you-build-one">What is a CLI and Why Should You Build One?</a></p>
</li>
<li><p><a href="#heading-cli-syntax-anatomy">CLI Syntax Anatomy</a></p>
</li>
<li><p><a href="#heading-how-dart-receives-terminal-input">How Dart Receives Terminal Input</a></p>
</li>
<li><p><a href="#heading-core-cli-concepts-in-dart">Core CLI Concepts in Dart</a></p>
<ul>
<li><p><a href="#heading-stdout-stderr-and-stdin">stdout, stderr, and stdin</a></p>
</li>
<li><p><a href="#heading-exit-codes">Exit Codes</a></p>
</li>
<li><p><a href="#heading-environment-variables">Environment Variables</a></p>
</li>
<li><p><a href="#heading-file-and-directory-operations">File and Directory Operations</a></p>
</li>
<li><p><a href="#heading-running-external-processes">Running External Processes</a></p>
</li>
<li><p><a href="#heading-platform-detection">Platform Detection</a></p>
</li>
<li><p><a href="#heading-async-in-cli">Async in CLI</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-setting-up-your-dart-cli-project">Setting Up Your Dart CLI Project</a></p>
</li>
<li><p><a href="#heading-cli-1-hello-cli-the-fundamentals">CLI 1 — Hello CLI: The Fundamentals</a></p>
</li>
<li><p><a href="#heading-cli-2-darttodo-a-terminal-task-manager">CLI 2 — dart_todo: A Terminal Task Manager</a></p>
<ul>
<li><p><a href="#heading-introducing-the-args-package">Introducing the args Package</a></p>
</li>
<li><p><a href="#heading-building-darttodo">Building dart_todo</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-cli-3-darthttp-a-lightweight-api-request-runner">CLI 3 — dart_http: A Lightweight API Request Runner</a></p>
<ul>
<li><a href="#heading-building-darthttp">Building dart_http</a></li>
</ul>
</li>
<li><p><a href="#heading-adding-color-and-polish-to-your-cli">Adding Color and Polish to Your CLI</a></p>
</li>
<li><p><a href="#heading-testing-your-cli-tool">Testing Your CLI Tool</a></p>
</li>
<li><p><a href="#heading-deploying-and-distributing-your-cli">Deploying and Distributing Your CLI</a></p>
<ul>
<li><p><a href="#heading-mode-1-pubdev-public-package-distribution">Mode 1: pub.dev — Public Package Distribution</a></p>
</li>
<li><p><a href="#heading-mode-2-local-path-activation">Mode 2: Local Path Activation</a></p>
</li>
<li><p><a href="#heading-mode-3-compiled-binary-via-github-releases">Mode 3: Compiled Binary via GitHub Releases</a></p>
</li>
<li><p><a href="#heading-mode-4-homebrew-tap">Mode 4: Homebrew Tap</a></p>
</li>
<li><p><a href="#heading-mode-5-docker">Mode 5: Docker</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-choosing-the-right-distribution-mode">Choosing the Right Distribution Mode</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, you should have:</p>
<ul>
<li><p>Dart SDK installed (<code>dart --version</code> should work in your terminal)</p>
</li>
<li><p>Basic familiarity with Dart syntax</p>
</li>
<li><p>Comfort with the terminal and running commands</p>
</li>
<li><p>A pub.dev account (for the publishing section)</p>
</li>
<li><p>A GitHub account (for the binary distribution section)</p>
</li>
</ul>
<h2 id="heading-what-is-a-cli-and-why-should-you-build-one">What is a CLI and Why Should You Build One?</h2>
<p>A CLI (or <strong>Command Line Interface</strong>) is a program you interact with entirely through text commands in a terminal, rather than through buttons and screens in a graphical interface.</p>
<p>Many of the tools you likely already rely on as a developer are CLI tools:</p>
<pre><code class="language-yaml">flutter build apk
git commit -m "fix: auth flow"
dart pub get
npm install
</code></pre>
<p>Flutter, Git, Dart, npm – all CLIs. You are already a CLI user every single day. This article is about becoming a CLI builder.</p>
<p>There are three strong reasons to build CLI tools as a developer:</p>
<ol>
<li><p><strong>Automating repetitive work:</strong> Anything you type more than twice a week is a candidate for automation. Generating boilerplate folder structures, running sequences of commands, scaffolding files, checking environments before a build a CLI turns a seven-step manual process into a single command.</p>
</li>
<li><p><strong>Standardising team workflows:</strong> Instead of a README that says "run these commands in this order," you ship one command that does all of it – consistently, every time, with no room for human error or a missed step.</p>
</li>
<li><p><strong>Building and publishing tooling.</strong> A published Dart CLI package is a tangible artifact. It shows up on pub.dev, gets installed and used by other developers, and communicates real engineering depth in a way that a portfolio or resume cannot.</p>
</li>
</ol>
<h2 id="heading-cli-syntax-anatomy">CLI Syntax Anatomy</h2>
<p>Before writing a single line of code, it helps to understand the structure of a CLI command. Every command follows a consistent pattern:</p>
<pre><code class="language-bash">tool [subcommand] [arguments] [options/flags]
</code></pre>
<p>Breaking down a real example:</p>
<pre><code class="language-bash">flutter build apk --release --obfuscate
│       │     │   │
tool    sub   arg  flags
</code></pre>
<ul>
<li><p><strong>Tool</strong> — the program itself (<code>flutter</code>, <code>dart</code>, <code>git</code>)</p>
</li>
<li><p><strong>Subcommand</strong> — the action being performed (<code>build</code>, <code>run</code>, <code>pub</code>)</p>
</li>
<li><p><strong>Arguments</strong> — what the action operates on (<code>apk</code>, <code>main.dart</code>, a filename)</p>
</li>
<li><p><strong>Flags and Options</strong> — modifiers that change behaviour</p>
</li>
</ul>
<p>There are two types of options:</p>
<pre><code class="language-plaintext">--release              # Boolean flag — either present or absent

--output=build/app     # Key-value option — name and a value
-v                     # Short flag — single hyphen, single character
</code></pre>
<p>This is the anatomy your CLIs will follow. Understanding it before writing any code means you will design your commands intentionally rather than stumbling into structure by accident.</p>
<h2 id="heading-how-dart-receives-terminal-input">How Dart Receives Terminal Input</h2>
<p>In Dart, everything the user types after your tool name is passed into your program through the <code>main</code> function:</p>
<pre><code class="language-dart">void main(List&lt;String&gt; args) {
  print(args);
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/mytool.dart hello world --name=Seyi
# [hello, world, --name=Seyi]
</code></pre>
<p>That <code>List&lt;String&gt; args</code> is just a list of strings. Each word or flag the user typed becomes an element in that list. Everything else you build on top of a CLI subcommands, flags, validation — is ultimately just processing this list.</p>
<h2 id="heading-core-cli-concepts-in-dart">Core CLI Concepts in Dart</h2>
<p>Before building anything, there's a set of foundational concepts that every CLI developer needs to understand. These are the building blocks that everything else sits on top of.</p>
<h3 id="heading-stdout-stderr-and-stdin">stdout, stderr, and stdin</h3>
<p>Most developers use <code>print()</code> for all output when they start building CLIs. That works for learning but it's incorrect in production.</p>
<p>There are two separate output streams in a terminal program:</p>
<ul>
<li><p><code>stdout</code> — regular output, meant for the user</p>
</li>
<li><p><code>stderr</code> — error output, meant for diagnostic messages and failures</p>
</li>
</ul>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Error: no arguments provided');
    exit(1);
  }

  stdout.writeln('Processing: ${args[0]}');
}
</code></pre>
<p>Keeping these separate matters because users can redirect stdout to a file without errors polluting it:</p>
<pre><code class="language-bash">dart run bin/tool.dart &gt; output.txt
# Errors still appear in the terminal
# Normal output goes cleanly to the file
</code></pre>
<p>Tools like <code>git</code>, <code>flutter</code>, and <code>curl</code> all do this correctly. Your CLI should too.</p>
<p><code>stdin</code> is the third stream — reading input from the user interactively at runtime:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  stdout.write('Enter your name: ');
  final name = stdin.readLineSync();

  if (name == null || name.trim().isEmpty) {
    stderr.writeln('Error: no name provided');
    exit(1);
  }

  stdout.writeln('Hello, $name!');
}
</code></pre>
<p><code>stdout.write</code> (without <code>ln</code>) keeps the cursor on the same line so the user types right after the prompt. <code>stdin.readLineSync()</code> blocks until the user presses Enter and returns the typed string, or <code>null</code> if the stream closes unexpectedly. Always handle the null case.</p>
<h3 id="heading-exit-codes">Exit Codes</h3>
<p>Every program returns an exit code when it finishes. This is how the shell – and any script or CI system calling your tool – knows whether it succeeded or failed.</p>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Error: please provide an argument');
    exit(1); // failure
  }

  stdout.writeln('Done');
  exit(0); // success — also the default if you don't call exit()
}
</code></pre>
<p>The conventions are:</p>
<ul>
<li><p><code>0</code> — success</p>
</li>
<li><p><code>1</code> — general failure</p>
</li>
<li><p><code>2</code> — incorrect usage (wrong arguments, missing flags)</p>
</li>
</ul>
<p>Exit codes are critical when your CLI is called inside shell scripts or GitHub Actions workflows. A non-zero exit code stops a pipeline immediately. That's exactly the behaviour you want from a quality gate or a validation step.</p>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>Your CLI can read environment variables set in the user's shell:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  final token = Platform.environment['API_TOKEN'];

  if (token == null) {
    stderr.writeln('Error: API_TOKEN environment variable is not set');
    exit(1);
  }

  stdout.writeln('Token found — proceeding...');
}
</code></pre>
<p>Set it in the terminal and run:</p>
<pre><code class="language-bash">export API_TOKEN=mytoken123
dart run bin/tool.dart
# Token found — proceeding...
</code></pre>
<p>This pattern is essential for CLI tools that interact with APIs, cloud services, or CI environments where credentials should never be hardcoded.</p>
<h3 id="heading-file-and-directory-operations">File and Directory Operations</h3>
<p>Many CLI tools read from or write to the file system. Dart's <code>dart:io</code> library covers everything you need:</p>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: tool &lt;filename&gt;');
    exit(2);
  }

  final file = File(args[0]);

  if (!file.existsSync()) {
    stderr.writeln('Error: "${args[0]}" not found');
    exit(1);
  }

  final contents = file.readAsStringSync();
  stdout.writeln(contents);

  final output = File('output.txt');
  output.writeAsStringSync('Processed:\n$contents');
  stdout.writeln('Written to output.txt');
}
</code></pre>
<p>Working with directories:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  // Where the command was run from
  final cwd = Directory.current.path;
  stdout.writeln('Working directory: $cwd');

  // Create a directory relative to current location
  final dir = Directory('$cwd/generated');

  if (!dir.existsSync()) {
    dir.createSync(recursive: true);
    stdout.writeln('Created: ${dir.path}');
  } else {
    stdout.writeln('Already exists: ${dir.path}');
  }
}
</code></pre>
<p>The <code>recursive: true</code> flag on <code>createSync</code> means it creates all intermediate directories — equivalent to <code>mkdir -p</code> in bash.</p>
<h3 id="heading-running-external-processes">Running External Processes</h3>
<p>One of the most powerful things a CLI can do is call other programs. Your Dart CLI can run <code>git</code>, <code>flutter</code>, <code>dart</code>, or any shell command programmatically:</p>
<pre><code class="language-dart">import 'dart:io';

void main() async {
  // Run a command and wait for it to finish
  final result = await Process.run('dart', ['pub', 'get']);

  stdout.write(result.stdout);

  if (result.exitCode != 0) {
    stderr.write(result.stderr);
    exit(result.exitCode);
  }

  stdout.writeln('Dependencies installed successfully');
}
</code></pre>
<p>For long-running commands where you want output to stream live as it happens:</p>
<pre><code class="language-dart">import 'dart:io';

void main() async {
  final process = await Process.start('flutter', ['build', 'apk']);

  // Pipe output directly to the terminal in real time
  process.stdout.pipe(stdout);
  process.stderr.pipe(stderr);

  final exitCode = await process.exitCode;
  exit(exitCode);
}
</code></pre>
<p><code>Process.run</code> — waits for completion, returns all output at once. Use for short commands.</p>
<p><code>Process.start</code> — streams output live as it arrives. Use for long-running commands where the user needs to see progress.</p>
<h3 id="heading-platform-detection">Platform Detection</h3>
<p>Sometimes your CLI needs to behave differently depending on the operating system it is running on:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  if (Platform.isWindows) {
    stdout.writeln('Running on Windows');
  } else if (Platform.isMacOS) {
    stdout.writeln('Running on macOS');
  } else if (Platform.isLinux) {
    stdout.writeln('Running on Linux');
  }

  // Useful for path handling across operating systems
  stdout.writeln(Platform.pathSeparator); // \ on Windows, / elsewhere
  stdout.writeln(Platform.operatingSystem); // 'macos', 'linux', 'windows'
}
</code></pre>
<p>This matters when your CLI creates files, resolves paths, or calls shell commands that differ between operating systems.</p>
<h3 id="heading-async-in-cli">Async in CLI</h3>
<p>Dart CLIs support <code>async/await</code> natively. Any <code>main</code> function can be made async:</p>
<pre><code class="language-dart">import 'dart:io';

void main() async {
  stdout.writeln('Starting...');

  await Future.delayed(const Duration(seconds: 1)); // simulating async work

  stdout.writeln('Done');
}
</code></pre>
<p>Any operation involving file I/O, HTTP requests, or spawning processes will be asynchronous. Get comfortable with async <code>main</code> functions early — you'll use them constantly.</p>
<h2 id="heading-setting-up-your-dart-cli-project">Setting Up Your Dart CLI Project</h2>
<p>Create a new Dart console project:</p>
<pre><code class="language-bash">dart create -t console my_cli_tool
cd my_cli_tool
</code></pre>
<p>This generates a clean structure:</p>
<pre><code class="language-plaintext">my_cli_tool/
  bin/
    my_cli_tool.dart    ← entry point
  lib/                  ← shared library code
  test/                 ← tests
  pubspec.yaml
  README.md
</code></pre>
<p>The <code>bin/</code> directory is where your executable entry point lives. The <code>lib/</code> directory is where you put everything else — commands, utilities, models — that <code>bin/</code> imports and uses.</p>
<p>Open <code>pubspec.yaml</code>. You'll need to add an <code>executables</code> block before publishing:</p>
<pre><code class="language-yaml">name: my_cli_tool
description: A sample CLI tool built with Dart
version: 1.0.0

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  my_cli_tool: my_cli_tool  # executable name: bin file name

dependencies:
  args: ^2.4.2

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0
</code></pre>
<p>The <code>executables</code> block is what makes <code>dart pub global activate my_cli_tool</code> work. It tells Dart which script in <code>bin/</code> to expose as a runnable command after installation.</p>
<h2 id="heading-cli-1-hello-cli-the-fundamentals">CLI 1 — Hello CLI: The Fundamentals</h2>
<p>This first CLI uses pure Dart — no packages. The goal is to get comfortable with args, subcommands, input validation, and exit codes before introducing any external dependencies.</p>
<p>Replace the contents of <code>bin/my_cli_tool.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    printHelp();
    exit(0);
  }

  final command = args[0];

  switch (command) {
    case 'greet':
      handleGreet(args.sublist(1));
    case 'time':
      handleTime();
    case 'echo':
      handleEcho(args.sublist(1));
    case 'help':
      printHelp();
    default:
      stderr.writeln('Unknown command: "$command"');
      stderr.writeln('Run "mytool help" to see available commands.');
      exit(1);
  }
}

void handleGreet(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: mytool greet &lt;name&gt;');
    exit(2);
  }

  final name = args[0];
  stdout.writeln('Hello, $name! Welcome to your first Dart CLI.');
}

void handleTime() {
  final now = DateTime.now();
  stdout.writeln(
    'Current time: ${now.hour.toString().padLeft(2, '0')}:'
    '${now.minute.toString().padLeft(2, '0')}:'
    '${now.second.toString().padLeft(2, '0')}',
  );
}

void handleEcho(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: mytool echo &lt;message&gt;');
    exit(2);
  }

  stdout.writeln(args.join(' '));
}

void printHelp() {
  stdout.writeln('''
mytool — a simple Dart CLI

Usage:
  mytool &lt;command&gt; [arguments]

Commands:
  greet &lt;name&gt;      Greet someone by name
  time              Show the current time
  echo &lt;message&gt;    Echo a message back to the terminal
  help              Show this help message

Examples:
  mytool greet Seyi
  mytool echo "Hello from the terminal"
  mytool time
  ''');
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/my_cli_tool.dart help

dart run bin/my_cli_tool.dart greet Seyi
# Hello, Seyi! Welcome to your first Dart CLI.

dart run bin/my_cli_tool.dart time
# Current time: 14:32:10

dart run bin/my_cli_tool.dart echo "Dart CLIs are powerful"
# Dart CLIs are powerful

dart run bin/my_cli_tool.dart unknown
# Unknown command: "unknown"
# Run "mytool help" to see available commands.
</code></pre>
<p>Three things this CLI demonstrates that are worth internalising:</p>
<ol>
<li><p><strong>Subcommands are just a switch on</strong> <code>args[0]</code><strong>.</strong> The pattern is simple and scalable — add a new <code>case</code> to add a new command.</p>
</li>
<li><p><code>args.sublist(1)</code> <strong>passes remaining args to the handler.</strong> When <code>greet</code> receives <code>['greet', 'Seyi']</code>, it calls <code>handleGreet(['Seyi'])</code> — clean and isolated.</p>
</li>
<li><p><strong>Every error path has a message and a non-zero exit code.</strong> The user always knows what went wrong and what to do next.</p>
</li>
</ol>
<h2 id="heading-cli-2-darttodo-a-terminal-task-manager">CLI 2 — dart_todo: A Terminal Task Manager</h2>
<p>This CLI introduces the <code>args</code> package, JSON file persistence, and structured terminal output. It's meaningfully more complex than CLI 1 and reflects real patterns you will use in production tools.</p>
<h3 id="heading-introducing-the-args-package">Introducing the args Package</h3>
<p>Manually parsing <code>List&lt;String&gt; args</code> works for simple cases, but breaks down quickly when you add flags like <code>--priority=high</code>, boolean options like <code>--done</code>, or commands with multiple optional arguments.</p>
<p>The <code>args</code> package handles all of that cleanly.</p>
<p>Add it to your <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">dependencies:
  args: ^2.4.2
</code></pre>
<p>Run:</p>
<pre><code class="language-bash">dart pub get
</code></pre>
<p>The core concept in <code>args</code> is the <code>ArgParser</code>. You define what your CLI accepts, and <code>args</code> handles parsing, validation, and generating help text automatically:</p>
<pre><code class="language-dart">import 'package:args/args.dart';

void main(List&lt;String&gt; arguments) {
  final parser = ArgParser()
    ..addCommand('add')
    ..addCommand('list')
    ..addFlag('help', abbr: 'h', negatable: false);

  final results = parser.parse(arguments);

  if (results['help'] as bool) {
    print(parser.usage);
    return;
  }
}
</code></pre>
<p>For more complex CLIs with subcommands that each have their own flags, use <code>ArgParser</code> per command:</p>
<pre><code class="language-dart">final parser = ArgParser();

final addCommand = ArgParser()
  ..addOption('priority', abbr: 'p', defaultsTo: 'normal');

parser.addCommand('add', addCommand);
</code></pre>
<h3 id="heading-building-darttodo">Building dart_todo</h3>
<p>Create a fresh project:</p>
<pre><code class="language-bash">dart create -t console dart_todo
cd dart_todo
</code></pre>
<p>Update <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">name: dart_todo
description: A terminal task manager built with Dart
version: 1.0.0

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  dart_todo: dart_todo

dependencies:
  args: ^2.4.2

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0
</code></pre>
<p>Run <code>dart pub get</code>.</p>
<p>Create the folder structure:</p>
<pre><code class="language-plaintext">dart_todo/
  bin/
    dart_todo.dart
  lib/
    models/
      task.dart
    storage/
      task_storage.dart
    commands/
      add_command.dart
      list_command.dart
      complete_command.dart
      delete_command.dart
      clear_command.dart
  pubspec.yaml
</code></pre>
<h4 id="heading-step-1-the-task-model-libmodelstaskdart">Step 1 — The Task Model (<code>lib/models/task.dart</code>)</h4>
<pre><code class="language-dart">class Task {
  final int id;
  final String title;
  final String priority;
  final bool isComplete;
  final DateTime createdAt;

  Task({
    required this.id,
    required this.title,
    required this.priority,
    this.isComplete = false,
    required this.createdAt,
  });

  Task copyWith({bool? isComplete}) {
    return Task(
      id: id,
      title: title,
      priority: priority,
      isComplete: isComplete ?? this.isComplete,
      createdAt: createdAt,
    );
  }

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        'id': id,
        'title': title,
        'priority': priority,
        'isComplete': isComplete,
        'createdAt': createdAt.toIso8601String(),
      };

  factory Task.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Task(
        id: json['id'] as int,
        title: json['title'] as String,
        priority: json['priority'] as String,
        isComplete: json['isComplete'] as bool,
        createdAt: DateTime.parse(json['createdAt'] as String),
      );
}
</code></pre>
<h4 id="heading-step-2-storage-libstoragetaskstoragedart">Step 2 — Storage (<code>lib/storage/task_storage.dart</code>)</h4>
<p>This class handles reading and writing tasks to a local JSON file so they persist between CLI runs:</p>
<pre><code class="language-dart">import 'dart:convert';
import 'dart:io';

import '../models/task.dart';

class TaskStorage {
  static final _file = File(
    '${Platform.environment['HOME'] ?? Directory.current.path}/.dart_todo.json',
  );

  static List&lt;Task&gt; loadAll() {
    if (!_file.existsSync()) return [];

    try {
      final content = _file.readAsStringSync();
      final List&lt;dynamic&gt; json = jsonDecode(content) as List&lt;dynamic&gt;;
      return json
          .map((e) =&gt; Task.fromJson(e as Map&lt;String, dynamic&gt;))
          .toList();
    } catch (_) {
      return [];
    }
  }

  static void saveAll(List&lt;Task&gt; tasks) {
    final json = jsonEncode(tasks.map((t) =&gt; t.toJson()).toList());
    _file.writeAsStringSync(json);
  }
}
</code></pre>
<p>Tasks are stored in a hidden JSON file in the user's home directory — a common pattern for CLI tools that need lightweight local persistence.</p>
<h4 id="heading-step-3-commands">Step 3 — Commands</h4>
<p><code>lib/commands/add_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../models/task.dart';
import '../storage/task_storage.dart';

void runAdd(List&lt;String&gt; args, String priority) {
  if (args.isEmpty) {
    stderr.writeln('Usage: dart_todo add &lt;title&gt; [--priority=high|normal|low]');
    exit(2);
  }

  final title = args.join(' ');
  final tasks = TaskStorage.loadAll();

  final newTask = Task(
    id: tasks.isEmpty ? 1 : tasks.last.id + 1,
    title: title,
    priority: priority,
    createdAt: DateTime.now(),
  );

  tasks.add(newTask);
  TaskStorage.saveAll(tasks);

  stdout.writeln('Added task #\({newTask.id}: "\)title" [$priority]');
}
</code></pre>
<p><code>lib/commands/list_command.dart</code>:</p>
<pre><code class="language-cpp">import 'dart:io';

import '../storage/task_storage.dart';

void runList() {
  final tasks = TaskStorage.loadAll();

  if (tasks.isEmpty) {
    stdout.writeln('No tasks yet. Add one with: dart_todo add &lt;title&gt;');
    return;
  }

  stdout.writeln('');
  stdout.writeln('  ID   Status      Priority   Title');
  stdout.writeln('  ───  ──────────  ─────────  ────────────────────────');

  for (final task in tasks) {
    final status = task.isComplete ? 'done  ' : 'pending';
    final id = task.id.toString().padRight(4);
    final priority = task.priority.padRight(9);
    stdout.writeln('  \(id \)status  \(priority  \){task.title}');
  }

  stdout.writeln('');
}
</code></pre>
<p><code>lib/commands/complete_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../storage/task_storage.dart';

void runComplete(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: dart_todo complete &lt;id&gt;');
    exit(2);
  }

  final id = int.tryParse(args[0]);
  if (id == null) {
    stderr.writeln('Error: "${args[0]}" is not a valid task ID');
    exit(1);
  }

  final tasks = TaskStorage.loadAll();
  final index = tasks.indexWhere((t) =&gt; t.id == id);

  if (index == -1) {
    stderr.writeln('Error: No task found with ID $id');
    exit(1);
  }

  if (tasks[index].isComplete) {
    stdout.writeln('Task #$id is already complete.');
    return;
  }

  tasks[index] = tasks[index].copyWith(isComplete: true);
  TaskStorage.saveAll(tasks);

  stdout.writeln('Task #\(id marked as complete: "\){tasks[index].title}"');
}
</code></pre>
<p><code>lib/commands/delete_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../storage/task_storage.dart';

void runDelete(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: dart_todo delete &lt;id&gt;');
    exit(2);
  }

  final id = int.tryParse(args[0]);
  if (id == null) {
    stderr.writeln('Error: "${args[0]}" is not a valid task ID');
    exit(1);
  }

  final tasks = TaskStorage.loadAll();
  final index = tasks.indexWhere((t) =&gt; t.id == id);

  if (index == -1) {
    stderr.writeln('Error: No task found with ID $id');
    exit(1);
  }

  final title = tasks[index].title;
  tasks.removeAt(index);
  TaskStorage.saveAll(tasks);

  stdout.writeln('Deleted task #\(id: "\)title"');
}
</code></pre>
<p><code>lib/commands/clear_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../storage/task_storage.dart';

void runClear() {
  stdout.write('Are you sure you want to delete all tasks? (y/N): ');
  final input = stdin.readLineSync()?.trim().toLowerCase();

  if (input != 'y') {
    stdout.writeln('Cancelled.');
    return;
  }

  TaskStorage.saveAll([]);
  stdout.writeln('All tasks cleared.');
}
</code></pre>
<h4 id="heading-step-4-entry-point-bindarttododart">Step 4 — Entry Point (<code>bin/dart_todo.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:io';

import 'package:args/args.dart';

import '../lib/commands/add_command.dart';
import '../lib/commands/clear_command.dart';
import '../lib/commands/complete_command.dart';
import '../lib/commands/delete_command.dart';
import '../lib/commands/list_command.dart';

void main(List&lt;String&gt; arguments) {
  final parser = ArgParser();

  // Add subcommand parsers
  final addParser = ArgParser()
    ..addOption(
      'priority',
      abbr: 'p',
      defaultsTo: 'normal',
      allowed: ['high', 'normal', 'low'],
      help: 'Task priority level',
    );

  parser
    ..addCommand('add', addParser)
    ..addCommand('list')
    ..addCommand('complete')
    ..addCommand('delete')
    ..addCommand('clear')
    ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');

  ArgResults results;

  try {
    results = parser.parse(arguments);
  } catch (e) {
    stderr.writeln('Error: $e');
    stderr.writeln(parser.usage);
    exit(2);
  }

  if (results['help'] as bool || results.command == null) {
    printHelp(parser);
    exit(0);
  }

  final command = results.command!;

  switch (command.name) {
    case 'add':
      runAdd(command.rest, command['priority'] as String);
    case 'list':
      runList();
    case 'complete':
      runComplete(command.rest);
    case 'delete':
      runDelete(command.rest);
    case 'clear':
      runClear();
    default:
      stderr.writeln('Unknown command: "${command.name}"');
      exit(1);
  }
}

void printHelp(ArgParser parser) {
  stdout.writeln('''
dart_todo — a terminal task manager

Usage:
  dart_todo &lt;command&gt; [arguments]

Commands:
  add &lt;title&gt;        Add a new task
    -p, --priority   Priority: high, normal, low (default: normal)
  list               List all tasks
  complete &lt;id&gt;      Mark a task as complete
  delete &lt;id&gt;        Delete a task
  clear              Delete all tasks

Examples:
  dart_todo add "Write the CLI article" --priority=high
  dart_todo list
  dart_todo complete 1
  dart_todo delete 2
  dart_todo clear
  ''');
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/dart_todo.dart add "Write the CLI article" --priority=high
# Added task #1: "Write the CLI article" [high]

dart run bin/dart_todo.dart add "Review PR comments"
# Added task #2: "Review PR comments" [normal]

dart run bin/dart_todo.dart list
#   ID   Status      Priority   Title
#   ───  ──────────  ─────────  ────────────────────────
#   1    ⬜ pending  high       Write the CLI article
#   2    ⬜ pending  normal     Review PR comments

dart run bin/dart_todo.dart complete 1
# Task #1 marked as complete: "Write the CLI article"

dart run bin/dart_todo.dart delete 2
# Deleted task #2: "Review PR comments"
</code></pre>
<p><code>dart_todo</code> demonstrates the patterns that form the backbone of almost every real CLI tool — argument parsing with <code>args</code>, JSON persistence, interactive prompts, structured output, and clean error handling across every command.</p>
<h2 id="heading-cli-3-darthttp-a-lightweight-api-request-runner">CLI 3 — dart_http: A Lightweight API Request Runner</h2>
<p>This is the most complex CLI in this article – and the most immediately useful. <code>dart_http</code> lets developers make HTTP requests directly from the terminal, with pretty-printed JSON responses, response metadata, header support, and the ability to save responses to a file.</p>
<pre><code class="language-bash">dart_http get https://jsonplaceholder.typicode.com/users/1
dart_http post https://jsonplaceholder.typicode.com/posts --body='{"title":"Hello"}'
dart_http get https://jsonplaceholder.typicode.com/users --save=users.json
dart_http get https://api.example.com/me --header="Authorization: Bearer mytoken"
</code></pre>
<h3 id="heading-building-darthttp">Building dart_http</h3>
<p>Create the project:</p>
<pre><code class="language-bash">dart create -t console dart_http
cd dart_http
</code></pre>
<p>Update <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">name: dart_http
description: A lightweight API request runner for the terminal
version: 1.0.0

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  dart_http: dart_http

dependencies:
  args: ^2.4.2
  http: ^1.2.1

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0
</code></pre>
<p>Run <code>dart pub get</code>.</p>
<p>Project structure:</p>
<pre><code class="language-plaintext">dart_http/
  bin/
    dart_http.dart
  lib/
    runner/
      request_runner.dart
    printer/
      response_printer.dart
    utils/
      headers_parser.dart
  pubspec.yaml
</code></pre>
<h4 id="heading-step-1-headers-parser-libutilsheadersparserdart">Step 1 — Headers Parser (<code>lib/utils/headers_parser.dart</code>)</h4>
<pre><code class="language-dart">Map&lt;String, String&gt; parseHeaders(List&lt;String&gt; rawHeaders) {
  final headers = &lt;String, String&gt;{};

  for (final header in rawHeaders) {
    final index = header.indexOf(':');
    if (index == -1) continue;

    final key = header.substring(0, index).trim();
    final value = header.substring(index + 1).trim();
    headers[key] = value;
  }

  return headers;
}
</code></pre>
<h4 id="heading-step-2-response-printer-libprinterresponseprinterdart">Step 2 — Response Printer (<code>lib/printer/response_printer.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:convert';
import 'dart:io';

void printResponse({
  required int statusCode,
  required String body,
  required int durationMs,
  required int bodyBytes,
}) {
  final statusLabel = _statusLabel(statusCode);
  final size = _formatSize(bodyBytes);

  stdout.writeln('');
  stdout.writeln('\(statusLabel | \){durationMs}ms | $size');
  stdout.writeln('─' * 50);

  try {
    final decoded = jsonDecode(body);
    const encoder = JsonEncoder.withIndent('  ');
    stdout.writeln(encoder.convert(decoded));
  } catch (_) {
    // Not JSON — print as plain text
    stdout.writeln(body);
  }

  stdout.writeln('');
}

String _statusLabel(int code) {
  if (code &gt;= 200 &amp;&amp; code &lt; 300) return '✅ $code';
  if (code &gt;= 300 &amp;&amp; code &lt; 400) return '↪️  $code';
  if (code &gt;= 400 &amp;&amp; code &lt; 500) return '❌ $code';
  return '$code';
}

String _formatSize(int bytes) {
  if (bytes &lt; 1024) return '${bytes}b';
  if (bytes &lt; 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}kb';
  return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}mb';
}
</code></pre>
<h4 id="heading-step-3-request-runner-librunnerrequestrunnerdart">Step 3 — Request Runner (<code>lib/runner/request_runner.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:io';

import 'package:http/http.dart' as http;

import '../printer/response_printer.dart';

Future&lt;void&gt; runRequest({
  required String method,
  required String url,
  required Map&lt;String, String&gt; headers,
  String? body,
  String? saveToFile,
}) async {
  final uri = Uri.tryParse(url);

  if (uri == null) {
    stderr.writeln('Error: "$url" is not a valid URL');
    exit(1);
  }

  stdout.writeln('→ \({method.toUpperCase()} \)url');

  http.Response response;
  final stopwatch = Stopwatch()..start();

  try {
    switch (method.toLowerCase()) {
      case 'get':
        response = await http.get(uri, headers: headers);
      case 'post':
        response = await http.post(uri, headers: headers, body: body);
      case 'put':
        response = await http.put(uri, headers: headers, body: body);
      case 'patch':
        response = await http.patch(uri, headers: headers, body: body);
      case 'delete':
        response = await http.delete(uri, headers: headers);
      default:
        stderr.writeln('Error: unsupported method "$method"');
        exit(2);
    }
  } catch (e) {
    stderr.writeln('Error: request failed — $e');
    exit(1);
  }

  stopwatch.stop();

  printResponse(
    statusCode: response.statusCode,
    body: response.body,
    durationMs: stopwatch.elapsedMilliseconds,
    bodyBytes: response.bodyBytes.length,
  );

  if (saveToFile != null) {
    final file = File(saveToFile);
    file.writeAsStringSync(response.body);
    stdout.writeln('Response saved to $saveToFile');
  }
}
</code></pre>
<h4 id="heading-step-4-entry-point-bindarthttpdart">Step 4 — Entry Point (<code>bin/dart_http.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:io';

import 'package:args/args.dart';

import '../lib/runner/request_runner.dart';
import '../lib/utils/headers_parser.dart';

void main(List&lt;String&gt; arguments) async {
  final parser = ArgParser();

  for (final method in ['get', 'post', 'put', 'patch', 'delete']) {
    final commandParser = ArgParser()
      ..addMultiOption('header', abbr: 'H', help: 'Request header (repeatable)')
      ..addOption('body', abbr: 'b', help: 'Request body (for POST/PUT/PATCH)')
      ..addOption('save', abbr: 's', help: 'Save response body to a file');

    parser.addCommand(method, commandParser);
  }

  parser.addFlag('help', abbr: 'h', negatable: false, help: 'Show help');

  ArgResults results;

  try {
    results = parser.parse(arguments);
  } catch (e) {
    stderr.writeln('Error: $e');
    printHelp();
    exit(2);
  }

  if (results['help'] as bool || results.command == null) {
    printHelp();
    exit(0);
  }

  final command = results.command!;
  final method = command.name!;
  final rest = command.rest;

  if (rest.isEmpty) {
    stderr.writeln('Error: please provide a URL');
    stderr.writeln('Usage: dart_http $method &lt;url&gt;');
    exit(2);
  }

  final url = rest[0];
  final rawHeaders = command['header'] as List&lt;String&gt;;
  final body = command['body'] as String?;
  final saveToFile = command['save'] as String?;

  final headers = parseHeaders(rawHeaders);

  // Default Content-Type for requests with a body
  if (body != null &amp;&amp; !headers.containsKey('Content-Type')) {
    headers['Content-Type'] = 'application/json';
  }

  await runRequest(
    method: method,
    url: url,
    headers: headers,
    body: body,
    saveToFile: saveToFile,
  );
}

void printHelp() {
  stdout.writeln('''
dart_http — a lightweight API request runner

Usage:
  dart_http &lt;method&gt; &lt;url&gt; [options]

Methods:
  get       Send a GET request
  post      Send a POST request
  put       Send a PUT request
  patch     Send a PATCH request
  delete    Send a DELETE request

Options:
  -H, --header    Add a request header (repeatable)
  -b, --body      Request body (JSON string)
  -s, --save      Save response body to a file
  -h, --help      Show this help message

Examples:
  dart_http get https://jsonplaceholder.typicode.com/users
  dart_http get https://api.example.com/me --header="Authorization: Bearer token"
  dart_http post https://api.example.com/posts --body=\'{"title":"Hello"}\'
  dart_http get https://api.example.com/users --save=users.json
  ''');
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/dart_http.dart get https://jsonplaceholder.typicode.com/users/1

# → GET https://jsonplaceholder.typicode.com/users/1
# 200 | 87ms | 510b
# ──────────────────────────────────────────────────
# {
#   "id": 1,
#   "name": "Leanne Graham",
#   "username": "Bret",
#   "email": "Sincere@april.biz"
# }

dart run bin/dart_http.dart get https://jsonplaceholder.typicode.com/users --save=users.json
# → GET https://jsonplaceholder.typicode.com/users
# 200 | 143ms | 5.3kb
# ──────────────────────────────────────────────────
# [ ... ]
# Response saved to users.json

dart run bin/dart_http.dart post https://jsonplaceholder.typicode.com/posts \
  --body='{"title":"Hello from dart_http","userId":1}'
# → POST https://jsonplaceholder.typicode.com/posts
# 201 | 312ms | 72b
</code></pre>
<h2 id="heading-adding-color-and-polish-to-your-cli">Adding Color and Polish to Your CLI</h2>
<p>The CLIs above are functional, but terminal output can be made significantly more readable with color. The <code>ansi_styles</code> package provides ANSI escape code support for coloring text in the terminal.</p>
<p>Add it to <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">dependencies:
  ansi_styles: ^0.3.0
</code></pre>
<p>Using it:</p>
<pre><code class="language-dart">import 'package:ansi_styles/ansi_styles.dart';

stdout.writeln(AnsiStyles.green('✅ Success'));
stdout.writeln(AnsiStyles.red('❌ Error: something went wrong'));
stdout.writeln(AnsiStyles.yellow('⚠️  Warning: check your config'));
stdout.writeln(AnsiStyles.bold('dart_http — API request runner'));
stdout.writeln(AnsiStyles.cyan('→ GET https://api.example.com/users'));
</code></pre>
<p>Apply color intentionally and consistently:</p>
<ul>
<li><p><strong>Green</strong> — success states, completed operations</p>
</li>
<li><p><strong>Red</strong> — errors and failures</p>
</li>
<li><p><strong>Yellow</strong> — warnings and non-blocking issues</p>
</li>
<li><p><strong>Cyan</strong> — informational output, URLs, paths</p>
</li>
<li><p><strong>Bold</strong> — headers, tool names, important values</p>
</li>
</ul>
<p>Avoid coloring everything. Color loses meaning when it is everywhere. Use it to draw the user's eye to what actually matters.</p>
<h2 id="heading-testing-your-cli-tool">Testing Your CLI Tool</h2>
<p>CLI tools are testable, and they should be tested. The most reliable approach is to test the logic inside your commands directly — not the terminal output formatting, but the behaviour.</p>
<p>Add <code>test</code> to your dev dependencies if it's not already there:</p>
<pre><code class="language-yaml">dev_dependencies:
  test: ^1.24.0
</code></pre>
<p><strong>Testing command logic:</strong></p>
<pre><code class="language-dart">import 'package:test/test.dart';

import '../lib/models/task.dart';

void main() {
  group('Task model', () {
    test('copyWith updates isComplete correctly', () {
      final task = Task(
        id: 1,
        title: 'Write tests',
        priority: 'high',
        createdAt: DateTime.now(),
      );

      final completed = task.copyWith(isComplete: true);

      expect(completed.isComplete, isTrue);
      expect(completed.title, equals('Write tests'));
      expect(completed.id, equals(1));
    });

    test('toJson and fromJson round-trips correctly', () {
      final task = Task(
        id: 2,
        title: 'Ship the tool',
        priority: 'normal',
        createdAt: DateTime.parse('2025-01-01T00:00:00.000'),
      );

      final json = task.toJson();
      final restored = Task.fromJson(json);

      expect(restored.id, equals(task.id));
      expect(restored.title, equals(task.title));
      expect(restored.priority, equals(task.priority));
    });
  });
}
</code></pre>
<p><strong>Testing the headers parser:</strong></p>
<pre><code class="language-dart">import 'package:test/test.dart';

import '../lib/utils/headers_parser.dart';

void main() {
  group('parseHeaders', () {
    test('parses a single header correctly', () {
      final result = parseHeaders(['Authorization: Bearer mytoken']);
      expect(result['Authorization'], equals('Bearer mytoken'));
    });

    test('parses multiple headers', () {
      final result = parseHeaders([
        'Authorization: Bearer token',
        'Accept: application/json',
      ]);
      expect(result.length, equals(2));
      expect(result['Accept'], equals('application/json'));
    });

    test('ignores malformed headers without a colon', () {
      final result = parseHeaders(['malformed-header']);
      expect(result.isEmpty, isTrue);
    });
  });
}
</code></pre>
<p>Run your tests:</p>
<pre><code class="language-bash">dart test
</code></pre>
<h2 id="heading-deploying-and-distributing-your-cli">Deploying and Distributing Your CLI</h2>
<p>Building a CLI tool is half the work. Getting it into the hands of developers is the other half. There are five distribution paths available, each suited to a different use case.</p>
<h3 id="heading-mode-1-pubdev-public-package-distribution">Mode 1: pub.dev — Public Package Distribution</h3>
<p>Publishing to pub.dev makes your tool installable by anyone in the Dart and Flutter community with a single command.</p>
<h4 id="heading-prepare-your-package">Prepare your package:</h4>
<p>Your <code>pubspec.yaml</code> needs to be complete:</p>
<pre><code class="language-yaml">name: dart_http
description: A lightweight API request runner for Dart developers.
version: 1.0.0
homepage: https://github.com/yourname/dart_http

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  dart_http: dart_http
</code></pre>
<p>The <code>executables</code> block is critical. It tells pub.dev which script in <code>bin/</code> to expose as a runnable command.</p>
<p>You also need:</p>
<ul>
<li><p><code>README.md</code> — what the tool does, how to install it, usage examples</p>
</li>
<li><p><code>CHANGELOG.md</code> — version history</p>
</li>
<li><p><code>LICENSE</code> — an open source license (MIT is standard)</p>
</li>
</ul>
<h4 id="heading-validate-before-publishing">Validate before publishing:</h4>
<pre><code class="language-bash">dart pub publish --dry-run
</code></pre>
<p>This runs all validation checks without actually publishing. Fix any warnings before proceeding.</p>
<h4 id="heading-publish">Publish:</h4>
<pre><code class="language-bash">dart pub publish
</code></pre>
<p>You will be prompted to authenticate with your pub.dev account. Once published, your tool is available globally:</p>
<pre><code class="language-bash">dart pub global activate dart_http
dart_http get https://api.example.com/users
</code></pre>
<h3 id="heading-mode-2-local-path-activation">Mode 2: Local Path Activation</h3>
<p>For internal team tools that you don't want to publish publicly, activate directly from a local or cloned repository:</p>
<pre><code class="language-bash">dart pub global activate --source path /path/to/dart_http
</code></pre>
<p>Any developer on the team clones the repo and runs this command once. The tool is then available globally in their terminal without needing a pub.dev publish.</p>
<p>This is the right distribution mode for:</p>
<ul>
<li><p>Internal company tooling</p>
</li>
<li><p>Tools that depend on private packages</p>
</li>
<li><p>Work-in-progress tools shared within a team before a public release</p>
</li>
</ul>
<h3 id="heading-mode-3-compiled-binary-via-github-releases">Mode 3: Compiled Binary via GitHub Releases</h3>
<p>Dart can compile to a self-contained native executable — no Dart SDK required on the target machine. This makes your tool accessible to developers outside the Dart ecosystem.</p>
<h4 id="heading-compile">Compile:</h4>
<pre><code class="language-bash"># macOS
dart compile exe bin/dart_http.dart -o dist/dart_http-macos

# Linux
dart compile exe bin/dart_http.dart -o dist/dart_http-linux

# Windows
dart compile exe bin/dart_http.dart -o dist/dart_http-windows.exe
</code></pre>
<p>The compiled binary is fully self-contained. Copy it to any machine and run it — no Dart installation needed.</p>
<h4 id="heading-automate-with-github-actions">Automate with GitHub Actions:</h4>
<p>Create <code>.github/workflows/release.yml</code>:</p>
<pre><code class="language-yaml">name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v3

      - uses: dart-lang/setup-dart@v1
        with:
          sdk: stable

      - name: Install dependencies
        run: dart pub get

      - name: Compile binary
        run: |
          mkdir -p dist
          dart compile exe bin/dart_http.dart -o dist/dart_http-${{ runner.os }}

      - name: Upload binary to release
        uses: softprops/action-gh-release@v1
        with:
          files: dist/dart_http-${{ runner.os }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
</code></pre>
<p>Every time you push a version tag (<code>v1.0.0</code>), GitHub Actions compiles binaries for all three platforms and attaches them to the GitHub Release automatically.</p>
<h4 id="heading-write-an-install-script">Write an install script:</h4>
<pre><code class="language-bash">#!/usr/bin/env bash
set -euo pipefail

VERSION="1.0.0"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
BINARY="dart_http-$OS"
INSTALL_DIR="/usr/local/bin"

curl -L "https://github.com/yourname/dart_http/releases/download/v\(VERSION/\)BINARY" \
  -o "$INSTALL_DIR/dart_http"

chmod +x "$INSTALL_DIR/dart_http"
echo "dart_http installed successfully"
</code></pre>
<p>Developers install it with:</p>
<pre><code class="language-bash">curl -fsSL https://raw.githubusercontent.com/yourname/dart_http/main/install.sh | bash
</code></pre>
<h3 id="heading-mode-4-homebrew-tap">Mode 4: Homebrew Tap</h3>
<p>Homebrew is the standard package manager for macOS and is widely used on Linux. A Homebrew tap makes your tool installable with <code>brew install</code> — the most familiar installation pattern for macOS developers.</p>
<h4 id="heading-create-your-tap-repository">Create your tap repository:</h4>
<p>Create a new GitHub repository named <code>homebrew-tools</code> (the <code>homebrew-</code> prefix is required by Homebrew's naming convention).</p>
<h4 id="heading-write-the-formula">Write the formula:</h4>
<p>Create <code>Formula/dart_http.rb</code> in that repository:</p>
<pre><code class="language-ruby">class DartHttp &lt; Formula
  desc "A lightweight API request runner for the terminal"
  homepage "https://github.com/yourname/dart_http"
  version "1.0.0"

  on_macos do
    url "https://github.com/yourname/dart_http/releases/download/v1.0.0/dart_http-macOS"
    sha256 "YOUR_SHA256_HASH_HERE"
  end

  on_linux do
    url "https://github.com/yourname/dart_http/releases/download/v1.0.0/dart_http-Linux"
    sha256 "YOUR_SHA256_HASH_HERE"
  end

  def install
    bin.install "dart_http-#{OS.mac? ? 'macOS' : 'Linux'}" =&gt; "dart_http"
  end

  test do
    system "#{bin}/dart_http", "--help"
  end
end
</code></pre>
<p>Generate the SHA256 hash for each binary:</p>
<pre><code class="language-bash">shasum -a 256 dist/dart_http-macOS
</code></pre>
<h4 id="heading-install-from-the-tap">Install from the tap:</h4>
<pre><code class="language-bash">brew tap yourname/tools
brew install dart_http
</code></pre>
<p>When you release a new version, update the <code>url</code> and <code>sha256</code> values in the formula and push the change. Users run <code>brew upgrade dart_http</code> to update.</p>
<h3 id="heading-mode-5-docker">Mode 5: Docker</h3>
<p>Docker distribution is best suited for CI environments, teams that standardise on containers, or tools with complex dependencies.</p>
<h4 id="heading-write-a-dockerfile">Write a Dockerfile:</h4>
<pre><code class="language-dockerfile">FROM dart:stable AS build

WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

COPY . .
RUN dart compile exe bin/dart_http.dart -o /app/dart_http

FROM debian:stable-slim
COPY --from=build /app/dart_http /usr/local/bin/dart_http

ENTRYPOINT ["dart_http"]
</code></pre>
<p>This uses a multi-stage build: the first stage compiles the binary using the Dart SDK image, and the second stage copies only the binary into a minimal Debian image. The final image has no Dart SDK — just the compiled binary.</p>
<h4 id="heading-build-and-run">Build and run:</h4>
<pre><code class="language-bash">docker build -t dart_http .
docker run dart_http get https://jsonplaceholder.typicode.com/users/1
</code></pre>
<h4 id="heading-publish-to-docker-hub">Publish to Docker Hub:</h4>
<pre><code class="language-bash">docker tag dart_http yourname/dart_http:1.0.0
docker push yourname/dart_http:1.0.0
</code></pre>
<p>Users can then run your tool without installing anything locally:</p>
<pre><code class="language-bash">docker run yourname/dart_http get https://api.example.com/users
</code></pre>
<h2 id="heading-choosing-the-right-distribution-mode">Choosing the Right Distribution Mode</h2>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Best for</th>
<th>Dart SDK required</th>
</tr>
</thead>
<tbody><tr>
<td>pub.dev</td>
<td>Public Dart/Flutter developer tools</td>
<td>Yes</td>
</tr>
<tr>
<td>Local path activation</td>
<td>Internal team tools, pre-release builds</td>
<td>Yes</td>
</tr>
<tr>
<td>Compiled binary</td>
<td>Language-agnostic tools, broad adoption</td>
<td>No</td>
</tr>
<tr>
<td>Homebrew tap</td>
<td>macOS/Linux developer tools</td>
<td>No</td>
</tr>
<tr>
<td>Docker</td>
<td>CI environments, complex dependencies</td>
<td>No</td>
</tr>
</tbody></table>
<p>For most tools, the practical recommendation is:</p>
<ul>
<li><p>Start with <strong>pub.dev</strong> if your audience is Dart developers</p>
</li>
<li><p>Add <strong>compiled binary + GitHub Releases</strong> once you want broader adoption</p>
</li>
<li><p>Add a <strong>Homebrew tap</strong> when macOS developers start asking for it</p>
</li>
<li><p>Use <strong>Docker</strong> only when it is already part of your team's workflow</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You've gone from understanding what a CLI is to building three progressively complex tools and distributing them across five different channels.</p>
<p>The foundational skills – <code>args</code>, <code>stdin</code>, <code>stdout</code>, <code>stderr</code>, exit codes, file I/O, and process spawning – are the same building blocks that tools like <code>flutter</code>, <code>git</code>, and <code>dart</code> themselves are built on. Everything else is composition.</p>
<p>The three CLIs we built (Hello CLI, <code>dart_todo</code>, and <code>dart_http</code>) each introduced a new layer: raw Dart fundamentals, the <code>args</code> package with JSON persistence, and real-world HTTP interaction. The distribution section ensures that whatever you build next, you have a clear path to getting it in front of the developers who will use it.</p>
<p>Dart is a powerful language for CLI development. Its strong typing, async support, native compilation, and pub.dev ecosystem make it a serious choice for building developer tooling, not just mobile apps.</p>
<p>The next step is building something that solves a real problem for you or your team, and shipping it.</p>
<p>Happy coding!!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Unblock Your AI PR Review Bottleneck: A Tech Lead’s Guide to Building a Codebase-Aware Reviewer ]]>
                </title>
                <description>
                    <![CDATA[ A few months ago, I was reviewing a pull request that added three new API endpoints. The diff was clean. Tests passed. The agent that generated it had even written sensible authorisation checks. By ev ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-unblock-ai-pr-review-bottleneck-handbook/</link>
                <guid isPermaLink="false">69f906a346610fd60629a300</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ code review ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ leadership ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Qudrat Ullah ]]>
                </dc:creator>
                <pubDate>Mon, 04 May 2026 20:50:43 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/c94dff21-66d0-4256-bf3e-25c1978364d9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A few months ago, I was reviewing a pull request that added three new API endpoints. The diff was clean. Tests passed. The agent that generated it had even written sensible authorisation checks. By every signal I usually rely on, it was ready to merge.</p>
<p>The problem only showed up when I checked which authentication middleware the agent had imported.</p>
<p>Our codebase had two: a v1 middleware backed by MongoDB and a v2 middleware backed by MySQL, which we had spent the previous quarter migrating.</p>
<p>New endpoints were supposed to use v2. The agent had used v1 for all three. Tests passed because user records still existed in both databases (that was the point of the migration), and the v1 middleware happily authenticated them. The code worked. But every new endpoint we shipped was reinforcing the legacy auth path we had just spent a quarter trying to retire.</p>
<p>I caught it on the second read. Twenty minutes after the comments, the engineer fixed it and reopened the PR. The third reviewer probably wouldn't have caught it. The migration timeline lived in a Slack thread from six months earlier. The rule that "new endpoints use v2" lived in my head.</p>
<p>This kind of catch is the slow-burn version of why AI changed my job as a tech lead. Code generation got faster. My review queue got longer. The hardest reviews were the ones where everything looked right, and the only thing wrong was something that lived in the team's collective memory rather than in the diff.</p>
<p>This handbook is about what we did to fix that. It's the story of how we went from drowning in clean-looking PRs to running a custom AI PR reviewer that catches a meaningful share of these mistakes before any human is pulled in. The fix turned out to be less about buying a better tool and more about moving the team's memory into a place the AI could actually read.</p>
<p>The lessons should transfer whether your team uses Claude Code, Cursor, Cline, GitHub Copilot, or any combination. The structure matters more than the tool.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-the-old-bottleneck-and-the-one-ai-created">The Old Bottleneck, and the One AI Created</a></p>
</li>
<li><p><a href="#heading-what-the-new-review-work-actually-looks-like">What the New Review Work Actually Looks Like</a></p>
</li>
<li><p><a href="#heading-why-i-did-not-just-buy-a-tool">Why I Did Not Just Buy a Tool</a></p>
</li>
<li><p><a href="#heading-the-realisation-move-the-rules-into-the-codebase">The Realisation: Move the Rules Into the Codebase</a></p>
</li>
<li><p><a href="#heading-two-files-that-changed-everything-agentsmd-and-claudemd">Two Files That Changed Everything: AGENTS.md and CLAUDE.md</a></p>
</li>
<li><p><a href="#heading-where-per-service-memory-files-earn-their-keep">Where Per-Service Memory Files Earn Their Keep</a></p>
</li>
<li><p><a href="#heading-what-this-looks-like-on-disk">What This Looks Like on Disk</a></p>
</li>
<li><p><a href="#heading-generated-documentation-as-a-side-effect">Generated Documentation as a Side Effect</a></p>
</li>
<li><p><a href="#heading-building-the-pr-review-command">Building the PR Review Command</a></p>
</li>
<li><p><a href="#heading-guardrails-read-only-by-default">Guardrails: Read-Only by Default</a></p>
</li>
<li><p><a href="#heading-the-compounding-loop-that-made-the-real-difference">The Compounding Loop That Made the Real Difference</a></p>
</li>
<li><p><a href="#heading-starting-from-zero-on-an-existing-project">Starting From Zero on an Existing Project</a></p>
</li>
<li><p><a href="#heading-what-still-needs-human-review">What Still Needs Human Review</a></p>
</li>
<li><p><a href="#heading-a-two-week-setup-plan">A Two-Week Setup Plan</a></p>
</li>
<li><p><a href="#heading-what-is-working-what-i-am-still-improving">What Is Working, What I Am Still Improving</a></p>
</li>
<li><p><a href="#heading-sources">Sources</a></p>
</li>
</ul>
<h2 id="heading-the-old-bottleneck-and-the-one-ai-created">The Old Bottleneck, and the One AI Created</h2>
<p>To understand why this fix was needed, it helps to remember what reviewing code looked like a couple of years ago.</p>
<p>Back then, the slow part was upstream of the PR. A ticket would land, and before anyone could open a branch, there was a long preamble of context-gathering.</p>
<p>Junior engineers needed time to understand what the change was for. Senior engineers had to explain business rules and architectural decisions. Tickets sat in "ready" columns for days while someone with the right context made themselves available. Then the writing itself took time, because typing real code is slower than typing comments about it.</p>
<p>That bottleneck mostly dissolved when the team got serious about AI-assisted development. Engineers used the agent to read the codebase, ask clarifying questions, draft an implementation plan, and produce a working branch in hours instead of days. Tickets moved through the queue faster. Junior engineers shipped more without blocking on senior availability. From the outside, this looked like an unambiguous win.</p>
<p>But the bottleneck didn't disappear. It moved.</p>
<p>Within a few weeks of widespread AI adoption, my review queue had doubled. Then tripled. Engineers were opening PRs faster than I could read them.</p>
<p>The PRs themselves looked clean: well-formatted, with sensible variable names, passing tests, and AI-generated descriptions that read better than most human-written ones.</p>
<p>On the surface, this was great. In practice, it was creating a different kind of pain. I was the senior engineer who knew which patterns mattered and which paths through the codebase were the right ones, and I was the bottleneck. The team's velocity was now capped by my reading speed.</p>
<p>The CircleCI 2026 State of Software Delivery report confirmed I was not alone. Drawing on more than 28 million CI workflow runs across over 22,000 organisations, the report showed feature branch throughput had grown 59% year over year, the largest jump CircleCI had ever measured. Main branch throughput, where code actually gets promoted to production, fell by 7% for the median team in the same period. Build success rates dropped to 70.8%, the lowest in five years.</p>
<p>The pattern was consistent across the industry. AI accelerated writing. The rest of the system absorbed the cost.</p>
<p>So the question for me, as a tech lead, became concrete: how do I unblock myself without lowering the bar?</p>
<h2 id="heading-what-the-new-review-work-actually-looks-like">What the New Review Work Actually Looks Like</h2>
<p>Before I explain the fix, it helps to know what kinds of issues were actually piling up. They weren't the dramatic kind. None of them would crash production. They were small, recurring, and looked plausible at a glance.</p>
<p>Take the simplest case I kept catching. An engineer would ask the agent to add a delete button on a new screen. The button needed to call our existing backend delete endpoint. Instead of reusing the hook the team already had for that endpoint, the agent would write the fetch call inline.</p>
<p>The code worked. The tests passed. But a week later, when someone changed the backend response shape, only one of the two call sites got updated.</p>
<p>That kind of duplication doesn't show up in a code review unless the reviewer happens to remember that a hook exists.</p>
<p>Another example I saw constantly: the agent comparing a status field against the literal string <code>"completed"</code> instead of using the <code>Status.Completed</code> enum that the rest of the services used. The code ran. The tests ran. The next refactor of the enum quietly skipped the file. After a few days, someone would spend half a day debugging a state machine that was working fine until the agent's literal silently fell out of sync.</p>
<p>These were two-minute fixes once spotted, but spotting them took me a reasonable time per PR. The friction wasn't the difficulty. It was the repetition.</p>
<p>The pattern repeated across larger problems, too.</p>
<p>I once asked an agent to build an event creation wizard. The wizard needed several dropdowns and one new component.</p>
<p>We have a design system folder where shared UI components live, and the rule on the team is simple: check there first, and if you build something new, register it there.</p>
<p>The agent had no way to know that. It only loaded the wizard's own files, so it never opened the design system folder. It generated brand new dropdowns inline, with APIs that were almost identical to the ones we already had. The new component went straight into the wizard rather than into the design system. CI passed. The wizard worked. We caught the duplication in human review, but it was the kind of catch that depended entirely on a reviewer who happened to know the design system existed.</p>
<p>The same pattern hit in one of the repos I was looking at for backend architecture. Backend follows a strict four-layer pattern: route, controller, app, repo. Controllers must never call repository functions directly. That rule keeps authorisation centralised, business logic testable, and database concerns isolated.</p>
<p>One PR I reviewed had the agent calling repo functions straight from a controller, skipping the app layer entirely. The code worked. The tests passed because the agent had also written tests against the new shape. But it broke a discipline the team had spent years building. If that PR had landed, the next AI-assisted PR could have used it as a template, and the layering would have eroded one diff at a time.</p>
<p>The common thread is that all of these mistakes had something written down somewhere, in code, in a Slack thread, in a senior engineer's head, that would have prevented them. The information existed. The agent just couldn't see it.</p>
<h2 id="heading-why-i-did-not-just-buy-a-tool">Why I Did Not Just Buy a Tool</h2>
<p>The obvious next move was to install one of the AI PR reviewers that flooded the market in 2026.</p>
<p>I evaluated several. Anthropic launched Claude Code Review in March 2026, billed on token usage and averaging \(15 to \)25 per review. CodeRabbit Pro charges \(24 per developer per month on annual billing, or \)30 per developer per month on monthly billing, with seats counted against developers who actually open PRs. Greptile in March 2026 moved to a base-plus-usage model at $30 per seat per month, including 50 reviews, after which each additional review costs a dollar. GitHub announced that all Copilot plans will transition to usage-based billing on June 1, 2026, with code reviews consuming both AI Credits and GitHub Actions minutes from that date.</p>
<p>For a small team with low PR volume, none of these is a dealbreaker. For a larger team running heavy AI-assisted development, the costs compound fast. A 10-person team running five PRs each per day blows through Greptile's included reviews in a single week. CodeRabbit Pro at \(24 per seat scales linearly with developers. The premium Claude Code Review at \)15 to $25 per PR is the most expensive option per review by an order of magnitude.</p>
<p>I looked at the cost numbers, but cost wasn't actually the deciding factor. The deciding factor was that none of these tools would have caught the problems I just listed.</p>
<p>A generic reviewer wouldn't have caught the v1/v2 middleware. It had no way to know v2 was the canonical path. A generic reviewer wouldn't have caught the duplicate dropdowns. It had no way to know our design system existed. A generic reviewer wouldn't have caught the bypassed architecture. It had no way to know that controllers must not call repositories.</p>
<p>The information that lets a reviewer flag any of these is exactly the information that lives in the team's head, not in any tool's default prompt.</p>
<p>The better-rated tools support custom rules, and that's where I started to see the real shape of the problem. Once you are configuring custom rules, you've already accepted that the value is in the rules. The tool is just whatever runs them.</p>
<p>This raised a different question: if the rules are the product, why pay per seat or per review for someone else's wrapper around them?</p>
<p>This is what made me change direction.</p>
<h2 id="heading-the-realisation-move-the-rules-into-the-codebase">The Realisation: Move the Rules Into the Codebase</h2>
<p>Once I started thinking of the rules as the product, the path forward got clearer.</p>
<p>I asked myself a simple question: what was I actually doing in code review that the AI was not? The answer turned out to be the same thing, over and over. I was typing review comments that captured a piece of the team's memory.</p>
<p>"Use the Status enum, not a string literal." "There is already a hook for this in <code>/hooks/useDeleteItem</code>." "Controllers must not import from the repo layer; route this through the app layer." "Check the design system folder before creating new components."</p>
<p>Each of those comments was knowledge that lived in my head and arrived in the codebase one PR comment at a time. None of it was available to the agent the next time it generated a similar PR.</p>
<p>So the fix was not to buy a smarter reviewer. The fix was to write the rules down in a place every agent on the team would read before any review happened.</p>
<p>If I had typed "use the enum, not a literal" three times in three different PRs, that was a rule the agent should know about from now on. If I had pointed at the design system folder for the fourth time, that was a rule. If I had explained the four-layer architecture twice in PR comments, that was a rule.</p>
<p>I needed somewhere to put these rules. That turned out to be a less obvious decision than I expected.</p>
<h2 id="heading-two-files-that-changed-everything-agentsmd-and-claudemd">Two Files That Changed Everything: AGENTS.md and CLAUDE.md</h2>
<p>If you start looking into how to give an AI agent a persistent project context, you run into two competing conventions almost immediately.</p>
<p>The first is <strong>AGENTS.md</strong>, an open standard that has gathered real momentum. According to InfoQ, by mid-2025, the format had already been adopted by more than 20,000 GitHub repositories and was being positioned as a complement to traditional documentation: machine-readable context that lives alongside human-facing files like README.md.</p>
<p>The standard's own site reports it is now used by more than 60,000 open-source projects and has moved to stewardship under the Agentic AI Foundation, which sits inside the Linux Foundation. The format is supported by OpenAI Codex, GitHub Copilot, Google Gemini, Cursor, and Windsurf, among others.</p>
<p>The second is <strong>CLAUDE.md</strong>, which is Anthropic's convention for Claude Code. The Claude Code documentation describes two complementary memory systems: CLAUDE.md, where you write the persistent context yourself, and an auto-memory mechanism that lets Claude save its own notes from corrections and observed patterns. By default, Claude Code reads CLAUDE.md, not AGENTS.md.</p>
<p>This split mattered for us because half the team uses Claude Code and the other half uses Cursor. We had two practical options: maintain both files with the same content (and accept the duplication), or symlink one filename to the other so both ecosystems read the same source of truth. We went with the symlink. It's one less thing to drift.</p>
<p>The next question was what to actually put in the file. After a few iterations, here's the shape that worked. Think of it as a briefing document for a new engineer who has read no code and seen no Slack threads. The minimum content was:</p>
<ul>
<li><p>The tech stack (languages, frameworks, package manager)</p>
</li>
<li><p>The project structure, especially important for our monorepo</p>
</li>
<li><p>Where shared utilities, components, and helpers live, and the rule that new code should reuse them before creating new versions</p>
</li>
<li><p>Architectural patterns the project follows, with file path examples</p>
</li>
<li><p>Anti-patterns and what to do instead</p>
</li>
<li><p>Test conventions and where good examples live</p>
</li>
<li><p>Pointers to deeper documentation when more detail is needed</p>
</li>
</ul>
<p>Two practical rules emerged from the first month of using these files.</p>
<p><strong>Keep them lean:</strong> There is a counterintuitive failure mode with long instruction lists: the agent doesn't just skip the new ones at the bottom. The average compliance across all of them drops. A bloated memory file becomes a memory file that the agent skims. If a section runs more than a paragraph or two, move it to a separate document and link to it.</p>
<p><strong>Phrase rules as imperatives, not aspirations:</strong> "Controllers must not call repositories. Route through the app layer." beats "Try to keep controllers thin." The first is testable. The second is decorative.</p>
<p>That was the entry point. But a single root-level file was not enough for a monorepo with multiple services and frontends, which led to the next decision.</p>
<h2 id="heading-where-per-service-memory-files-earn-their-keep">Where Per-Service Memory Files Earn Their Keep</h2>
<p>A single <code>AGENTS.md</code> at the root of a monorepo collapses under its own weight pretty quickly. Each service in our codebase has its own architecture, conventions, and business rules. Trying to fit all of that into one file produced a long document that the agent treated as background noise, and we were back to the bloat problem from the previous section.</p>
<p>The pattern that worked: every service or app gets its own <code>AGENTS.md</code> at its root, and the project-level <code>AGENTS.md</code> becomes an index that points to them.</p>
<p>A per-service <code>AGENTS.md</code> covers things like:</p>
<ul>
<li><p>The architecture for this service (the four-layer pattern, the directory layout)</p>
</li>
<li><p>Naming conventions specific to this service</p>
</li>
<li><p>Test patterns and where good examples live</p>
</li>
<li><p>Business rules that this service is responsible for</p>
</li>
<li><p>Inter-service contracts and what other services consume from this one</p>
</li>
<li><p>Pointers to deeper docs in <code>docs/</code></p>
</li>
<li><p>A "Lessons learned" section, which I'll come back to in the section on the compounding loop</p>
</li>
</ul>
<p>The same lean rule applies. Keep it short, point at examples, and phrase guidance as imperatives.</p>
<p>The reason this works mechanically is that the agent loads the right files for the work at hand. When an engineer asks the agent to change something in <code>backend/</code>, the agent reads the project-level <code>AGENTS.md</code>, sees that work in <code>backend/</code> should be guided by <code>backend/AGENTS.md</code>, and loads that file. It doesn't load the frontend's <code>AGENTS.md</code>, because that work is somewhere else. The context window stays focused on what's relevant.</p>
<p>Without this split, you have two bad options. Either you put everything in the root file, where the agent ignores most of it, or you put nothing in the root file, where the agent has no team context at all. The per-service split gives you both depth and signal.</p>
<p>But these files only work if the deeper docs they point to actually exist, which is where the next piece of the system came in.</p>
<h2 id="heading-what-this-looks-like-on-disk">What This Looks Like on Disk</h2>
<p>Before going further, it helps to see the whole structure laid out. Here's the shape we settled on for our monorepo. The exact folder names follow Claude Code's conventions. If you use Cursor, it would be <code>.cursor/</code>, and if you use Cline, it would be <code>.clinerules</code> – but the shape transfers directly.</p>
<pre><code class="language-plaintext">project-root/
├── AGENTS.md                       # symlink to CLAUDE.md
├── CLAUDE.md                       # root memory file
├── README.md                       # human-facing project readme
│
├── .claude/                        # tool-specific config folder
│   ├── README.md                   # explains the .claude/ layout
│   ├── settings.json               # permissions and guardrails
│   ├── agents/                     # specialised subagents (optional)
│   ├── commands/                   # slash commands engineers run
│   │   ├── review-pr.md            # the PR review command
│   │   └── plan-feature.md         # implementation plan command
│   ├── hooks/                      # lifecycle hooks (optional)
│   ├── pr-rules/                   # rule files for PR review
│   │   ├── common.md               # rules that apply to every PR
│   │   ├── frontend.md             # rules for frontend changes
│   │   ├── backend.md              # rules for backend changes
│   │   ├── service-a.md            # rules for service-a
│   │   └── service-b.md            # rules for service-b
│   └── skills/                     # reusable workflows
│
├── frontend/
│   ├── AGENTS.md                   # frontend conventions
│   ├── docs/
│   │   ├── overview.md
│   │   ├── architecture.md         # routing, state, data layer
│   │   ├── design-system.md        # design system reference
│   │   └── testing.md              # test conventions
│   └── src/
│
├── backend/
│   ├── AGENTS.md                   # the four-layer pattern
│   ├── docs/
│   │   ├── overview.md
│   │   ├── architecture.md         # route -&gt; controller -&gt; app -&gt; repo
│   │   ├── auth.md                 # v1 vs v2 middleware
│   │   ├── business-rules.md
│   │   └── integrations.md
│   └── src/
│
├── service-a/
│   ├── AGENTS.md
│   ├── docs/
│   │   ├── overview.md
│   │   ├── business-rules.md
│   │   └── integrations.md
│   └── src/
│
└── service-b/
    ├── AGENTS.md
    ├── docs/
    │   ├── overview.md
    │   ├── business-rules.md
    │   └── integrations.md
    └── src/
</code></pre>
<p>A few things worth pointing out:</p>
<p>The <code>.claude/</code> folder uses standard subfolder names: <code>commands</code>, <code>agents</code>, <code>hooks</code>, <code>skills</code>. These follow Claude Code's plugin model, but most modern AI coding tools have similar slots. Following the conventions makes the structure recognisable to anyone on the team and lowers the cost of switching tools later.</p>
<p>The <code>pr-rules/</code> folder isn't a standard convention. It's a folder we created to hold per-area review rules that the PR review command loads selectively. You don't have to call it <code>pr-rules</code> – the name matters less than having one place where review rules live.</p>
<p>Each service has its own <code>AGENTS.md</code> plus a <code>docs/</code> folder. The root <code>AGENTS.md</code> is short and acts as an index. It tells the agent things like "if you touch files in <code>backend/</code>, also read <code>backend/AGENTS.md</code> first." The per-service file then points at the deeper docs as needed.</p>
<h2 id="heading-generated-documentation-as-a-side-effect">Generated Documentation as a Side Effect</h2>
<p>Setting up per-service <code>AGENTS.md</code> files surfaced a problem I had been quietly avoiding. Most of our services didn't have decent documentation. Not API reference material, which lives in code, but the higher-level "what does this service do, what business rules does it enforce, what does it consume and produce" information that lives in nobody's head except the original author's.</p>
<p>The honest reason was that writing this kind of documentation by hand had never paid back the time it took. By the time the doc was finished, half of it was already stale.</p>
<p>So I tried something I wouldn't have considered earlier. I used the AI itself to generate a first draft for each service. I pointed the agent at each service's code and asked it to produce a <code>docs/</code> folder with a specific structure: an overview, a list of business rules, an integrations document, a domain model, and any quirks worth knowing. The agent read the code, traced the call paths, and wrote a draft.</p>
<p>I then reviewed the output by hand, corrected the things it got wrong, and committed the result. The first drafts were 70-80% correct. The remaining 20-30% was where the agent had made plausible but wrong inferences, and those were exactly the cases where human review mattered.</p>
<p>The generated docs ended up serving two audiences. The agent uses them when reasoning about changes, which means it has real context for the service it's touching rather than guessing from local files. And new engineers use them on their first day, which has cut our onboarding time noticeably.</p>
<p>We used to write onboarding documents that drifted out of date within months. These docs stay closer to current because the agent reads them on every PR, and any drift gets surfaced when the agent gives wrong advice based on stale information.</p>
<p>The pattern that works is to keep the per-service <code>AGENTS.md</code> short and pointing at the docs, rather than duplicating their content. <code>AGENTS.md</code> is the always-loaded index. <code>docs/</code> holds the details. The agent loads the relevant doc on demand when the task calls for it.</p>
<p>With the rules in place and the docs in place, I had everything I needed to build the actual reviewer.</p>
<h2 id="heading-building-the-pr-review-command">Building the PR Review Command</h2>
<p>This is the piece that most directly unblocked my queue.</p>
<p>This command didn't appear out of nowhere. It started as the checklist I was running through in my head every time I opened a PR. I was reviewing every change manually, leaving the same comments, flagging the same patterns. So I wrote that checklist down, expanded it with references to the per-service docs for the harder rules, and turned it into a command anyone on the team could run.</p>
<p>Then I handed it to the engineers and changed the rule: run this on your own branch before marking the PR ready for review. That single shift moved the work from after the PR was opened to before. Engineers now catch 90-95% of the blockers, improvements, and nice-to-haves on their own machine, fix them locally, and only then push the change.</p>
<p>The PR description includes the AI's summary, so when anyone opens the PR, they can see the reviewer's green signal at the top before even reading the diff.</p>
<p>GitHub stays clean. The conversation on the PR becomes about the things that actually need a human, not the recurring stuff the team already knows how to fix.</p>
<p>The command lives in <code>.claude/commands/review-pr.md</code>. Here's a generalised version. Your tool's command structure may differ, but the shape is what matters.</p>
<pre><code class="language-markdown"># Review PR

Review the current branch's PR. Be direct. Cite `file:line`. Surface real issues,
no padding.

## 1. Scope the diff

Run, in order:

    gh pr view --json number,title,body,headRefName 2&gt;/dev/null || true
    git fetch origin main
    git log --no-merges origin/main..HEAD --oneline
    git diff origin/main...HEAD --stat
    git diff origin/main...HEAD

Read the PR body. Note the stated intent. Every change should trace to it. Flag
anything that does not.

Use `...` (three dots) for the diff. It compares against the merge base and
excludes commits brought in by merging main.

## 2. Load rules

Always read `.claude/pr-rules/common.md`.

Then read the per-area file for each workspace touched in the diff:

| Workspace path | Rules file                      |
| -------------- | ------------------------------- |
| `frontend/**`  | `.claude/pr-rules/frontend.md`  |
| `backend/**`   | `.claude/pr-rules/backend.md`   |
| `service-a/**` | `.claude/pr-rules/service-a.md` |
| `service-b/**` | `.claude/pr-rules/service-b.md` |

For non-trivial changes, follow doc pointers inside the rules files (for
example, `backend/AGENTS.md`, `backend/docs/architecture.md`).

Apply every entry under each file's "Lessons learned" section as a check.

## 3. Output

Use exactly this format.

    ## Summary
    &lt;one paragraph: what the PR does, whether it matches the stated intent&gt;

    ## Blocking
    - [file:line] issue, why it blocks

    ## Should fix
    - [file:line] issue

    ## Nice to have
    - issue

    ## Verified
    - what was checked and looks good

If nothing blocks, say so. Do not manufacture concerns.

If you find an issue worth remembering for future PRs, suggest the bullet to
add to the relevant rules file's "Lessons learned" section. Do not edit the
rules file yourself, leave that to the human.
</code></pre>
<p>A few of the design choices in this command turned out to matter more than I expected.</p>
<p>The structured output format (Summary, Blocking, Should fix, Nice to have, Verified) keeps the review easy to scan and easy to paste into a PR description. The "Verified" section is the most underrated of the five: it tells the human reviewer what the AI already checked, so they can spend their attention elsewhere. Without it, the human reviewer ends up doing the same checks twice.</p>
<p>The instruction to be direct and stop padding does real work. Without it, AI reviewers tend to manufacture concerns to look thorough, which trains engineers to skim past the bot. Telling it explicitly to say "nothing blocks" when nothing blocks made the signal-to-noise ratio of the output much better.</p>
<p>The "suggest a bullet for the rules file" instruction at the end is the heart of the whole system, and I'll explain why in the section on the compounding loop. The key constraint here is that the agent suggests the bullet but doesn't commit to it. A human evaluates whether it's general enough to be a rule, and only then adds it to the file. That manual step is what keeps the rules sharp instead of bloated.</p>
<p>With each PR, if humans fix something or the AI suggests something, you keep adding those to your MD files and keep improving your agents for the future. The result compounds quickly.</p>
<p>One more thing here: the diff-scoping commands are all read-only. The command shouldn't be able to push, edit PRs, or close anything. Which is the next piece of the system.</p>
<h2 id="heading-guardrails-read-only-by-default">Guardrails: Read-Only by Default</h2>
<p>Giving an AI agent broad permissions on your codebase is a security incident waiting to happen. Even if you trust the model to behave, an LLM occasionally does unexpected things, and a fast-moving agent on an unrestricted shell can cause damage in seconds.</p>
<p>The fix is a <code>settings.json</code> (in Claude Code – other tools have their own equivalents) at the root of <code>.claude/</code> that explicitly declares what the agent can and can't do. The deny list matters more than the allow list, and a good one is organised around four categories of risk.</p>
<p>The first is <strong>secrets and configuration</strong>. Any read against anything that appears to be a credential is blocked. That covers <code>.env</code> files of every variant (<code>.env</code>, <code>.env.local</code>, <code>.env.production</code>, <code>.env.test</code>, and so on), <code>.npmrc</code>, <code>.netrc</code>, <code>.pgpass</code>, <code>id_rsa</code>, <code>id_ed25519</code>, <code>*.pem</code>, <code>*.key</code>, <code>*.p12</code>, <code>**/credentials.json</code>, <code>**/secrets.json</code>, <code>**/.aws/**</code>, <code>**/.ssh/**</code>, <code>**/.gcloud/**</code>, and <code>**/.kube/**</code>. Environment dumps are blocked too: <code>env</code>, <code>printenv</code>, <code>set</code>, <code>export</code>. The agent has no legitimate reason to read or echo any of these, ever.</p>
<p>The second is <strong>destructive Git operations</strong>. The agent can read Git history but can't rewrite or push it. Blocked: <code>git push</code>, <code>git commit</code>, <code>git revert</code>, <code>git cherry-pick</code>, <code>git merge</code>, <code>git rebase</code>, <code>git reset --hard</code>, <code>git tag</code>. Allowed: <code>git fetch</code>, <code>git status</code>, <code>git log</code>, <code>git diff</code>, <code>git show</code>, <code>git branch</code>, <code>git rev-parse</code>, <code>git merge-base</code>, <code>git config --get</code>.</p>
<p>The third is <strong>write operations on PRs and issues</strong>. The agent can read your GitHub state but can't act on it. Blocked: <code>gh pr create</code>, <code>gh pr edit</code>, <code>gh pr merge</code>, <code>gh pr close</code>, <code>gh pr comment</code>, <code>gh pr review</code>, <code>gh issue create</code>, <code>gh issue edit</code>, <code>gh issue close</code>, <code>gh issue comment</code>, <code>gh release create</code>, <code>gh repo create</code>, <code>gh repo edit</code>, <code>gh repo delete</code>. Allowed: <code>gh pr view</code>, <code>gh pr list</code>, <code>gh pr diff</code>, <code>gh pr checks</code>, <code>gh issue view</code>, <code>gh issue list</code>, <code>gh release view</code>.</p>
<p>The fourth is <strong>workflow and automation control</strong>. These are the surfaces where a compromised or misled agent could do the most damage. Blocked: <code>gh workflow run</code>, <code>gh run rerun</code>, <code>gh run cancel</code>, <code>gh secret</code>, <code>gh variable</code>, <code>gh auth</code>, <code>gh ssh-key</code>, <code>gh gpg-key</code>, and the unrestricted <code>gh api</code>.</p>
<p>For shell commands the agent legitimately needs to run, like build and test commands, allowlist specific patterns: <code>pnpm test</code>, <code>pnpm lint</code>, <code>pnpm format:check</code>, <code>pnpm build</code>, <code>pnpm vitest</code>. Anything outside the allowed list requires human confirmation. These are your own settings&nbsp;– I've just mentioned what I prefer.</p>
<p>The pattern is simple: read-only by default, write-allowed only for the specific commands you have explicitly approved. The agent can investigate, plan, and recommend. It can't ship.</p>
<p>With the structure in place and the guardrails set, the system started doing its job. What I didn't expect was how much better it would get over the months that followed.</p>
<h2 id="heading-the-compounding-loop-that-made-the-real-difference">The Compounding Loop That Made the Real Difference</h2>
<p>When we started, the AI reviewer was useful but not transformative. It caught some obvious issues, missed plenty of subtle ones, and produced a fair amount of noise.</p>
<p>The first month, my review burden dropped by 35%. The time I was spending on PR checking was reduced to 1/3, almost. Decent, not life-changing.</p>
<p>What changed over time wasn't the tool. It was the rules.</p>
<p>Every time a PR creator and reviewer caught something the AI had missed, we were adding bullets to the relevant rules file. Every time the AI flagged something useful that turned out to be a recurring pattern, the agent's own suggestion at the end of the review went into the file.</p>
<p>After a few days, the rules files had grown into something that captured a meaningful fraction of the team's collective review knowledge, written down in a place every agent on the team would read.</p>
<p>The catch rate went up. The noise went down because the rules also said what was acceptable and what we already considered solved. New engineers stopped getting the same comments on their first three PRs because the AI caught the comments first. Engineers joining the team didn't have to absorb the conventions through six months of review feedback. They installed the project, opened it in their editor, and the agent already knew.</p>
<p>This is the part most teams miss when they evaluate AI PR review tools. They look at the catch rate today and decide whether the tool is worth the price. The catch rate today isn't the right number. The right number is what the catch rate looks like in six months, after the rules file has absorbed every recurring mistake your team has made.</p>
<p>A single rule written down today saves a small amount of review time. Over a hundred PRs, it saves more. After a year, the rules file is a written-down version of a tech lead's accumulated taste. We've switched between Claude Code, the GitHub Copilot CLI, and Cursor for various tasks during this period. The AI tool changes, but the rules file in the repo stays the same.</p>
<p>The discipline that makes this work is treating the rules file as living documentation. Every recurring review comment is a candidate for promotion into the file. If you catch yourself typing the same feedback in two different PRs, that's a rule that belongs in <code>pr-rules/</code>. The "suggest a bullet" instruction in the review command is what makes this practical: the AI does the typing, the human does the deciding.</p>
<p>This is also what made me realise the system was worth the time it took to set up. The PR review command, on its own, is useful but unremarkable. The compounding loop is what turns it into infrastructure.</p>
<h2 id="heading-starting-from-zero-on-an-existing-project">Starting From Zero on an Existing Project</h2>
<p>If you've read this far and feel like the gap between your project and what I just described is a sprint of work, that's the most common reaction. It's also not correct.</p>
<p>The blank <code>AGENTS.md</code> is intimidating, especially on an existing codebase. You know your team has a thousand conventions, and writing a thousand rules sounds like a project that takes weeks before it produces any value.</p>
<p>The honest answer is that you can't write all the rules up front, and you shouldn't try. The first version of any of these files should take an afternoon, not a sprint.</p>
<p>Here's how I would actually start.</p>
<p>Run <code>/init</code> (or your tool's equivalent). In Claude Code, <code>/init</code> scans the project, infers the obvious shape (language, framework, entry points, build commands), and writes an initial <code>CLAUDE.md</code>. The output is a starting point, not a finished file. Read it, delete most of what it generates, and keep the bones.</p>
<p>Then add three things, each one bullet long.</p>
<p>First, an architecture rule. Pick the single most important convention your team enforces. For us, that was the four-layer pattern. The bullet was: "Controllers must not call repository functions directly. They must go through the app layer."</p>
<p>Second, a discoverability rule. Pick the single most important shared resource the team has, the one new code is most likely to duplicate. For us, that was the design system. The bullet was: "Before creating a new UI component, check <code>/src/design-system/</code> first."</p>
<p>Third, a "do not touch" rule. Pick the single most dangerous file or area in the codebase. Auth, billing, or migrations whichever has the most production risk. The bullet was: "Do not modify files in <code>/auth/</code> without human approval."</p>
<p>That's enough to start. Three rules, ten minutes of writing, and most of your team's recurring AI mistakes start to drop.</p>
<p>If even three rules feels like too much, start with one. Pick a single line that matters in your codebase and write it down.</p>
<p>"No <code>any</code> types in TypeScript." "Always use the enum, never compare against the string literal." "Run the linter before opening a PR." It doesn't have to be sophisticated. It doesn't have to cover edge cases. It just has to capture one piece of judgement that lives in your head today and would otherwise stay there.</p>
<p>Tomorrow, add another. The first week, you might catch 5% of the recurring mistakes. By 20 or 30 PRs in, you might catch 20-30%. The rules file doesn't need to be impressive on day one. It needs to exist and keep growing.</p>
<p>This is the compounding effect I'll come back to soon, and it's the reason this approach works on real projects rather than just in theory.</p>
<p>From there, the file grows the same way it would grow for any team. Every review catch becomes a candidate rule. After a few weeks, you have ten or fifteen rules. After a few months, you have a real review system.</p>
<p>The mistake is trying to write the perfect file on day one. The right file is the one you start with and keep editing.</p>
<h2 id="heading-what-still-needs-human-review">What Still Needs Human Review</h2>
<p>This system doesn't replace human review, and it shouldn't be allowed to.</p>
<p>The AI reviewer catches what the rules describe, plus a fair number of obvious things it would have spotted anyway. It doesn't catch problems that depend on context the rules don't capture. It doesn't catch product judgement. It doesn't catch the question of whether the change should have been built at all.</p>
<p>It also has an important blind spot when reviewing AI-authored code. The reviewer shares the same training data and reasoning patterns as the agent that wrote the code. If the original agent missed the v1/v2 distinction because it had no way to see the migration timeline, an AI reviewer reading the same diff has the same problem. Two AIs in a review loop are not two independent reviewers. They share blind spots.</p>
<p>That is why the AI reviewer in this setup never approves a PR. It produces a structured review that goes into the PR description. A human still reads the change and approves it. The AI is the first pass, not the gate.</p>
<p>Accountability also has to live with a human. When something the AI approved breaks production, someone has to own the post-mortem and decide what changes are needed for next time. The AI can't be that person. What it can do, well, is reduce the stack of small mistakes a human reviewer has to find before they get to the harder questions.</p>
<h2 id="heading-a-two-week-setup-plan">A Two-Week Setup Plan</h2>
<p>If you want to set this up for your own team, here's a concrete plan that fits in a couple of weeks. None of this needs to happen in a single push.</p>
<h3 id="heading-day-1-bootstrap-the-memory-file">Day 1: Bootstrap the memory file.</h3>
<p>Run <code>/init</code> (or your tool's equivalent) at the root of the project. Read the generated <code>CLAUDE.md</code> (or <code>AGENTS.md</code>). Delete most of it. Keep the tech stack and project structure sections.</p>
<p>Add the three rules from the previous section: one architecture rule, one discoverability rule, and one "do not touch" rule. Decide whether you want both files or a symlink.</p>
<h3 id="heading-day-2-add-per-service-files-for-your-highest-risk-areas">Day 2: Add per-service files for your highest-risk areas</h3>
<p>Pick the two or three areas of the codebase that change most often or carry the most risk. Add an <code>AGENTS.md</code> to each, following the same lean pattern. Include the architectural pattern for that area, the naming conventions, where to find good test examples, and pointers to any existing docs. Skip anything that doesn't need to be there yet.</p>
<h3 id="heading-day-3-set-up-the-directory-structure-and-guardrails">Day 3: Set up the directory structure and guardrails</h3>
<p>Create a <code>.claude/</code> folder (or your tool's equivalent) at the root, with <code>commands/</code> and <code>pr-rules/</code> subfolders. Add a <code>settings.json</code> with the deny list categories from the guardrails section. Test that the agent can't read a <code>.env</code> file, run <code>git push</code>, or create a PR. If any of those work, fix the settings before doing anything else.</p>
<h3 id="heading-day-4-write-the-pr-review-command">Day 4: Write the PR review command</h3>
<p>Adapt the command in this article to your structure. Include the diff scoping, the rule loading, the output format, and the "suggest a new rule" instruction at the end. Run it on a branch you've already merged, and tune the output until it's useful.</p>
<h3 id="heading-day-5-run-it-on-real-prs">Day 5: Run it on real PRs</h3>
<p>Have one or two engineers run the command on their next PRs before opening them. Read the output. Note what it caught, what it missed, and what was noise. Add the missing catches to the rules files. The first week is mostly tuning.</p>
<h3 id="heading-week-2-roll-out-and-document">Week 2: Roll out and document</h3>
<p>Once the command produces useful output reliably, ask the whole team to run it before opening PRs and paste the output into the PR description. Add a short section to your contributing guide explaining the workflow. Set a recurring item in your team's rituals to review the rules files monthly and trim anything that has gone stale.</p>
<p>That gets you to a working system. From there, the maintenance is incremental. Every recurring review comment becomes a candidate rule. Every architectural decision becomes a candidate update to the relevant <code>AGENTS.md</code>. The system improves as a side effect of the work the team is already doing.</p>
<h2 id="heading-what-is-working-what-i-am-still-improving">What Is Working, What I Am Still Improving</h2>
<p>Here's my honest assessment after a few months of running this:</p>
<h3 id="heading-whats-working">What's Working</h3>
<p>My review burden is meaningfully smaller. Engineers fix most of the easy mistakes before I see the PR. The "Verified" section of the AI's output tells me what to skip past. New engineers ramp faster because the conventions live in a place their tooling reads. The rules files have grown into something I would actually use to onboard someone new.</p>
<h3 id="heading-what-isnt-finished">What Isn't Finished</h3>
<p>The AI still misses problems that depend on context, and the rules don't capture them. The rules files grow, but they also need pruning, and we haven't been disciplined about that.</p>
<p>We're still figuring out how to handle rules that apply only conditionally. Docs are helping in that case, but we need to keep those up to date. And no system survives a determined engineer who skips the workflow or docs when they're in a rush.</p>
<p>There's no shortcut here. The work is real, ongoing, and mostly about discipline. The discipline is treating your codebase as something the AI needs to learn, and treating every recurring review comment as something that should be written down once instead of typed thirty times. If you're willing to do that, the tools take care of the rest.</p>
<p>If you take three things from this article, take these.</p>
<ol>
<li><p>First, don't pay for a generic reviewer to do a job your codebase needs to inform. Generic reviewers catch generic problems. Most of your real review work is specific to your team.</p>
</li>
<li><p>Second, put the rules in a file the AI reads, not in your head. <code>AGENTS.md</code>, <code>CLAUDE.md</code>, per-service files, per-area rules files. Pick a structure and stick to it.</p>
</li>
<li><p>Third, treat every human review catch as a chance to update the rules. The compounding effect over months is the entire point. A review system that improves itself is worth more than any single tool.</p>
</li>
</ol>
<p>That's the system. It took a couple of weeks to build the foundation and a few months for the rules to mature. It costs very little to run, and it has done more for our PR throughput than any tool I evaluated.</p>
<h2 id="heading-sources">Sources</h2>
<ul>
<li><p>CircleCI's 2026 State of Software Delivery report, analysing more than 28 million CI workflows from over 22,000 organisations: <a href="https://circleci.com/resources/2026-state-of-software-delivery/">https://circleci.com/resources/2026-state-of-software-delivery/</a></p>
</li>
<li><p>CircleCI's blog post detailing the year-over-year throughput numbers, including the 59% feature branch growth and the main branch decline: <a href="https://circleci.com/blog/five-takeaways-2026-software-delivery-report/">https://circleci.com/blog/five-takeaways-2026-software-delivery-report/</a></p>
</li>
<li><p>GitHub announcement of Copilot's transition to usage-based billing on June 1, 2026: <a href="https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/">https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/</a></p>
</li>
<li><p>GitHub changelog confirming Copilot code review will start consuming GitHub Actions minutes on June 1, 2026: <a href="https://github.blog/changelog/2026-04-27-github-copilot-code-review-will-start-consuming-github-actions-minutes-on-june-1-2026/">https://github.blog/changelog/2026-04-27-github-copilot-code-review-will-start-consuming-github-actions-minutes-on-june-1-2026/</a></p>
</li>
<li><p>AGENTS.md, the open standard's official site, including its stewardship under the Agentic AI Foundation and the Linux Foundation: <a href="https://agents.md/">https://agents.md/</a></p>
</li>
<li><p>Anthropic's Claude Code documentation on the memory system, including CLAUDE.md, auto memory, and the /init command: <a href="https://code.claude.com/docs/en/memory">https://code.claude.com/docs/en/memory</a></p>
</li>
<li><p>Anthropic's Claude Code GitHub Actions documentation, including notes on token-based billing and recommended cost controls: <a href="https://code.claude.com/docs/en/github-actions">https://code.claude.com/docs/en/github-actions</a></p>
</li>
<li><p>CodeRabbit's pricing documentation, confirming the per-developer-per-month seat model: <a href="https://docs.coderabbit.ai/management/plans">https://docs.coderabbit.ai/management/plans</a></p>
</li>
<li><p>Greptile's March 2026 pricing announcement, introducing the base-plus-usage model at $30 per seat per month with 50 included reviews: <a href="https://www.greptile.com/blog/greptile-v4">https://www.greptile.com/blog/greptile-v4</a></p>
</li>
<li><p>HumanLayer's write-up on writing a good CLAUDE.md, including data on instruction-following degradation: <a href="https://www.humanlayer.dev/blog/writing-a-good-claude-md">https://www.humanlayer.dev/blog/writing-a-good-claude-md</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How AI Changed the Economics of Writing Clean Code ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever wanted to add an interface to a codebase and gotten pushback, you already know the argument: "That's twice the code for the same thing." And honestly? It was a fair point. You'd write t ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-ai-changed-the-economics-of-writing-clean-code/</link>
                <guid isPermaLink="false">69f0bce210a70b3335bf635a</guid>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Code Quality ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aaron Yong ]]>
                </dc:creator>
                <pubDate>Tue, 28 Apr 2026 13:57:54 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/ecb13bda-70dd-437a-8d9a-4ef8b18ccc05.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever wanted to add an interface to a codebase and gotten pushback, you already know the argument: "That's twice the code for the same thing."</p>
<p>And honestly? It was a fair point. You'd write the contract — the interface, the abstract class, the protocol — and then write the implementation. Two files where one would do. That's more surface area, more indirection, and more to maintain.</p>
<p>The Ruby and Rails communities built an entire philosophy around this: convention over configuration, less ceremony, fewer keystrokes. If the framework could infer your intent, why spell it out?</p>
<p>Then AI happened.</p>
<p>I was recently chatting with a CEO about what current-generation software engineers get wrong, and he put it cleanly:</p>
<blockquote>
<p>"Abstract interfaces were challenging a few months ago just because it required twice as much code. But with AI, lines of code are free. The reason we still need such constructs is because at some point a human still needs to look at the code. Interfaces reduce the cognitive load."</p>
</blockquote>
<p>That framing stuck with me. The cost of writing code has collapsed. The cost of reading it hasn't moved. And that asymmetry changes everything about how you should think about abstraction.</p>
<p>Here's what I mean.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-your-brain-is-the-bottleneck">Your Brain Is the Bottleneck</a></p>
</li>
<li><p><a href="#heading-the-greats-already-knew-this">The Greats Already Knew This</a></p>
</li>
<li><p><a href="#heading-the-economics-have-flipped">The Economics Have Flipped</a></p>
</li>
<li><p><a href="#heading-the-data-backs-it-up">The Data Backs It Up</a></p>
</li>
<li><p><a href="#heading-the-contrarian-case-and-why-it-actually-agrees">The Contrarian Case (And Why It Actually Agrees)</a></p>
</li>
<li><p><a href="#heading-what-this-means-for-you">What This Means for You</a></p>
</li>
<li><p><a href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-your-brain-is-the-bottleneck">Your Brain Is the Bottleneck</h2>
<p>This isn't a vibes argument. There's actual neuroscience behind why interfaces help.</p>
<p>In 1988, educational psychologist John Sweller introduced Cognitive Load Theory. A <a href="https://dl.acm.org/doi/full/10.1145/3483843">2022 ACM review</a> covers how it's been applied to computing education since.</p>
<p>The short version: your brain juggles three types of load when processing information. <em>Intrinsic</em> load is the inherent difficulty of the problem itself. <em>Extraneous</em> load is the noise — poorly organized information, unnecessary details, bad naming. <em>Germane</em> load is the good stuff — the mental effort you spend building useful mental models.</p>
<p>Here's the kicker: your working memory can only hold a handful of chunks of information at a time — cognitive scientists typically estimate somewhere between 2 and 6. Not 2 to 6 files, or 2 to 6 classes — 2 to 6 <em>things</em>.</p>
<p>Felienne Hermans explores this in <em>The Programmer's Brain</em> (2021), arguing that design patterns act as chunking aids. When you recognize a Strategy pattern, your brain collapses an entire class hierarchy into a single cognitive unit. The word "Strategy" replaces five classes and their relationships. That's not hand-waving about clean code — that's how human memory actually works.</p>
<p>And we can literally see it on brain scans. In 2021, a team led by Norman Peitek and Janet Siegmund published <a href="https://dl.acm.org/doi/10.1109/ICSE43902.2021.00056">an fMRI study on program comprehension</a> that won the ACM SIGSOFT Distinguished Paper Award at ICSE.</p>
<p>They put developers in brain scanners and watched what happened when they read code. The finding: semantic-level comprehension — understanding <em>what</em> code does — required measurably less neural activation than bottom-up syntactic parsing — tracing <em>how</em> it does it.</p>
<p>An interface lets you comprehend at the semantic level. <code>UserRepository.findById(id)</code> tells you everything you need to know without opening the implementation. Your brain doesn't need to hold the SQL query, the connection pool logic, the error handling, and the result mapping in working memory simultaneously. The interface compresses all of that into one chunk.</p>
<p>That's not elegance. That's neuroscience.</p>
<h2 id="heading-the-greats-already-knew-this">The Greats Already Knew This</h2>
<p>The case for abstraction isn't new. The people who built the foundations of computer science were making this argument before most of us were born.</p>
<p>Dijkstra said it with precision:</p>
<blockquote>
<p><em>"The purpose of abstracting is not to be vague, but to create a new semantic level in which one can be absolutely precise."</em></p>
</blockquote>
<p>Abstraction isn't about hiding things from people who can't handle complexity. It's about creating a level of discourse where you can reason clearly.</p>
<p>David Parnas formalized information hiding in his <a href="https://dl.acm.org/doi/10.1145/361598.361623">1972 ACM paper</a>: <em>"Every module is characterized by its knowledge of a design decision which it hides from all others."</em> He proved that decomposing systems by design decisions (rather than processing steps) produced modules that were both more flexible <em>and</em> easier to understand. Comprehensibility wasn't a bonus — it was the design criterion.</p>
<p>Tony Hoare argued that abstraction is the most powerful tool available to the human intellect — a way to manage complexity by focusing on what matters and ignoring what doesn't. Martin Fowler brought it down to earth:</p>
<blockquote>
<p><em>"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."</em></p>
</blockquote>
<p>And then there's John Ousterhout, whose book <em>A Philosophy of Software Design</em> (2018) makes the connection to cognitive load explicit. His central argument: more lines of code can actually be <em>simpler</em> if they reduce cognitive load.</p>
<p>His concept of <em>deep modules</em> — simple interfaces hiding complex implementations — is essentially the argument that interfaces are worth their weight in code. The Unix file system API (<code>open</code>, <code>close</code>, <code>read</code>, <code>write</code>, <code>lseek</code>) is five functions hiding an enormous amount of complexity. That's a deep module. That's the goal.</p>
<p>The Gang of Four put it first in their book for a reason. Page one: <em>"Program to an interface, not an implementation."</em></p>
<p>None of this is controversial. But it's easy to forget when your AI tool just generated 200 lines of perfectly functional inline code in three seconds.</p>
<h2 id="heading-the-economics-have-flipped">The Economics Have Flipped</h2>
<p>Here's where the CEO's insight becomes an economic argument.</p>
<p>The historical case against interfaces was always about <em>writing cost</em>. Interfaces meant more code to write, more files to create, more boilerplate to maintain. The entire dynamic typing movement — Python, Ruby, JavaScript — was partly a reaction to the ceremony that languages like Java imposed. Convention over configuration. Don't Repeat Yourself. Less is more.</p>
<p>But ask yourself: what exactly is the cost of writing boilerplate now?</p>
<p>GitHub's <a href="https://arxiv.org/abs/2302.06590">2022 controlled study</a> found that developers using Copilot completed tasks 55% faster. The boilerplate that used to justify skipping interfaces — the extra file, the type definitions, the method signatures — takes seconds to generate. The writing cost of an interface has effectively collapsed to zero.</p>
<p>But again, the reading cost hasn't budged.</p>
<p>Robert C. Martin argued in <em>Clean Code</em> (2008) that developers spend far more time reading code than writing it — an observation he framed as a ratio of 10 to 1.</p>
<p>You can quibble with the exact number (it's anecdotal), but the direction is consistent across studies. A <a href="https://ieeexplore.ieee.org/document/7997917/">large-scale field study</a> tracking 78 professional developers across 3,148 working hours found they spend roughly 58% of their time on program comprehension alone. New developer onboarding averages six weeks — most of which is spent understanding existing systems, not producing new ones.</p>
<p>Addy Osmani named this asymmetry perfectly. In a <a href="https://addyosmani.com/blog/comprehension-debt/">March 2026 piece</a>, he described <em>comprehension debt</em>:</p>
<blockquote>
<p>"When a developer on your team writes code, the human review process has always been a bottleneck — but a productive and educational one. Reading their PR forces comprehension. AI-generated code breaks that feedback loop. The volume is too high."</p>
</blockquote>
<p>The output looks clean, passes linting, follows conventions — precisely the signals that historically triggered merge confidence. But comprehension debt is distinct from technical debt because it accumulates invisibly — your velocity metrics, your DORA scores, your PR counts all look fine while your team's actual understanding of the codebase quietly erodes.</p>
<p>So here's the math: AI reduced the cost of writing abstractions to near zero. The cost of <em>not</em> having them — in human reading time, onboarding friction, and comprehension debt — hasn't changed at all. The break-even point for "is this interface worth it?" just shifted massively in favor of "yes."</p>
<h2 id="heading-the-data-backs-it-up">The Data Backs It Up</h2>
<p>This isn't theoretical. We have data on what happens when AI generates code without good abstractions.</p>
<p><a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research">GitClear analyzed 211 million changed lines of code</a> between 2020 and 2024. Their findings: code churn — lines reverted or updated within two weeks — doubled compared to the pre-AI baseline. Copy-pasted code blocks rose from 8.3% to 12.3%. And refactoring-associated changes dropped from 25% to under 10%.</p>
<p>AI-generated code, as they put it, "resembles an itinerant contributor, prone to violate the DRY-ness of the repos visited."</p>
<p>The <a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">METR study</a> (2025) found something even more striking. Experienced open-source developers <em>predicted</em> AI would make them 24% faster. They <em>perceived</em> being 20% faster while using it. They were actually 19% slower. The perception gap is the story — you <em>feel</em> productive while generating code that creates more work downstream.</p>
<p>And then there's a study from Anthropic (yes, the company that makes Claude — full disclosure). They observed 52 software engineers learning a new library. The AI-assisted group completed tasks at the same speed, but scored <a href="https://arxiv.org/abs/2601.20245">17% lower on comprehension quizzes</a> afterward — 50% versus 67%. The biggest declines were in debugging ability. You can ship code you don't understand. You can't debug code you don't understand.</p>
<p>Kent Beck <a href="https://tidyfirst.substack.com/p/90-of-my-skills-are-now-worth-0">put it bluntly</a>: "The value of 90% of my skills just dropped to $0. The leverage for the remaining 10% went up 1000x." What that remaining 10% is, he leaves deliberately open — but it's hard to read that and not think about system design.</p>
<h2 id="heading-the-contrarian-case-and-why-it-actually-agrees">The Contrarian Case (And Why It Actually Agrees)</h2>
<p>I'd be dishonest if I didn't address the people who argue against abstraction. And some of them are very smart.</p>
<p>Casey Muratori's <a href="https://www.computerenhance.com/p/clean-code-horrible-performance">"Clean Code, Horrible Performance"</a> demonstrated that polymorphism and virtual dispatch can make code 10 to 15 times slower than straightforward procedural alternatives.</p>
<p>His benchmark is real. If you're writing a game engine or a high-frequency trading system, abstract interfaces on your hot path will cost you.</p>
<p>Dan Abramov wrote <a href="https://overreacted.io/goodbye-clean-code/">"Goodbye, Clean Code"</a> after watching a premature abstraction make his codebase harder to modify:</p>
<blockquote>
<p><em>"My code traded the ability to change requirements for reduced duplication, and it was not a good trade."</em></p>
</blockquote>
<p>Sandi Metz <a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">put it more sharply</a>: <em>"Duplication is far cheaper than the wrong abstraction."</em></p>
<p>And Rich Hickey, in his talk <a href="https://www.infoq.com/presentations/Simple-Made-Easy/">"Simple Made Easy"</a>, draws the critical distinction: <em>simple</em> (not intertwined) is not the same as <em>easy</em> (familiar). Wrong abstractions <em>complect</em> — they braid concerns together rather than separating them.</p>
<p>Here's the thing: none of these are arguments against abstraction. They're arguments against <em>bad</em> abstraction.</p>
<p>Muratori's performance argument applies to hot paths in performance-critical systems — not to your REST API's service layer. Abramov and Metz argue against <em>premature</em> abstraction — pulling patterns out before you understand the domain. And Hickey's entire talk is a case <em>for</em> the right abstractions, the ones that genuinely decompose rather than complect.</p>
<p>The irony is that in an AI-assisted world, these arguments are <em>easier</em> to address. You can generate the explicit, unabstracted version first. Let it stabilize. Watch the patterns emerge. Then extract the abstraction — with AI handling the mechanical refactoring. The cost of the "duplicate first, abstract later" approach just dropped to near zero.</p>
<h2 id="heading-what-this-means-for-you">What This Means for You</h2>
<p>If you're writing code with AI tools — and at this point, <a href="https://survey.stackoverflow.co/2024/ai">most of us are</a> — the temptation is to let the AI produce whatever it produces and move on. It works. It passes the tests. Ship it.</p>
<p>But "it works" is table stakes. The harder question is: can the next person who opens this code understand it in under five minutes? Can <em>you</em> understand it in six months?</p>
<p>Interfaces aren't about making code prettier or satisfying some abstract (pun intended) design principle. They're compression algorithms for human cognition. They let your brain operate at the semantic level instead of the syntactic level. And now that AI has eliminated the only real cost of creating them — the boilerplate — there's no economic argument left for skipping them.</p>
<p>The rules haven't changed. The excuse has just expired.</p>
<h2 id="heading-references">References</h2>
<h3 id="heading-academic-papers">Academic Papers</h3>
<ul>
<li><p>Duran, R., Zavgorodniaia, A., &amp; Sorva, J. (2022). <a href="https://dl.acm.org/doi/full/10.1145/3483843">"Cognitive Load Theory in Computing Education Research: A Review."</a> <em>ACM Transactions on Computing Education, 22</em>(4), Article 40.</p>
</li>
<li><p>Parnas, D.L. (1972). <a href="https://dl.acm.org/doi/10.1145/361598.361623">"On the Criteria To Be Used in Decomposing Systems into Modules."</a> <em>Communications of the ACM, 15</em>(12), 1053–1058.</p>
</li>
<li><p>Peitek, N., Apel, S., Parnin, C., Brechmann, A., &amp; Siegmund, J. (2021). <a href="https://dl.acm.org/doi/10.1109/ICSE43902.2021.00056">"Program Comprehension and Code Complexity Metrics: An fMRI Study."</a> <em>ICSE 2021</em>. ACM SIGSOFT Distinguished Paper Award.</p>
</li>
<li><p>Peng, S., Kalliamvakou, E., Cihon, P., &amp; Demirer, M. (2023). <a href="https://arxiv.org/abs/2302.06590">"The Impact of AI on Developer Productivity: Evidence from GitHub Copilot."</a> <em>arXiv:2302.06590</em>.</p>
</li>
<li><p>Shen, J.H. &amp; Tamkin, A. (2026). <a href="https://arxiv.org/abs/2601.20245">"How AI Impacts Skill Formation."</a> <em>arXiv:2601.20245</em>.</p>
</li>
<li><p>Xia, X., Bao, L., Lo, D., Xing, Z., Hassan, A.E., &amp; Li, S. (2018). <a href="https://ieeexplore.ieee.org/document/7997917/">"Measuring Program Comprehension: A Large-Scale Field Study with Professionals."</a> <em>IEEE Transactions on Software Engineering, 44</em>(10), 951–976.</p>
</li>
<li><p>METR. (2025). <a href="https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/">"Measuring the Impact of Early 2025 AI on Experienced Open Source Developer Productivity."</a> <em>metr.org</em>.</p>
</li>
</ul>
<h3 id="heading-talks-and-blog-posts">Talks and Blog Posts</h3>
<ul>
<li><p>Hickey, R. (2011). <a href="https://www.infoq.com/presentations/Simple-Made-Easy/">"Simple Made Easy."</a> <em>Strange Loop Conference</em>.</p>
</li>
<li><p>Beck, K. (2023). <a href="https://tidyfirst.substack.com/p/90-of-my-skills-are-now-worth-0">"90% of My Skills Are Now Worth $0."</a> <em>Tidy First? Substack</em>.</p>
</li>
<li><p>Osmani, A. (2026). <a href="https://addyosmani.com/blog/comprehension-debt/">"Comprehension Debt: The Hidden Cost of AI-Generated Code."</a> <em>addyosmani.com</em>.</p>
</li>
<li><p>Muratori, C. (2023). <a href="https://www.computerenhance.com/p/clean-code-horrible-performance">"Clean Code, Horrible Performance."</a> <em>Computer Enhance</em>.</p>
</li>
<li><p>Abramov, D. (2020). <a href="https://overreacted.io/goodbye-clean-code/">"Goodbye, Clean Code."</a> <em>overreacted.io</em>.</p>
</li>
<li><p>Metz, S. (2016). <a href="https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction">"The Wrong Abstraction."</a> <em>sandimetz.com</em>.</p>
</li>
<li><p>GitClear. (2025). <a href="https://www.gitclear.com/ai_assistant_code_quality_2025_research">"AI Assistant Code Quality in 2025."</a> <em>gitclear.com</em>.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ From Symptoms to Root Cause: How to Use the 5 Whys Technique ]]>
                </title>
                <description>
                    <![CDATA[ Most teams don't struggle because they can't fix problems. They struggle because they fix the wrong thing. An API fails in production. You restart the service, errors go away, and it feels resolved. U ]]>
                </description>
                <link>https://www.freecodecamp.org/news/from-symptoms-to-root-cause-how-to-use-the-5-whys-technique/</link>
                <guid isPermaLink="false">69ea4d69904b915438990f19</guid>
                
                    <category>
                        <![CDATA[ problem solving skills ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ debugging ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Thu, 23 Apr 2026 16:48:41 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/b5dbd964-9a03-448d-92a5-92e3b4a47fef.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most teams don't struggle because they can't fix problems. They struggle because they fix the wrong thing.</p>
<p>An API fails in production. You restart the service, errors go away, and it feels resolved. Until it happens again. And again. What's happening here is simple: you're treating symptoms, not the underlying cause.</p>
<p>The <strong>5 Whys technique</strong> is a straightforward way to deal with this. It comes from the Toyota Production System and was designed to help teams dig deeper into problems instead of settling for quick fixes.</p>
<p>The idea is simple. Ask "why" repeatedly until you reach the real cause.</p>
<p>But in practice, this is where things go wrong.</p>
<p>Teams often:</p>
<ul>
<li><p>Stop too early</p>
</li>
<li><p>Assume answers without checking data</p>
</li>
<li><p>Focus on people instead of systems</p>
</li>
<li><p>Treat "five" as a rule instead of a guideline</p>
</li>
</ul>
<p>So even though the process looks structured, the outcome is still shallow.</p>
<p>In this article, we'll focus on how to actually use the 5 Whys in real situations. Not just the theory, but what it looks like when you apply it to an engineering problem.</p>
<h3 id="heading-heres-what-well-cover">Here's What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-what-is-the-5-whys-technique">What is the 5 Whys Technique?</a></p>
</li>
<li><p><a href="#heading-origins-of-the-5-whys-method">Origins of the 5 Whys Method</a></p>
</li>
<li><p><a href="#heading-how-to-conduct-an-effective-5-whys-analysis">How to Conduct an Effective 5 Whys Analysis</a></p>
</li>
<li><p><a href="#heading-real-world-example-applying-5-whys-in-an-engineering-scenario">Real-World Example: Applying 5 Whys in an Engineering Scenario</a></p>
</li>
<li><p><a href="#heading-when-to-use-and-when-not-to-use-5-whys">When to Use (and When Not to Use) 5 Whys</a></p>
</li>
<li><p><a href="#heading-benefits-of-the-5-whys-technique">Benefits of the 5 Whys Technique</a></p>
</li>
<li><p><a href="#heading-common-pitfalls-and-limitations">Common Pitfalls and Limitations</a></p>
</li>
<li><p><a href="#heading-tips-for-using-5-whys-effectively">Tips for Using 5 Whys Effectively</a></p>
</li>
<li><p><a href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-what-is-the-5-whys-technique">What is the 5 Whys Technique?</h2>
<p>The 5 Whys technique is a way to break down a problem by repeatedly asking why it happened, with the goal of reaching a cause that actually explains the issue and can be addressed.</p>
<p>At its core, it's not about the number five. The name can be misleading. What matters is the process of following a chain of cause and effect until the explanation stops being superficial and starts becoming useful.</p>
<p>Each answer you uncover should move you one level deeper. You start with what went wrong, then explore what led to it, and continue until you reach something that is both believable and actionable. In most real situations, that final answer is not a single event but a gap in a system, a missing check, or an assumption that was never validated.</p>
<p>The technique became widely known through the Toyota Production System, where it was used to improve processes by focusing on causes rather than quick fixes.</p>
<p>That context is important because it highlights the original intent. The goal was not just to explain problems, but to prevent them from happening again.</p>
<p>A simple example makes this clearer. Imagine a mobile app suddenly starts crashing after a release. Asking "Why?" might look like this:</p>
<ol>
<li><p>Why is the app crashing? → Because a null value is being accessed in the code.</p>
</li>
<li><p>Why is there a null value? → Because the API response is missing a required field</p>
</li>
<li><p>Why is the field missing? → Because a recent backend change made the field optional.</p>
</li>
<li><p>Why was this change not handled in the app? → Because the app assumes the field is always present.</p>
</li>
<li><p>Why was this assumption not caught earlier? → Because there are no contract tests validating API responses.</p>
</li>
</ol>
<p>At this point, the issue is no longer just "fix the null check". The deeper problem is the lack of validation between systems, which allows breaking changes to slip through.</p>
<p>A useful way to think about the 5 Whys is that it forces you to stay with the problem a little longer than you normally would. Most of the time, the first explanation feels sufficient, so it's easy to stop there. This method pushes you to go one step further, and then another, until the explanation holds up under scrutiny.</p>
<p>At the same time, it's not a rigid formula. You might reach a solid root cause in three steps, or it might take more than five. The quality of the reasoning matters more than the count.</p>
<h2 id="heading-origins-of-the-5-whys-method">Origins of the 5 Whys Method</h2>
<p>The 5 Whys method comes from the Toyota Production System, a manufacturing approach focused on continuous improvement and problem solving at the source.</p>
<p>It's often associated with Sakichi Toyoda, whose philosophy was simple: don’t just fix a problem. Understand why it happened so it doesn't happen again.</p>
<p>Inside Toyota, this wasn't treated as a formal tool or checklist. It was part of the day-to-day way of working. When something went wrong on the production line, the goal wasn't to get things running quickly and move on. The goal was to stop, investigate, and make sure the same issue wouldn't repeat.</p>
<p>That mindset is important to understand. The 5 Whys was never meant to be a rigid exercise where you ask five questions and stop. It was a way to encourage deeper thinking and accountability in processes.</p>
<p>Another key idea in the Toyota system is that problems are usually caused by processes, not people. Instead of asking "who made the mistake", the focus is on "what allowed this mistake to happen". The 5 Whys fits naturally into this approach because it pushes you toward system level causes rather than individual blame.</p>
<p>Over time, the method spread beyond manufacturing and is now used in software engineering, product teams, operations, and many other fields. The context has changed, but the core idea remains the same: if you don't understand the cause, you're likely to see the same problem again.</p>
<p>This origin story is useful not just as background, but as a reminder of intent. The value of the 5 Whys doesn't come from the questions themselves. It comes from the discipline of not settling for the first answer.</p>
<h2 id="heading-how-to-conduct-an-effective-5-whys-analysis">How to Conduct an Effective 5 Whys Analysis</h2>
<p>A 5 Whys analysis works best when it is treated as a structured way of thinking, not a checklist to rush through. The quality of the outcome depends less on how many times you ask "why" and more on how carefully you reason through each step.</p>
<p>It helps to approach it in stages, each with a clear purpose.</p>
<h3 id="heading-step-1-define-the-problem-clearly">Step 1: Define the Problem Clearly</h3>
<p>Start with a problem statement that is specific and observable. Avoid vague descriptions like "the system is slow" or "things are failing". Instead, describe what actually happened in a way that can be verified.</p>
<p>For example, "API response time exceeded 5 seconds for 30 percent of requests between 2 PM and 3 PM" is much more useful than "API is slow".</p>
<p>A clear problem statement keeps the analysis grounded. If the starting point is fuzzy, the entire chain of reasoning will drift.</p>
<h3 id="heading-step-2-ask-why-iteratively">Step 2: Ask "Why" Iteratively</h3>
<p>Once the problem is defined, begin asking why it happened. Each answer should directly address the question before it and naturally lead to the next one.</p>
<p>The key here is continuity. Every step should feel like a logical extension of the previous one. If you find yourself jumping topics or introducing unrelated explanations, it's a sign that the chain is breaking.</p>
<p>Keep going until the answers stop being immediate symptoms and start pointing toward underlying conditions or decisions.</p>
<p>Also, don't force the process to stop at five. Some problems may need fewer steps, while others may need more. What matters is reaching a point where the explanation is meaningful and actionable.</p>
<h3 id="heading-step-3-validate-each-answer-with-evidence">Step 3: Validate Each Answer with Evidence</h3>
<p>This is where many analyses go wrong. It's easy to come up with plausible answers, but plausibility is not enough.</p>
<p>Each "why" should be backed by some form of evidence. This could be logs, metrics, recent changes, or direct observation. If an answer can't be verified, treat it as a hypothesis and confirm it before moving forward.</p>
<p>Without validation, the entire analysis becomes a chain of assumptions. Even if the final answer sounds reasonable, it may not reflect reality.</p>
<h3 id="heading-step-4-identify-the-root-cause">Step 4: Identify the Root Cause</h3>
<p>A good root cause is one that explains the sequence of events and can be acted upon to prevent the issue in the future.</p>
<p>In many cases, this turns out to be a gap in a process rather than a single technical failure. It could be a missing validation step, an incomplete test, or an assumption that was never challenged.</p>
<p>If the final answer still feels like a symptom, you probably need to go one level deeper. On the other hand, if the answer points to something you can change in your system or workflow, you are likely in the right place.</p>
<h3 id="heading-step-5-define-corrective-actions">Step 5: Define Corrective Actions</h3>
<p>The analysis is only useful if it leads to meaningful action.</p>
<p>Once you've identified the root cause, the next step is to define changes that prevent the problem from happening again. These should go beyond quick fixes and address the underlying issue.</p>
<p>For example, instead of just fixing a bug, you might introduce better testing, add monitoring, or improve review processes.</p>
<p>Good corrective actions share a few traits: they're specific, practical to implement, and they directly address the root cause identified in the analysis.</p>
<h2 id="heading-real-world-example-applying-5-whys-in-an-engineering-scenario">Real-World Example: Applying 5 Whys in an Engineering Scenario</h2>
<p>To see how this works in practice, let’s walk through a realistic backend issue. The goal here is not just to reach an answer, but to show how each step builds on evidence and leads to something actionable.</p>
<h3 id="heading-the-problem">The Problem:</h3>
<p>Users report intermittent failures while fetching order details:</p>
<pre><code class="language-bash">GET /api/orders/{id}
→ HTTP 500 Internal Server Error
</code></pre>
<p>Application logs show:</p>
<pre><code class="language-plaintext">// Java 21 example (Spring Boot style logging)
logger.error("Database connection timeout while fetching order", ex);
</code></pre>
<p>At this point, it's tempting to conclude that the database is the problem. But that's only what we can see on the surface.</p>
<h3 id="heading-applying-the-5-whys">Applying the 5 Whys</h3>
<h4 id="heading-1-why-did-the-api-return-a-500-error">1. Why did the API return a 500 error?</h4>
<p>Because the database query timed out.</p>
<p>This is directly supported by the error logs, so we can treat it as a confirmed fact.</p>
<h4 id="heading-2-why-did-the-query-time-out">2. Why did the query time out?</h4>
<p>Because the database connection pool was exhausted.</p>
<p>Metrics show that all available connections were in use during peak traffic.</p>
<h4 id="heading-3-why-was-the-connection-pool-exhausted">3. Why was the connection pool exhausted?</h4>
<p>Because some requests were holding database connections for too long.</p>
<p>Slow query logs confirm that a subset of queries had unusually high execution times.</p>
<h4 id="heading-4-why-were-some-queries-slow">4. Why were some queries slow?</h4>
<p>Because a recently introduced feature added a query on a non-indexed column.</p>
<p>Looking at recent deployments reveals a change that introduced filtering without proper indexing.</p>
<h4 id="heading-5-why-was-an-unoptimized-query-deployed-to-production">5. Why was an unoptimized query deployed to production?</h4>
<p>Because there is no performance validation step in the development or release process.</p>
<p>There are no checks in code review or CI/CD to catch inefficient database queries before deployment.</p>
<h3 id="heading-root-cause">Root Cause</h3>
<p>The issue is not the timeout itself.</p>
<p>It's this:</p>
<blockquote>
<p>The system allows inefficient database queries to reach production without any safeguards.</p>
</blockquote>
<img src="https://cdn.hashnode.com/uploads/covers/61c1acb4a90dea775da8262b/f93fb121-d5ac-45bc-8b3b-cc4f915c48a3.png" alt="f93fb121-d5ac-45bc-8b3b-cc4f915c48a3" style="display:block;margin:0 auto" width="423" height="544" loading="lazy">

<h3 id="heading-what-a-shallow-fix-would-look-like">What a Shallow Fix Would Look Like</h3>
<p>If we stopped early, we might:</p>
<ul>
<li><p>Increase the database timeout</p>
</li>
<li><p>Increase the connection pool size</p>
</li>
</ul>
<p>These might reduce the frequency of failures, but they don't solve the underlying problem.</p>
<h3 id="heading-what-a-strong-fix-looks-like">What a Strong Fix Looks Like</h3>
<p>A proper 5 Whys analysis leads to changes that improve the system:</p>
<ul>
<li><p>Add appropriate indexing for frequently queried fields</p>
</li>
<li><p>Introduce query performance checks in CI/CD pipelines</p>
</li>
<li><p>Add monitoring and alerts for slow queries</p>
</li>
<li><p>Include database considerations in code reviews</p>
</li>
</ul>
<h3 id="heading-why-this-example-matters">Why This Example Matters</h3>
<p>The difference between a shallow fix and a real solution is depth.</p>
<p>The first explanation often feels sufficient, especially under pressure. But stopping there means the issue is likely to return in a different form.</p>
<p>The value of the 5 Whys comes from following the chain all the way to something you can change in your system.</p>
<h2 id="heading-when-to-use-and-when-not-to-use-5-whys">When to Use (and When Not to Use) 5 Whys</h2>
<p>Like any problem-solving method, the 5 Whys is useful in the right context and less effective in others. Knowing when to apply it is just as important as knowing how to use it.</p>
<p>If used appropriately, it can uncover meaningful insights. If used in the wrong situation, it can lead to oversimplified or misleading conclusions</p>
<h3 id="heading-when-to-use-5-whys">When to Use 5 Whys</h3>
<p>The 5 Whys is most useful when your goal is to understand <strong>why something happened</strong>, not just to fix it and move on.</p>
<p>It works well in situations where problems are recurring or not fully explained by the first answer. For example, production incidents, repeated bugs, or issues that reappear after a quick fix are strong signals that you need deeper analysis. In these cases, the technique helps uncover what is happening beneath the surface.</p>
<p>It's also effective during retrospectives and postmortems. When a release doesn't go as expected or a sprint runs into issues, the 5 Whys helps teams move beyond observations like "this failed" and get to "why did this fail in the first place".</p>
<p>In general, use it when:</p>
<ul>
<li><p>The problem is not obvious</p>
</li>
<li><p>The issue has occurred more than once</p>
</li>
<li><p>You want to prevent recurrence, not just resolve the current instance</p>
</li>
</ul>
<h3 id="heading-when-not-to-use-5-whys">When Not to Use 5 Whys</h3>
<p>The 5 Whys has its limits, and using it in the wrong context can lead to oversimplified conclusions.</p>
<p>If a problem involves multiple interacting factors, a single chain of "why" questions may not capture the full picture. Complex systems often have several contributing causes, and forcing them into one linear explanation can hide important details. In such cases, the 5 Whys should be combined with other approaches.</p>
<p>It's also less effective when there's not enough data. If each answer is based on assumptions rather than evidence, the analysis quickly becomes unreliable. The method depends on validation at every step.</p>
<p>Another limitation is in time-critical situations. During an active incident, the priority is to restore the system. The deeper analysis should happen later, once things are stable.</p>
<p>Finally, if your goal is quantitative analysis or optimization, the 5 Whys alone isn't enough. You'll need more data-driven methods to support decision making.</p>
<p>A simple rule of thumb is this. If you are trying to <strong>learn from a problem</strong>, use the 5 Whys. If you are trying to <strong>fix something immediately or analyze complex data</strong>, use it carefully or alongside other techniques.</p>
<h2 id="heading-benefits-of-the-5-whys-technique">Benefits of the 5 Whys Technique</h2>
<p>The 5 Whys technique is simple, but it offers several powerful benefits that can help you solve problems more effectively and make lasting improvements. Here are the key advantages:</p>
<h3 id="heading-simple-and-easy-to-apply">Simple and Easy to Apply</h3>
<p>One of the biggest strengths of the 5 Whys is how easy it is to start using. You don't need special tools, training, or complex frameworks. It can be applied in a quick discussion, during debugging, or as part of a formal postmortem.</p>
<p>This low barrier makes it accessible across teams, regardless of experience level.</p>
<h3 id="heading-encourages-deeper-thinking">Encourages Deeper Thinking</h3>
<p>The method naturally pushes you to go beyond the first explanation. Instead of reacting to what's visible, it encourages you to question why the problem occurred in the first place.</p>
<p>This shift from surface-level fixes to deeper understanding often leads to better decisions.</p>
<h3 id="heading-promotes-system-level-improvements">Promotes System-Level Improvements</h3>
<p>When used correctly, the focus moves away from individual people and toward systems. Instead of asking who made a mistake, the analysis asks what allowed the mistake to happen.</p>
<p>This leads to improvements in processes, safeguards, and overall system design rather than one-off fixes.</p>
<h3 id="heading-works-well-in-team-settings">Works Well in Team Settings</h3>
<p>Because the approach is simple, it's easy for multiple people to contribute. Different perspectives help uncover gaps that might otherwise be missed.</p>
<p>It also creates a shared understanding of the problem, which is valuable during retrospectives and incident reviews.</p>
<h3 id="heading-helps-prevent-recurring-issues">Helps Prevent Recurring Issues</h3>
<p>Quick fixes often solve the immediate problem but don't stop it from happening again. The 5 Whys helps identify underlying causes, which makes it easier to prevent similar issues in the future.</p>
<p>Over time, this leads to more stable systems and fewer repeated incidents.</p>
<h2 id="heading-common-pitfalls-and-limitations">Common Pitfalls and Limitations</h2>
<p>While the 5 Whys technique is useful, it’s not always perfect. There are some limitations to keep in mind, so you can use it effectively and know when it might not be enough.</p>
<h3 id="heading-stopping-too-early">Stopping Too Early</h3>
<p>One of the most common mistakes is ending the analysis after the first or second answer. These early answers usually describe symptoms, not causes.</p>
<p>Stopping too soon leads to fixes that address the surface but leave the underlying issue unresolved.</p>
<h3 id="heading-treating-assumptions-as-facts">Treating Assumptions as Facts</h3>
<p>It's easy to come up with explanations that sound reasonable. But without evidence, they're just assumptions.</p>
<p>If each step isn't validated with logs, metrics, or observations, the entire analysis can drift away from reality.</p>
<h3 id="heading-focusing-on-individuals-instead-of-systems">Focusing on Individuals Instead of Systems</h3>
<p>Answers like "someone made a mistake" don't add much value. While they may be true, they don't explain why the system allowed that mistake to have an impact.</p>
<p>Focusing on processes and safeguards leads to more meaningful improvements.</p>
<h3 id="heading-oversimplifying-complex-problems">Oversimplifying Complex Problems</h3>
<p>The 5 Whys follows a linear chain of reasoning, but real-world systems often have multiple contributing factors.</p>
<p>Relying on a single chain can hide important interactions. In such cases, the method should be combined with other approaches.</p>
<h3 id="heading-treating-it-as-a-rigid-formula">Treating It as a Rigid Formula</h3>
<p>The name suggests asking "why" five times, but this shouldn't be taken literally. Some problems require fewer steps, while others need more.</p>
<p>Forcing the structure can lead to artificial or weak conclusions.</p>
<h3 id="heading-not-a-replacement-for-deeper-analysis">Not a Replacement for Deeper Analysis</h3>
<p>The 5 Whys isn't designed for every type of problem. For complex system failures, performance optimization, or data-heavy investigations, additional tools and methods are often required.</p>
<p>It works best as a starting point or a complement to other techniques, not a complete solution on its own.</p>
<h2 id="heading-tips-for-using-5-whys-effectively">Tips for Using 5 Whys Effectively</h2>
<p>To get the most out of the 5 Whys technique, there are a few tips that can help you use it effectively. These will guide you to ask the right questions and reach useful, actionable insights.</p>
<h3 id="heading-start-with-a-clear-specific-problem">Start with a Clear, Specific Problem</h3>
<p>A vague problem leads to vague answers. Spend a little extra time making sure the problem statement is precise and based on observable facts. This keeps the analysis grounded and avoids unnecessary detours.</p>
<h3 id="heading-base-every-step-on-evidence">Base Every Step on Evidence</h3>
<p>Treat each answer as something that needs to be verified. Use logs, metrics, recent changes, or direct observations to support your reasoning. If something can't be validated, call it out as a hypothesis and confirm it before moving forward.</p>
<h3 id="heading-keep-the-chain-logical-and-connected">Keep the Chain Logical and Connected</h3>
<p>Each "why" should naturally follow from the previous answer. If the reasoning starts to jump between unrelated ideas, pause and re-evaluate. A clean, logical chain is a strong indicator that you're on the right track.</p>
<h3 id="heading-focus-on-systems-not-individuals">Focus on Systems, Not Individuals</h3>
<p>Avoid stopping at explanations that point to human error. Instead, ask what allowed that error to have an impact. This shift in thinking leads to improvements that actually reduce the chances of similar issues in the future.</p>
<h3 id="heading-do-not-force-exactly-five-steps">Do Not Force Exactly Five Steps</h3>
<p>The number five is a guideline, not a rule. Some problems become clear in three steps, while others need more exploration. Stop when you reach a cause that's both convincing and actionable.</p>
<h3 id="heading-involve-the-right-people">Involve the Right People</h3>
<p>If possible, do the analysis as a group. People from different parts of the system bring different perspectives, which helps uncover details that might otherwise be missed. It also creates shared ownership of both the problem and the solution.</p>
<h3 id="heading-turn-insights-into-actions">Turn Insights into Actions</h3>
<p>The analysis only matters if it leads to change. Make sure the final outcome includes clear, practical steps that address the root cause. Without this, even a well-done analysis has limited impact.</p>
<h2 id="heading-summary">Summary</h2>
<p>The 5 Whys is a simple technique, but using it well takes some discipline.</p>
<p>At its core, it's about resisting the urge to stop at the first explanation. By following the chain of cause and effect, you move from symptoms to something you can actually fix. In many cases, that turns out to be a gap in a process rather than a one-off failure.</p>
<p>When applied thoughtfully, it helps teams learn from problems instead of just reacting to them. Over time, this leads to better systems, fewer recurring issues, and more confidence in how problems are handled.</p>
<p>The key is to treat it as a way of thinking, not just a set of steps.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Storyteller: A Medium For Guiding Others Through Code ]]>
                </title>
                <description>
                    <![CDATA[ As a computer science instructor, I have long wished that there was a better way to guide others through my code. When I was first learning to program, I was a big fan of traditional programming books ]]>
                </description>
                <link>https://www.freecodecamp.org/news/storyteller-a-medium-for-guiding-others-through-code/</link>
                <guid isPermaLink="false">69a23fd4d4053a09f35c3d3e</guid>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ code playbacks ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mark Mahoney ]]>
                </dc:creator>
                <pubDate>Sat, 28 Feb 2026 01:07:32 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5fc16e412cae9c5b190b6cdd/902c2299-ea98-4136-8ee8-36668f0c08ee.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As a computer science instructor, I have long wished that there was a better way to guide others through my code. When I was first learning to program, I was a big fan of traditional programming books. I have shelves and shelves of 800+ page books covering different programming languages and technologies.</p>
<p>I have known for a while now that most learners today don't share my love of big thick books, and to be honest, I rarely read those books in their entirety. Those big books often had a lot more exposition about the code than was probably needed. As a book buyer I wanted to make sure that I was getting my money's worth so the thicker they were, the better. It is much more common these days for learners to consume blog based tutorials and videos.</p>
<p>If you're learning to code right now, you've probably experienced the frustration of these formats too. I want to share something I've been working on that might help.</p>
<h2 id="heading-blogs-and-videos"><strong>Blogs and Videos</strong></h2>
<p>Blog-style tutorials mix code and the explanation of it in a top-to-bottom fashion. Scrolling through these web-based explanations feels familiar and one can copy and paste with ease. However, linking the explanation of the code and the code itself has always been less than ideal. Often I find myself jumping around the blog post wishing I could see the entire code example while working through the explanation. Instead, I am only able to see small parts of the code and it is challenging to see how those parts relate to other parts.</p>
<p>Video tutorials are very popular these days. They solve some of the problems associated with blog-style tutorials. Videos are great because you get two streams of information: the author's audible narrative and the code being written. A viewer can focus on the two streams simultaneously. However, videos have some problems too.</p>
<h3 id="heading-viewing-videos"><strong>Viewing Videos</strong></h3>
<p>From the perspective of the viewer, videos are hard to search through and are not useful as a copy and paste source or a code reference. More importantly, though, they discourage the viewer from taking their time and reflecting on the material. Often, when I am viewing a video tutorial I don't pause and let concepts sink in before the video moves on. Yes, I could be more disciplined and pause and rewind more often but usually I don't.</p>
<h3 id="heading-making-videos"><strong>Making videos</strong></h3>
<p>From the perspective of the video creator, it is clear that not all code being developed is interesting to watch. Some of it is not really worth showing the viewer. Not all video creators can keep the narrative interesting the whole time.</p>
<p>I know I struggle with the 'performance' aspect of making videos (you won't find me coding on Twitch anytime soon). Many times after I am done making a video, as I review it, I wish I had mentioned something that I forgot. It is hard to go back and edit the video without scrapping it and starting over.</p>
<h2 id="heading-storyteller"><strong>Storyteller</strong></h2>
<p>I have created a new medium to guide viewers through code examples. It combines the best of books, blog posts, and videos. This new medium allows a developer to write code using a top-notch editor (Visual Studio Code) and then replay the development of that code in the browser.</p>
<p>The author can add comments at important points in the evolution of the code. The comments can include text, hand drawn pictures, screenshots, and audio and video recordings. This allows the author to add visualizations that we have in our heads but don't make it into the code itself. The tool is called <a href="https://github.com/markm208/storyteller">Storyteller</a>.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/67df75cfc82238bba0f330b3/82dcb5c8-999f-432f-bd60-adcb3d8b9889.png" alt="82dcb5c8-999f-432f-bd60-adcb3d8b9889" style="display:block;margin:0 auto" width="3022" height="1638" loading="lazy">

<p>Here are a few examples of a 'playback':</p>
<ul>
<li><p><a href="https://playbackpress.com/books/pybook/chapter/2/10">Enlarging a Picture (Python)</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/cppbook/chapter/8/8">Dynamic Variables and Pointers (C++)</a></p>
</li>
</ul>
<p>These work best on a big screen. If you are viewing a playback on a small screen you can view it in 'blog' mode (there is button in the top right to switch from 'code' mode to 'blog' mode).</p>
<p>I have created groups of these guided code walk-throughs to help me teach different topics to my students. These are all free and hosted on a website I created called <a href="https://playbackpress.com/books">Playback Press</a>. Here are some of the 'books' I have created so far:</p>
<ul>
<li><p><a href="https://playbackpress.com/books/cppbook/">An Animated Introduction to Programming in C++</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/pybook/">An Animated Introduction to Programming with Python</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/webdevbook/">An Introduction to Web Development from Back to Front</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/cljbook/">An Animated Introduction to Clojure</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/exbook/">An Animated Introduction to Elixir</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/sqlbook/">Database Design and SQL for Beginners</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/flutterbook/">Mobile App Development with Dart and Flutter</a></p>
</li>
<li><p><a href="https://playbackpress.com/books/patternsbook/">OO Design Patterns with Java</a></p>
</li>
</ul>
<p>I usually assign these as readings in my classes instead of using expensive textbooks. It is a lot easier for me to write several programs than it is to find a perfect textbook.</p>
<p>I also use them for in-class demos instead of writing code live. This makes code demos flow much faster and smoother. If I make an interesting mistake while preparing the code I can still highlight it with a comment. If I make an uninteresting or embarrassing mistake I can just ignore it and the students won't focus on it.</p>
<h3 id="heading-the-advantages-of-code-playbacks"><strong>The Advantages of Code Playbacks:</strong></h3>
<ul>
<li><p>The primary focus is on the code. It is always visible and easy to search and navigate.</p>
</li>
<li><p>Since the code is so accessible, the explanation of it tends to be short and concise.</p>
</li>
<li><p>The narrative can include whiteboard style drawings, screenshots, or videos of running code in addition to a text explanation.</p>
</li>
<li><p>As an author, I can review the code several times and add/edit comments each time I go through it. I don't have to give a perfect performance like I do with a video.</p>
</li>
<li><p>Comment points highlight when the author wants the viewer to take a moment to really think about the code and reflect on it. The playback only moves forward when the viewer is ready.</p>
</li>
<li><p>The code mentioned in a comment can be highlighted so the viewer knows exactly where they should be looking.</p>
</li>
<li><p>The code can be downloaded at any point in the playback. Then a viewer can run it, change it, and add to it.</p>
</li>
<li><p>The tool is a language independent editor plug-in and can be used to describe programs in any language.</p>
</li>
<li><p>Viewers only need a web browser to go through a playback.</p>
</li>
</ul>
<p>Recently, I've been exploring how to make playbacks even more useful for learners.</p>
<h2 id="heading-ai-as-an-infinitely-patient-tutor"><strong>AI as an Infinitely Patient Tutor</strong></h2>
<p>I have extended code playbacks to include an AI tutor. One thing I've learned in my years of teaching is that students often hesitate to ask questions. They worry about looking foolish, or they don't want to slow down the class, or they simply can't articulate what's confusing them.</p>
<p>What if every student had access to a patient tutor who never got frustrated with repeated questions and could explain concepts in multiple ways until something clicked?</p>
<p>I've integrated AI directly into the playback experience. As students work through a playback, they can ask questions about anything they're seeing. This might be a specific line of code, a concept I mentioned in a comment, or how something connects to material from earlier in the playback. The AI has full context. It can see the code, it understands where the student is in the playback, and it can provide explanations tailored to that exact moment. The AI is right there <em>with</em> the student, looking at the same code, understanding the same context.</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/WAPql5KZFR4?si=jFnCqidSTtfaZA4e" frameborder="0" allowfullscreen="" title="Embedded content" loading="lazy"></iframe></div>

<p>The AI can also generate self-grading multiple choice questions based on the code and comments in a playback. These low-stakes quizzes make the learning experience more engaging and help learners check their understanding as they go.</p>
<p>Let me be clear: the AI doesn't replace me as an instructor. I still create the playbacks. I still decide what concepts to cover, what order to present them, and what examples best illustrate the ideas. The AI is an extension of my teaching, not a replacement for it.</p>
<p>Note: The AI features are available to registered users on <a href="https://playbackpress.com/books">Playback Press</a>. Registration is free but logging in is required to access the AI tutor. If you want to see what this feels like, try one of the playbacks linked above and ask the AI a question about what you're seeing.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>My goal has always been to help people learn to code. Books gave us depth but demanded commitment. Blogs gave us accessibility but fragmented the code. Videos gave us narrative but took away control. Playbacks keep the code front and center while letting learners move at their own pace and reflect when they need to. Adding AI doesn't change that philosophy, it just means there's always someone available to answer questions. Together, they get closer to the experience of having an expert sit beside you and walk you through a program. That's what I've been trying to build, and I think we're getting there.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the Singleton Design Pattern in Flutter: Lazy, Eager, and Factory Variations ]]>
                </title>
                <description>
                    <![CDATA[ In software engineering, sometimes you need only one instance of a class across your entire application. Creating multiple instances in such cases can lead to inconsistent behavior, wasted memory, or resource conflicts. The Singleton Design Pattern i... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-singleton-design-pattern-in-flutter-lazy-eager-and-factory-variations/</link>
                <guid isPermaLink="false">69740b7bc3e68b8de44a179f</guid>
                
                    <category>
                        <![CDATA[ Singleton Design Pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Object Oriented Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ood ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ flutter development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Factory Design Pattern ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mobile Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Fri, 23 Jan 2026 23:59:55 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769212761076/11d41d2a-8efa-4ddb-9ee2-218f5be00d9f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In software engineering, sometimes you need only one instance of a class across your entire application. Creating multiple instances in such cases can lead to inconsistent behavior, wasted memory, or resource conflicts.</p>
<p>The Singleton Design Pattern is a creational design pattern that solves this problem by ensuring that a class has exactly one instance and provides a global point of access to it.</p>
<p>This pattern is widely used in mobile apps, backend systems, and Flutter applications for managing shared resources such as:</p>
<ul>
<li><p>Database connections</p>
</li>
<li><p>API clients</p>
</li>
<li><p>Logging services</p>
</li>
<li><p>Application configuration</p>
</li>
<li><p>Security checks during app bootstrap</p>
</li>
</ul>
<p>In this article, we'll explore what the Singleton pattern is, how to implement it in Flutter/Dart, its variations (eager, lazy, and factory), and physical examples. By the end, you'll understand the proper way to use this pattern effectively and avoid common pitfalls.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-the-singleton-pattern">What is the Singleton Pattern?</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-when-to-use-the-singleton-pattern">When to Use the Singleton Pattern</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-singleton-class">How to Create a Singleton Class</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-eager-singleton">Eager Singleton</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lazy-singleton">Lazy Singleton</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-choosing-between-eager-and-lazy">Choosing Between Eager and Lazy</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-factory-constructors-in-the-singleton-pattern">Factory Constructors in the Singleton Pattern</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-factory-constructors">What Are Factory Constructors?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-implementing-singleton-with-factory-constructor">Implementing Singleton with Factory Constructor</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-when-not-to-use-a-singleton">When Not to Use a Singleton</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-singletons-can-be-problematic">Why Singletons Can Be Problematic</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-scenarios-where-you-should-avoid-singletons">Scenarios Where You Should Avoid Singletons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-general-guidelines">General Guidelines</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before diving into this tutorial, you should have:</p>
<ol>
<li><p>Basic understanding of the Dart programming language</p>
</li>
<li><p>Familiarity with Object-Oriented Programming (OOP) concepts, particularly classes and constructors</p>
</li>
<li><p>Basic knowledge of Flutter development (helpful but not required)</p>
</li>
<li><p>Understanding of static variables and methods in Dart</p>
</li>
<li><p>Familiarity with the concept of class instantiation</p>
</li>
</ol>
<h2 id="heading-what-is-the-singleton-pattern">What is the Singleton Pattern?</h2>
<p>The Singleton pattern is a creational design pattern that ensures a class has only one instance and that there is a global point of access to the instance.</p>
<p>Again, this is especially powerful when managing shared resources across an application.</p>
<h3 id="heading-when-to-use-the-singleton-pattern">When to Use the Singleton Pattern</h3>
<p>You should use a Singleton when you are designing parts of your system that must exist once, such as:</p>
<ol>
<li><p>Global app state (user session, auth token, app config)</p>
</li>
<li><p>Shared services (logger, API client, database connection)</p>
</li>
<li><p>Resource heavy logic (encryption handlers, ML models, cache manager)</p>
</li>
<li><p>Application boot security (run platform-specific root/jailbreak checks)</p>
</li>
</ol>
<p>For example, in a Flutter app, Android may check developer mode or root status, while iOS checks jailbroken device state. A Singleton security class is a perfect way to enforce that these checks run once globally during app startup.</p>
<h2 id="heading-how-to-create-a-singleton-class">How to Create a Singleton Class</h2>
<p>We have two major ways of creating a singleton class:</p>
<ol>
<li><p>Eager Instantiation</p>
</li>
<li><p>Lazy Instantiation</p>
</li>
</ol>
<h3 id="heading-eager-singleton">Eager Singleton</h3>
<p>This is where the Singleton is created at load time, whether it's used or not.</p>
<p>In this case, the instance of the singleton class as well as any initialization logic runs at load time, regardless of when this class is actually needed or used. Here's how it works:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EagerSingleton</span> </span>{
  EagerSingleton._internal();
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> EagerSingleton _instance = EagerSingleton._internal();

  <span class="hljs-keyword">static</span> EagerSingleton <span class="hljs-keyword">get</span> instance =&gt; _instance;

  <span class="hljs-keyword">void</span> sayHello() =&gt; <span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello from Eager Singleton"</span>);
}

<span class="hljs-comment">//usage</span>
<span class="hljs-keyword">void</span> main() {
  <span class="hljs-comment">// Accessing the singleton globally</span>
  EagerSingleton.instance.sayHello();
}
</code></pre>
<h4 id="heading-how-the-eager-singleton-works">How the Eager Singleton Works</h4>
<p>Let's break down what's happening in this implementation:</p>
<p>First, <code>EagerSingleton._internal()</code> is a private named constructor (notice the underscore prefix). This prevents external code from creating new instances using <code>EagerSingleton()</code>. The only way to get an instance is through the controlled mechanism we're about to define.</p>
<p>Next, <code>static final EagerSingleton _instance = EagerSingleton._internal();</code> is the key line. This creates the single instance immediately when the class is first loaded into memory. Because it's <code>static final</code>, it belongs to the class itself (not any particular instance) and can only be assigned once. The instance is created right here, at declaration time.</p>
<p>The <code>static EagerSingleton get instance =&gt; _instance;</code> getter provides global access to that single instance. Whenever you call <code>EagerSingleton.instance</code> anywhere in your code, you're getting the exact same object that was created when the class loaded.</p>
<p>Finally, <code>sayHello()</code> is just a regular method to demonstrate that the singleton works. You could replace this with any business logic your singleton needs to perform.</p>
<p>When you run the code in <code>main()</code>, the class loads, the instance is created immediately, and <code>EagerSingleton.instance.sayHello()</code> accesses that pre-created instance to call the method.</p>
<h4 id="heading-pros">Pros:</h4>
<ol>
<li><p>This is simple and thread safe, meaning it's not affected by concurrency, especially when your app runs on multithreads.</p>
</li>
<li><p>It's ideal if the instance is lightweight and may be accessed frequently.</p>
</li>
</ol>
<h4 id="heading-cons">Cons:</h4>
<ol>
<li>If this instance is never used through the runtime, it results in wasted memory and could impact application performance.</li>
</ol>
<h3 id="heading-lazy-singleton">Lazy Singleton</h3>
<p>In this case, the singleton instance is only created when the class is called or needed in runtime. Here, a trigger needs to happen before the instance is created. Let's see an example:</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LazySingleton</span> </span>{
  LazySingleton._internal(); 
  <span class="hljs-keyword">static</span> LazySingleton? _instance;

  <span class="hljs-keyword">static</span> LazySingleton <span class="hljs-keyword">get</span> instance {
    _instance ??= LazySingleton._internal();
    <span class="hljs-keyword">return</span> _instance!;
  }

  <span class="hljs-keyword">void</span> sayHello() =&gt; <span class="hljs-built_in">print</span>(<span class="hljs-string">"Hello from LazySingleton"</span>);
}

<span class="hljs-comment">//usage </span>
<span class="hljs-keyword">void</span> main() {
  <span class="hljs-comment">// Accessing the singleton globally</span>
  LazySingleton.instance.sayHello();
}
</code></pre>
<h4 id="heading-how-the-lazy-singleton-works">How the Lazy Singleton Works</h4>
<p>The lazy implementation differs from eager in one crucial way: timing.</p>
<p>Again, <code>LazySingleton._internal()</code> is a private constructor that prevents external instantiation.</p>
<p>But notice that <code>static LazySingleton? _instance;</code> is declared as nullable and not initialized. Unlike the eager version, no instance is created at load time. The variable simply exists as <code>null</code> until it's needed.</p>
<p>The magic happens in the getter: <code>_instance ??= LazySingleton._internal();</code> uses Dart's null-aware assignment operator. This line says "if <code>_instance</code> is null, create a new instance and assign it. Otherwise, keep the existing one." This is the lazy initialization: the instance is only created the first time someone accesses it.</p>
<p>The first time you call <code>LazySingleton.instance</code>, <code>_instance</code> is null, so a new instance is created. Every subsequent call finds that <code>_instance</code> already exists, so it just returns that same instance.</p>
<p>The <code>return _instance!;</code> uses the null assertion operator because we know <code>_instance</code> will never be null at this point (we just ensured it's not null in the previous line).</p>
<p>This approach saves memory because if you never call <code>LazySingleton.instance</code> in your app, the instance never gets created.</p>
<h4 id="heading-pros-1">Pros:</h4>
<ol>
<li><p>Saves application memory, as it only creates what is needed in runtime.</p>
</li>
<li><p>Avoids memory leaks.</p>
</li>
<li><p>Is ideal for resource heavy objects while considering application performance.</p>
</li>
</ol>
<h4 id="heading-cons-1">Cons:</h4>
<ol>
<li>Could be difficult to manage in multithreaded environments, as you have to ensure thread safety while following this pattern.</li>
</ol>
<h3 id="heading-choosing-between-eager-and-lazy">Choosing Between Eager and Lazy</h3>
<p>Now that we've broken down these two major types of singleton instantiation, it's worthy of note that you'll need to be intentional while deciding whether to create a singleton the eager or lazy way. Your use case/context should help you determine what singleton pattern you need to apply during object creation.</p>
<p>As an engineer, you need to ask yourself these questions when using a singleton for object creation:</p>
<ol>
<li><p>Do I need this class instantiated when the app loads?</p>
</li>
<li><p>Based on the user journey, will this class always be needed during every session?</p>
</li>
<li><p>Can a user journey be completed without needing to call any logic in this class?</p>
</li>
</ol>
<p>These three questions will determine what pattern (eager or lazy) you should use to fulfill best practices while maintaining scalability and high performance in your application.</p>
<h2 id="heading-factory-constructors-in-the-singleton-pattern">Factory Constructors in the Singleton Pattern</h2>
<p>Applying factory constructors in the Singleton pattern can be powerful if you use them properly. But first, let's understand what factory constructors are.</p>
<h3 id="heading-what-are-factory-constructors">What Are Factory Constructors?</h3>
<p>A factory constructor in Dart is a special type of constructor that doesn't always create a new instance of its class. Unlike regular constructors that must return a new instance, factory constructors can:</p>
<ol>
<li><p>Return an existing instance (perfect for singletons)</p>
</li>
<li><p>Return a subclass instance</p>
</li>
<li><p>Apply logic before deciding what to return</p>
</li>
<li><p>Perform validation or initialization before returning an object</p>
</li>
</ol>
<p>The <code>factory</code> keyword tells Dart that this constructor has the flexibility to return any instance of the class (or its subtypes), not necessarily a fresh one.</p>
<h3 id="heading-implementing-singleton-with-factory-constructor">Implementing Singleton with Factory Constructor</h3>
<p>This allows you to apply initialization logic while your class instance is being created before returning the instance.</p>
<pre><code class="lang-dart"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FactoryLazySingleton</span> </span>{
  FactoryLazySingleton._internal();
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> FactoryLazySingleton _instance = FactoryLazySingleton._internal();

  <span class="hljs-keyword">static</span> FactoryLazySingleton <span class="hljs-keyword">get</span> instance =&gt; _instance;

  <span class="hljs-keyword">factory</span> FactoryLazySingleton() {
    <span class="hljs-comment">// Your logic runs here</span>
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Factory constructor called"</span>);
    <span class="hljs-keyword">return</span> _instance;
  }
}
</code></pre>
<h4 id="heading-how-the-factory-constructor-singleton-works">How the Factory Constructor Singleton Works</h4>
<p>This implementation combines aspects of both eager and lazy patterns with additional control.</p>
<p>The <code>FactoryLazySingleton._internal()</code> private constructor and <code>static final _instance</code> create an eager singleton. The instance is created immediately when the class loads.</p>
<p>The <code>static get instance</code> provides the traditional singleton access pattern we've seen before.</p>
<p>But the interesting part is the <code>factory FactoryLazySingleton()</code> constructor. This is a public constructor that looks like a normal constructor call, but behaves differently. When you call <code>FactoryLazySingleton()</code>, instead of creating a new instance, it runs whatever logic you've placed inside (in this case, a print statement), then returns the existing <code>_instance</code>.</p>
<p>This pattern is powerful because:</p>
<ol>
<li><p>You can log when someone tries to create an instance</p>
</li>
<li><p>You can validate conditions before returning the instance</p>
</li>
<li><p>You can apply configuration based on parameters passed to the factory</p>
</li>
<li><p>You can choose to return different singleton instances based on conditions</p>
</li>
</ol>
<p>For example, you might have different configuration singletons for development vs production:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">factory</span> FactoryLazySingleton({<span class="hljs-built_in">bool</span> isProduction = <span class="hljs-keyword">false</span>}) {
  <span class="hljs-keyword">if</span> (isProduction) {
    <span class="hljs-comment">// Apply production configuration</span>
    _instance.configure(productionSettings);
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Apply development configuration</span>
    _instance.configure(devSettings);
  }
  <span class="hljs-keyword">return</span> _instance;
}
</code></pre>
<h4 id="heading-pros-2">Pros</h4>
<ol>
<li><p>You can add logic before returning an instance</p>
</li>
<li><p>You can cache or reuse the same object</p>
</li>
<li><p>You can dynamically return a subtype if needed</p>
</li>
<li><p>You avoid unnecessary instantiation</p>
</li>
<li><p>You can inject configuration or environment logic</p>
</li>
</ol>
<h4 id="heading-cons-2">Cons</h4>
<ol>
<li><p>Adds slight complexity compared to simple getter access</p>
</li>
<li><p>The factory constructor syntax might confuse developers unfamiliar with the pattern</p>
</li>
<li><p>If overused with complex logic, it can make debugging harder</p>
</li>
<li><p>Can create misleading code where <code>FactoryLazySingleton()</code> looks like it creates a new instance but doesn't</p>
</li>
</ol>
<h2 id="heading-when-not-to-use-a-singleton">When Not to Use a Singleton</h2>
<p>While singletons are powerful, they're not always the right solution. Understanding when to avoid them is just as important as knowing when to use them.</p>
<h3 id="heading-why-singletons-can-be-problematic">Why Singletons Can Be Problematic</h3>
<p>Singletons create global state, which can make your application harder to reason about and test. They introduce tight coupling between components that shouldn't necessarily know about each other, and they can make it difficult to isolate components for unit testing.</p>
<h3 id="heading-scenarios-where-you-should-avoid-singletons">Scenarios Where You Should Avoid Singletons</h3>
<p>Avoid using the Singleton pattern if:</p>
<h4 id="heading-you-need-multiple-independent-instances">You need multiple independent instances</h4>
<p>If different parts of your app need their own separate configurations or states, singletons force you into a one-size-fits-all approach.</p>
<p>For example, if you're building a multi-tenant application where each tenant needs isolated data, a singleton would cause data to bleed between tenants.</p>
<p><strong>Alternative</strong>: Use dependency injection to pass different instances to different parts of your app. Each component receives the specific instance it needs through its constructor or a service locator.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Instead of singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserRepository</span> </span>{
  <span class="hljs-keyword">final</span> DatabaseConnection db;
  UserRepository(<span class="hljs-keyword">this</span>.db); 
}

<span class="hljs-comment">// Usage</span>
<span class="hljs-keyword">final</span> dbForTenantA = DatabaseConnection(tenantId: <span class="hljs-string">'A'</span>);
<span class="hljs-keyword">final</span> dbForTenantB = DatabaseConnection(tenantId: <span class="hljs-string">'B'</span>);
<span class="hljs-keyword">final</span> repoA = UserRepository(dbForTenantA);
<span class="hljs-keyword">final</span> repoB = UserRepository(dbForTenantB);
</code></pre>
<h4 id="heading-your-architecture-avoids-shared-global-state">Your architecture avoids shared global state</h4>
<p>Modern architectural patterns like BLoC, Provider, or Riverpod in Flutter specifically aim to avoid global mutable state. Singletons work against these patterns by reintroducing global state.</p>
<p><strong>Alternative</strong>: Use state management solutions designed for Flutter. Provider, Riverpod, BLoC, or GetX offer better ways to share data across your app while maintaining testability and avoiding tight coupling.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Using Provider instead of singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppConfig</span> </span>{
  <span class="hljs-keyword">final</span> <span class="hljs-built_in">String</span> apiUrl;
  AppConfig(<span class="hljs-keyword">this</span>.apiUrl);
}

<span class="hljs-comment">// Provide it at the top level</span>
<span class="hljs-keyword">void</span> main() {
  runApp(
    Provider&lt;AppConfig&gt;(
      create: (_) =&gt; AppConfig(<span class="hljs-string">'https://api.example.com'</span>),
      child: MyApp(),
    ),
  );
}

<span class="hljs-comment">// Access it anywhere in the widget tree</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyWidget</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">final</span> config = Provider.of&lt;AppConfig&gt;(context);

  }
}
</code></pre>
<h4 id="heading-it-forces-tight-coupling-between-unrelated-classes">It forces tight coupling between unrelated classes</h4>
<p>When multiple unrelated classes depend on the same singleton, they become indirectly coupled. Changes to the singleton affect all these classes, making the codebase fragile and hard to refactor.</p>
<p><strong>Alternative</strong>: Use interfaces and dependency injection. Define what behavior you need through an interface, then inject implementations. This way, classes depend on abstractions, not concrete singletons.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Define an interface</span>
<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Logger</span> </span>{
  <span class="hljs-keyword">void</span> log(<span class="hljs-built_in">String</span> message);
}

<span class="hljs-comment">// Implementation</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConsoleLogger</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Logger</span> </span>{
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> log(<span class="hljs-built_in">String</span> message) =&gt; <span class="hljs-built_in">print</span>(message);
}

<span class="hljs-comment">// Classes depend on the interface, not a singleton</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentService</span> </span>{
  <span class="hljs-keyword">final</span> Logger logger;
  PaymentService(<span class="hljs-keyword">this</span>.logger);

  <span class="hljs-keyword">void</span> processPayment() {
    logger.log(<span class="hljs-string">'Processing payment'</span>);
  }
}

<span class="hljs-comment">// Easy to test with mock</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockLogger</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Logger</span> </span>{
  <span class="hljs-built_in">List</span>&lt;<span class="hljs-built_in">String</span>&gt; logs = [];
  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> log(<span class="hljs-built_in">String</span> message) =&gt; logs.add(message);
}
</code></pre>
<h4 id="heading-you-need-clean-isolated-testing">You need clean, isolated testing</h4>
<p>Singletons maintain state between tests, causing test pollution where one test affects another. This makes tests unreliable and order-dependent.</p>
<p><strong>Alternative</strong>: Use dependency injection and create fresh instances for each test. Most testing frameworks support this pattern, allowing you to inject mocks or fakes easily.</p>
<pre><code class="lang-dart"><span class="hljs-comment">// Testable code</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderService</span> </span>{
  <span class="hljs-keyword">final</span> PaymentProcessor processor;
  OrderService(<span class="hljs-keyword">this</span>.processor);
}

<span class="hljs-comment">// In tests</span>
<span class="hljs-keyword">void</span> main() {
  test(<span class="hljs-string">'processes order successfully'</span>, () {
    <span class="hljs-keyword">final</span> mockProcessor = MockPaymentProcessor();
    <span class="hljs-keyword">final</span> service = OrderService(mockProcessor); 

  });
}
</code></pre>
<h3 id="heading-general-guidelines">General Guidelines</h3>
<p>Use singletons sparingly and only when you truly need exactly one instance of something for the entire application lifecycle. Good candidates include logging systems, application-level configuration, and hardware interface managers.</p>
<p>For most other cases, prefer dependency injection, state management solutions, or simply passing instances where needed. These approaches make your code more flexible, testable, and maintainable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The Singleton pattern is a powerful creational tool, but like every tool, you should use it strategically.</p>
<p>Overusing singletons can make apps tightly coupled, hard to test, and less maintainable.</p>
<p>But when used correctly, the Singleton pattern helps you save memory, enforce consistency, and control object lifecycle beautifully.</p>
<p>The key is understanding your specific use case and choosing the right implementation approach – whether eager, lazy, or factory-based – that best serves your application's needs while maintaining clean, testable code.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Vibe Coding Effectively as a Dev ]]>
                </title>
                <description>
                    <![CDATA[ It may seem like everyone is a vibe coder these days, and prompting seemed like it would become the new coding. But is this AI-generated code really deployable? Bragging on social media about a clever script is one thing, but pushing a vibe coded app... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-vibe-coding-effectively-as-a-dev/</link>
                <guid isPermaLink="false">6925deb0b459e862808eb04c</guid>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vibe coding ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Tue, 25 Nov 2025 16:52:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1764089459731/0122c0b7-08e2-434a-b5eb-518025401951.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>It may seem like everyone is a vibe coder these days, and prompting seemed like it would become the new coding. But is this AI-generated code really deployable?</p>
<p>Bragging on social media about a clever script is one thing, but pushing a vibe coded app to prod comes with many security risks.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758881769141/9bedc585-5608-4660-a304-bbb10f10b8f2.png" alt="Vibe-debug, vibe-refactor, and vibe-check" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>With so many AI dev tools out there now, <a target="_blank" href="https://www.freecodecamp.org/news/how-to-perform-code-reviews-in-tech-the-painless-way/">code reviews</a> become more critical than ever.</p>
<p>This article will explore what <strong>vibe coding</strong> means and how code reviews should adapt in the era of AI.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-vibe-coding">What is Vibe Coding?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-vibe-coding-in-practice">How to Implement Vibe Coding in Practice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-isnt-vibe-coded-output-production-ready">Why isn’t Vibe Coded Output Production Ready?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-context-gaps-are-the-first-crack">Context gaps are the first crack.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-those-gaps-lead-directly-to-integration-blind-spots">Those gaps lead directly to integration blind spots.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-most-serious-risk-is-security-by-omission">The most serious risk is security by omission.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-and-correctness-evidence-are-thin">Testing and correctness evidence are thin.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-operability-lags-behind">Operability lags behind.</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-guidelines-for-ai-code-reviews">Guidelines for AI Code Reviews</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-code-review-process-in-vibe-coding">Code Review Process in Vibe Coding</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-checklist-for-reviewing-ai-generated-code">Checklist for Reviewing AI Generated Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-work-effectively-with-ai-tools">How to Work Effectively with AI Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-is-vibe-coding">What is Vibe Coding?</h2>
<p>In early 2025, AI researcher <a target="_blank" href="https://x.com/karpathy">Andrej Karpathy</a> popularized the term vibe coding to describe a new way of development in which you “fully give in to the vibes” and let AI write code while you focus on high level intent.</p>
<p>A developer expresses their desired functionality in plain language, and an AI system (like an <a target="_blank" href="https://en.wikipedia.org/wiki/Large_language_model">LLM</a>) generates the source code to implement it.</p>
<p>This code-by-prompt approach allows even beginners to produce working code without deep knowledge of programming languages. Karpathy joked that with advanced IDE agents (like <a target="_blank" href="https://www.devtoolsacademy.com/blog/cursor-vs-windsurf/">Cursor’s</a> Composer mode), “I barely even touch the keyboard... I ‘Accept All’ always, I don’t read the diffs anymore... and it mostly works”.</p>
<p>So, vibe coding is coding by vibe and trusting AI to handle the heavy lifting.</p>
<h2 id="heading-how-to-implement-vibe-coding-in-practice">How to Implement Vibe Coding in Practice</h2>
<p>In practice, vibe coding usually involves using AI assistants and adapting your workflow to a more interactive, prompt-driven style.</p>
<p>Here’s an overview of how you can “vibe code” a project:</p>
<h3 id="heading-step-1-choose-an-ai-assistant">Step 1: Choose an AI assistant</h3>
<p>Select a development env that supports AI code generation. Popular choices include <a target="_blank" href="https://cursor.com/">Cursor</a> and <a target="_blank" href="https://github.com/features/copilot">GitHub Copilot</a>.</p>
<h3 id="heading-step-2-define-your-requirements">Step 2: Define your requirements</h3>
<p>Instead of writing boilerplate code, describe what you want to build. Provide AI with a specific prompt detailing functionality. The more <a target="_blank" href="https://www.philschmid.de/context-engineering">context</a> and detail you give, the better AI can fulfill your intent.</p>
<p>For example, when I ran an SEO inspection for my website, DevTools Academy, I used this prompt in Cursor:</p>
<blockquote>
<p>“Now, act as a senior product engineer and UX strategist. Evaluate and improve <a target="_blank" href="https://www.devtoolsacademy.com">https://www.devtoolsacademy.com</a> with a practical, no-fluff lens.</p>
<p>Scope:</p>
<ul>
<li><p>UX</p>
</li>
<li><p>SEO and technical SEO</p>
</li>
<li><p>Positioning and messaging</p>
</li>
<li><p>Copywriting and information architecture</p>
</li>
<li><p>What to add to stand out in the developer tools space.”</p>
</li>
</ul>
</blockquote>
<p>This prompt works well because it gives the AI a clear role, a defined scope, and a specific intent. AI knows it’s not just fixing SEO but also reviewing how the site communicates value to devs. That combination of clarity and context produces actionable insights instead of surface-level suggestions.</p>
<p>Below is a screenshot of that audit in progress and showing how I reviewed code, metadata, and UX recommendations side by side.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761258218099/91e93726-1a7d-4d1a-9839-531355037dfc.png" alt="cursor screenshot showing CodeRabbit reviewing a pull request with comments and summary." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>You can checkout the full code on my open source <a target="_blank" href="https://github.com/tyaga001/devtoolsacademy">blog</a> here and check out closed PRs. This will help you learn how I use all these coding agents on a production ready app.</p>
<h3 id="heading-step-3-review-the-code">Step 3: Review the code</h3>
<p>AI will produce initial code based on your prompt. Think of this as a prototype – it’s not perfect. Run the code and see how it behaves.</p>
<p>Let’s look at an example: here, CodeRabbit is reviewing one of my <a target="_blank" href="https://github.com/tyaga001/devtoolsacademy/pull/145">pull requests</a> on GitHub. I had pushed a small fix to sort blog posts correctly and make sure the RSS feed reflects the latest publish date. Within seconds, CodeRabbit analyzed the diff, understood the intent behind my change, and explained exactly what the new code does.</p>
<p>It pointed out that the fix now sorts posts before mapping them, uses the sorted data for both items and the lastBuildDate, and ensures proper chronological order throughout the feed.</p>
<p>It’s like having a senior reviewer who not only checks syntax but also validates logic and confirms that your reasoning holds up.</p>
<p><a target="_blank" href="https://github.com/tyaga001/devtoolsacademy/pull/145"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758879621613/95bee1e7-3953-4416-b48b-e844332be950.png" alt="GitHub pull request showing CodeRabbit review comments on code changes with highlighted fixes." class="image--center mx-auto" width="600" height="400" loading="lazy"></a></p>
<p>This is just a reminder to expect imperfections. Vibe coding embraces a <em>“code first, refine later”</em> mindset. This means you get a working version quickly, then iteratively improve it. You might go through a few cycles of prompt -&gt; code -&gt; test -&gt; tweak.</p>
<h3 id="heading-step-4-validate-debug-polish">Step 4: Validate, debug, polish</h3>
<p>Once AI generated code meets your expectations, do a final review.</p>
<p>Throughout the process, the core idea is that you collaborate with the AI. The AI agent serves as a coding assistant, making real-time suggestions, automating tedious boilerplate, and even generating entire modules on your behalf.</p>
<h2 id="heading-why-isnt-vibe-coded-output-production-ready">Why Isn’t Vibe Coded Output Production Ready?</h2>
<p>Vibe coding moves fast: you describe intent, the AI produces something that runs, and you’re off to the next prompt. What’s missing is the slow, unglamorous work that usually turns a draft into shippable software, like shared context, architectural alignment, verification, and documentation.</p>
<p>AI generates plausible code based on patterns it has seen. But it doesn’t understand your team’s history, your system’s constraints, or the implicit rules that keep everything coherent over time.</p>
<p>That mismatch shows up the moment a “works on my machine” demo meets a real codebase.</p>
<p>Let’s explore the common pitfalls of vibe-coded code, so you’ll know what to watch for. Then, in the checklist section below, I’ll outline practical strategies to address or prevent each issue.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758271815928/5f763a0f-2dda-4318-8c19-0c9e58447abe.png" alt="AI is Limited by Context" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-context-gaps-are-the-first-crack">Context gaps are the first crack.</h3>
<p>AI only sees what you show it, so it’s easy for it to make the right local choice and the wrong global one: duplicating logic that already exists, choosing defaults that conflict with prior decisions, or introducing functions that don’t respect domain boundaries.</p>
<p>The result is code that looks reasonable in isolation but collides with existing assumptions and conventions once integrated.</p>
<h3 id="heading-those-gaps-lead-directly-to-integration-blind-spots">Those gaps lead directly to integration blind spots.</h3>
<p>Drafts often ignore the lived details of your environment – shared utilities, cross-cutting concerns, configuration, deployment hooks, and operational policies. Interfaces may line up at a glance and still fail at runtime because the draft doesn’t fit how your system composes modules, handles errors, or manages state across services.</p>
<h3 id="heading-the-most-serious-risk-is-security-by-omission">The most serious risk is security by omission.</h3>
<p>AI rarely includes robust input validation, clear authentication and authorization paths, or rate limiting unless you spell it out. Secrets handling and logging tend to be superficial or missing. That leaves common exposure points like request handlers, job processors, and webhook endpoints without the checks that prevent injection, SSRF, mass assignment, or data exfiltration.</p>
<p>Even when the surface looks tidy, the absence of explicit security controls means you’re trusting defaults you didn’t choose.</p>
<h3 id="heading-testing-and-correctness-evidence-are-thin">Testing and correctness evidence are thin.</h3>
<p>Quality suffers in quieter ways, too. Beyond “it runs,” there’s little to demonstrate behavior across edge cases or to guard against regressions.</p>
<p>Performance and scalability remain unknowns: extra network calls, N+1 patterns, and quadratic loops sneak in because nobody measured them. Dependencies and environments drift as versions aren’t pinned, infrastructure isn’t declared, and configuration lives only in the author’s head, making behavior differ across machines and CI.</p>
<h3 id="heading-operability-lags-behind">Operability lags behind.</h3>
<p>A lack of metrics, missing health/readiness probes, and no runbook make failures harder to detect and slower to recover from. Add in data quality and compliance concerns (PII handling, encoding assumptions, transitive license obligations), and you have code that demos well but isn’t ready for production’s reliability, security, and audit demands.</p>
<p>In short, vibe-coded output accelerates drafting but skips the shared understanding and evidence that make software safe to ship.</p>
<p>Until those gaps are closed, it’s a prototype, not a release.</p>
<h2 id="heading-guidelines-for-ai-code-reviews">Guidelines for AI Code Reviews</h2>
<p>Your team should keep pre-AI engineering standards as the bar, including security, tests, readability, maintainability, performance, and docs. AI should change how fast you gather the evidence for those standards, not how much evidence you require. In other words, use AI to accelerate the path to your existing bar, never to lower it.</p>
<p>Using AI, you can generate code at speed. But if reviews take the same amount of time (or more time), you lose some of the benefit. The goal isn’t to relax standards, it’s to shorten the time to prove you met them. That means layering in automation (tests, static analysis, secret scans, SCA) and AI-assisted review to catch obvious issues quickly so human reviewers can focus on intent, architecture, and risk.</p>
<p>Well-used assistants can help here. For example, tools like CodeRabbit, GitHub Copilot PR Reviewer, Claude Code, Cursor’s Bugbot, Graphite’s AI Review, and Greptile can highlight potential bugs, security gaps, style deviations, and mismatched intent, and summarize diffs for faster context. Treat these as accelerators for your existing process, not as replacements for judgment.</p>
<h3 id="heading-code-review-process-in-vibe-coding">Code Review Process in Vibe Coding</h3>
<p>The fundamentals of good code reviews haven’t changed – and in fact, they’re more critical now.</p>
<p>Below are some key principles to maintain speed without sacrificing quality.</p>
<h4 id="heading-1-trust-but-verify">1. Trust, but verify.</h4>
<p>A reviewer usually assumes the author understands the system. With vibe-coded output, the “author” may be an AI with limited context. If something looks odd or unnecessary, question it. Run the code, add/execute tests, or ask the developer/AI for clarification on intent and constraints.</p>
<h4 id="heading-2-dont-let-reviews-become-a-bottleneck">2. Don’t let reviews become a bottleneck.</h4>
<p>Vibe coding generates code quickly. If human review takes as long as hand-writing the change, you’ve erased the gain.</p>
<p>Combat this by front-loading automation: run unit/integration tests, static analysis (lint/SAST), secret scans, SCA, and basic perf checks in CI to clear the noise. Then reviewers spend their time on design trade-offs, boundary cases, and risk. The balance is: high standards, faster evidence.</p>
<h4 id="heading-3-use-ai-code-reviews-wisely">3. Use AI code reviews wisely</h4>
<p>AI can help review code just as it helps generate it. Modern “pair reviewer” tools scan a PR and surface likely bugs, security issues, missing tests, or style violations in minutes plus give natural-language summaries of the change.</p>
<p>Tools you can consider include CodeRabbit, GitHub Copilot PR Reviewer, Claude Code, Cursor Bugbot, Graphite, and Greptile. Many integrate with the CLI/IDE and GitHub/GitLab to leave actionable comments.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758272500586/a9cc891f-ab1a-47d8-a607-a772cbaef2e0.png" alt="coderabbit CLI" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Think of them as fast first-pass reviewers that increase coverage and consistency across PRs.</p>
<h4 id="heading-4-human-judgment-is-still-irreplaceable">4. Human judgment is still irreplaceable.</h4>
<p>Even the best AI reviewer is an assistant. Keep humans accountable for correctness, security posture, architectural fit, and user impact. A healthy pattern is AI first-pass &gt; human second-pass that inspects invariants, failure modes, and long-term maintainability.</p>
<h4 id="heading-5-maintain-a-high-bar-for-quality">5. Maintain a high bar for quality.</h4>
<p>It’s tempting to accept “it runs” when an AI wrote it. Don’t. Stakeholders still expect software to be robust, secure, and maintainable. Keep DRY, readability, and testability standards. Insist on input validation, authZ checks where relevant, and sensible logging/metrics. If you can’t provide evidence that you met the bar, you haven’t met it.</p>
<h4 id="heading-6-educate-and-document">6. Educate and document</h4>
<p>When reviewers find bugs or security flaws in AI-generated code, capture the lesson.</p>
<p>Update internal guides with patterns like “When generating handlers, validate and bound inputs, add rate limits, log request IDs, avoid N+1 queries, and sanitize user-visible output.” Over time, bake these into prompts, templates, repo scaffolds, and CI checks so the next AI draft starts closer to done.</p>
<h2 id="heading-checklist-for-reviewing-ai-generated-code">Checklist for Reviewing AI Generated Code</h2>
<p>Before approving any vibe-coded change, make the standards explicit and verifiable. Use this checklist to confirm behavior, security, performance, integration, and documentation so the draft you got from AI becomes code you can safely ship.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1762510535966/85ea547a-f955-446b-9e22-965dc18f9e49.png" alt="Checklist for Reviewing AI Generated Code" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Here’s a checklist a human reviewer should go through before approving vibe-coded output:</p>
<h3 id="heading-1-define-the-codes-purpose-scope-amp-non-goals">1. Define the code’s purpose (scope &amp; non-goals).</h3>
<p>Be explicit about what this change does and does not do. Tie it to a user story/ticket and call out non-goals so “helpful” AI changes don’t creep in.</p>
<h3 id="heading-2-verify-x-and-y-behavior-and-edge-cases">2. Verify X and Y (behavior and edge cases).</h3>
<p>Be clear about what you’re verifying. For example, verify input parsing and pagination boundaries, verify that error paths return the correct status and body, and verify that database writes are idempotent. Run existing tests, add missing unit/integration tests, and reproduce edge inputs (empty, null, huge, unicode).</p>
<h3 id="heading-3-perform-code-quality-checks-readability-dry-refactor-needs">3. Perform code-quality checks (readability, DRY, refactor needs).</h3>
<p>AI often produces verbose or duplicated logic. Ensure names are meaningful, side effects are clearly stated, and duplication is removed or minimized. Run linters/formatters, collapse repetition, and extract helpers where they aid clarity.</p>
<h3 id="heading-4-analyze-organization-and-structure-make-sure-it-fits-the-architecture">4. Analyze organization and structure (make sure it fits the architecture).</h3>
<p>AI writes code in isolation. Confirm the change uses existing utilities, layers, and boundaries (domain/services/controllers/jobs). Check imports and module placement, avoid reinventing existing helpers, and align with repository conventions.</p>
<h3 id="heading-5-validate-inputs-and-assumptions-make-the-implicit-explicit">5. Validate inputs and assumptions (make the implicit explicit).</h3>
<p>List the assumptions the AI made (default locale/timezone, allowed ranges, required fields). Add schema validation (DTO/class validators/JSON Schema). Empty, null, over-max, non-ASCII, unexpected enum, malicious strings. And finally, enforce limits/timeouts.</p>
<h3 id="heading-6-perform-security-audits-minimum-pass">6. Perform security audits (minimum pass).</h3>
<ul>
<li><p><strong>AuthN/AuthZ:</strong> Confirm endpoint checks identity and authorization paths; deny-by-default.</p>
</li>
<li><p><strong>Inputs:</strong> Sanitize/validate inputs, prevent injection (SQL/NoSQL/command), and escape user-visible output.</p>
</li>
<li><p><strong>Secrets</strong>: No secrets in code/diff/logs, use env/secret manager, and rotate any test keys.</p>
</li>
<li><p><strong>Abuse controls:</strong> Add rate limits, size limits, and timeouts on network and disk operations. Run SAST/secret scan/SCA, and fix or justify findings.</p>
</li>
</ul>
<h3 id="heading-7-do-a-performance-evaluation-right-now-at-a-small-scale">7. Do a performance evaluation (right now, at a small scale).</h3>
<p>Look for N+1s, needless network calls, unbounded loops, quadratic sorts. Add a micro-benchmark or run a quick load test for hot paths. Set sensible cache/timeout/retry with jitter where applicable.</p>
<h3 id="heading-8-manage-dependencies-pin-justify-minimize">8. Manage dependencies (pin, justify, minimize).</h3>
<p>Review any new libraries. Are they necessary? Maintained? License compatible? Pin versions, add lockfiles, or remove unused transitive adds.</p>
<h3 id="heading-9-review-documentation-what-to-add-and-where">9. Review documentation (what to add and where).</h3>
<p>Ensure the docs are in line with the code. AI often changes some parts or adds code blocks at different places while resolving various issues. These changes might not make it into the docs.</p>
<h3 id="heading-10-observability-see-problems-early">10. Observability (see problems early).</h3>
<p>Use structured logs with request/trace IDs, key counters/timers (success/error/latency), health/readiness probes, and a basic dashboard or alert stub.</p>
<h3 id="heading-11-compliance-and-data-handling-when-applicable">11. Compliance and data handling (when applicable).</h3>
<p>Identify any personally identifiable information (PII), document collection/retention, ensure masking/redaction in logs, verify dependency licenses and data-residency constraints.</p>
<h2 id="heading-how-to-work-effectively-with-ai-tools">How to Work Effectively with AI Tools</h2>
<p>At this point, you can probably see why it’s very important to understand the actual skills involved in AI-assisted development.</p>
<p>There’s a pretty big difference between an experienced developer who uses AI tools to help them get more done, and a newbie who thinks AI can build the next Facebook or Google just with a simple prompt.</p>
<p>An inexperienced dev will ask AI something like "Hey, Build me Twitter and make no mistakes"</p>
<p>But an experienced developer who has a solid fundamentals might say say something like:</p>
<ul>
<li><p>"AI, we're building a Twitter replica. Use $SQL_Database, Use $Language, Avoid $Common_Pitfalls, Follow $Standard_Practices."</p>
</li>
<li><p>"The generated code is prone to X problem, implement this fix."</p>
</li>
<li><p>"Implementation of $X is flawed because of $Y, do $Z instead."</p>
</li>
</ul>
<p>So as you can see, you still need to know the how's and the why's and what depends on what. Often you’ll just need to make the changes manually, because it will be faster. And you don’t want to outsource the critical thinking part, which is the part that AI can't actually do.</p>
<p>LLMs are good at information retrieval. If you know nothing about what you’re looking for, then asking an AI isn’t going to be that helpful (or that reliable). But if you have an idea, some background knowledge/context, and the skills to verify AI’s responses, then it can be really helpful.</p>
<p>Last month, I shared in my <a target="_blank" href="https://bytesizedbets.com/">newsletter</a> how my current coding loop looks in practice.</p>
<p>I draft with Claude Code (or Copilot/Cursor), open a PR, and let an AI reviewer like CodeRabbit (or Copilot PR Reviewer / Cursor Bugbot or Greptile) do the first pass. CI runs tests and scans.</p>
<p>I repeat until everything’s green and the PR is ready to merge. It’s fast, but it’s still disciplined.</p>
<p>If you want to understand why this kind of workflow is becoming essential, read this article: <a target="_blank" href="https://bytesizedbets.com/p/era-of-ai-slop-cleanup-has-begun">Era of AI Slop Cleanup Has Begun</a>. I talk about what’s happening in AI-assisted engineering, where generating code is easy, but keeping it clean and production ready takes experience – and you must have good programming skills.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>AI-generated code can boost productivity – but production value still comes from software that is robust, secure, and maintainable.</p>
<p>Mindless code generation creates technical debt. But when you integrate AI thoughtfully, with guardrails, verification, tests, security checks, and documentation, you can go faster without lowering your standards.</p>
<p>That's it for this article. I hope you learned something new today.</p>
<p>If you have any questions about code reviews, engineering, startups, or business in general, please find me on Twitter: <a target="_blank" href="https://x.com/TheAnkurTyagi">@TheAnkurTyag</a>i. I’d be more than happy to discuss them.</p>
<h3 id="heading-want-to-read-more-interesting-articles-like-this">Want to read more interesting articles like this?</h3>
<p>You can read more about the latest dev tools like this one on my <a target="_blank" href="https://www.devtoolsacademy.com/">website</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Concurrency vs. Parallelism: What’s the Difference and Why Should You Care? ]]>
                </title>
                <description>
                    <![CDATA[ In software engineering, certain concepts appear deceptively simple at first glance but fundamentally shape the way we design and architect systems. Concurrency and parallelism are two such concepts that warrant careful examination. These terms are f... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/concurrency-vs-parallelism-whats-the-difference-and-why-should-you-care/</link>
                <guid isPermaLink="false">68f25bf491a578e35f37419a</guid>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ concurrency ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Parallel Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Wisdom Usa ]]>
                </dc:creator>
                <pubDate>Fri, 17 Oct 2025 15:08:36 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760622633358/ad43bbd8-116c-42eb-95b7-0ef70156983a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In software engineering, certain concepts appear deceptively simple at first glance but fundamentally shape the way we design and architect systems. Concurrency and parallelism are two such concepts that warrant careful examination.</p>
<p>These terms are frequently used interchangeably, even among experienced developers. But while they may sound similar and occasionally overlap in practice, they address distinctly different problems and serve separate architectural goals. Understanding this distinction is not just an academic exercise. It directly impacts how you build scalable, efficient systems.</p>
<p>Whether you’re developing a high-traffic web server, training complex machine learning models, or optimising application performance, a solid grasp of these concepts can mean the difference between a solution that merely functions and one that scales elegantly under real-world conditions.</p>
<p>This article provides a comprehensive breakdown of both concepts through visual analogies, practical examples, and technical implementations. By the end, you will be equipped to confidently apply these principles in your software projects.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-understanding-the-fundamental-concepts">Understanding the Fundamental Concepts</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-the-kitchen-analogy">The Kitchen Analogy</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-concurrency-looks-like-in-practice">What Concurrency Looks Like in Practice</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-python-example-implementing-concurrency-with-asyncio">Python Example: Implementing Concurrency with asyncio</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-what-parallelism-looks-like-in-practice">What Parallelism Looks Like in Practice</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-python-example-implementing-parallelism-with-multiprocessing">Python Example: Implementing Parallelism with multiprocessing</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-concurrency-vs-parallelism-a-detailed-comparison">Concurrency vs. Parallelism: A Detailed Comparison</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-when-to-use-each">When to Use Each</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-real-world-applications-and-use-cases">Real-World Applications and Use Cases</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-concurrency-in-production-systems">Concurrency in Production Systems</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-parallelism-in-production-systems">Parallelism in Production Systems</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hybrid-approaches">Hybrid Approaches</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-choosing-the-right-approach-for-your-problem">Choosing the Right Approach for Your Problem</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-common-pitfall-to-avoid">Common Pitfall to Avoid</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-distinction-matters-in-practice">Why This Distinction Matters in Practice</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-misconceptions-and-clarifications">Common Misconceptions and Clarifications</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-practical-implementation-strategies">Practical Implementation Strategies</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-when-implementing-concurrency">When Implementing Concurrency</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-implementing-parallelism">When Implementing Parallelism</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-tools-and-technologies-by-language">Tools and Technologies by Language</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-further-learning-resources">Further Learning Resources</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-understanding-the-fundamental-concepts">Understanding the Fundamental Concepts</h2>
<p>Before diving into implementations, let’s establish some clear definitions:</p>
<p><strong>Concurrency</strong> refers to the ability of a system to manage multiple tasks within overlapping time periods. It does not necessarily mean these tasks execute at the exact same instant. Rather, concurrency is about structuring a program to handle multiple operations by interleaving their execution, often on a single processor core.</p>
<p><strong>Parallelism</strong>, by contrast, involves the simultaneous execution of multiple tasks. This typically requires multiple CPU cores or processors working in tandem, with each handling a separate portion of the workload at the same time.</p>
<h3 id="heading-the-kitchen-analogy">The Kitchen Analogy</h3>
<p>Consider the process of cooking as a helpful mental model:</p>
<p>A concurrent kitchen employs a single chef who rapidly switches between preparing multiple dishes. The chef might chop vegetables for one dish, then stir a sauce for another, then return to the first dish to continue preparation. From an observer's perspective, it appears that multiple dishes are being prepared "at once," but in reality, the chef is performing one action at a time in rapid succession.</p>
<p>A parallel kitchen has multiple chefs, each working on different dishes simultaneously. One chef prepares the appetiser while another works on the main course, and a third handles dessert. True simultaneous work is happening across multiple workers.</p>
<p>Same kitchen, different strategies, different outcomes.</p>
<h2 id="heading-what-concurrency-looks-like-in-practice">What Concurrency Looks Like in Practice</h2>
<p><a target="_blank" href="https://mechdampiitb.github.io/cs751_shyamsundar/"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760010754774/e4563560-b9b2-42e0-8c69-5c052db2656c.png" alt="Visual comparison between concurrency and parallelism in task execution. On the left, a single CPU alternates between Task 1 and Task 2 (or Thread 1 and Thread 2), illustrating concurrency without true parallelism. On the right, two CPUs execute Task 1 and Task 2 simultaneously, illustrating both concurrency and parallelism." class="image--center mx-auto" width="866" height="643" loading="lazy"></a></p>
<p>Concurrency is fundamentally about task scheduling, coordination, and resource management. It enables a program to handle multiple operations by strategically interleaving their execution, whether on a single core or across multiple threads.</p>
<p>A practical example: when you stream a video on YouTube while your device downloads a file in the background and your messaging app checks for new messages, your CPU is rapidly context-switching between these tasks. Each task gets a slice of processing time, creating the illusion of simultaneous execution even on a single-core processor.</p>
<h3 id="heading-python-example-implementing-concurrency-with-asyncio">Python Example: Implementing Concurrency with asyncio</h3>
<p>To examine concurrency in more detail, we’ll create a simple application which gets data on various APIs asynchronously. This is an example of how Python’s library, asyncio, lets us spawn multiple network operations without blocking so we can effectively use the waiting time.</p>
<p>In this implementation, we’ll be simulating API calls to a weather service, a news service, and a user profile database. Pay attention to the fact that all three requests begin nearly at the same time, yet the program doesn’t wait until one of them is completed before it begins the next one.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> asyncio

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_data_from_api</span>(<span class="hljs-params">api_name, delay</span>):</span>
    print(<span class="hljs-string">f"Starting request to <span class="hljs-subst">{api_name}</span>..."</span>)
    <span class="hljs-keyword">await</span> asyncio.sleep(delay)  <span class="hljs-comment"># Simulates network I/O wait</span>
    print(<span class="hljs-string">f"Received response from <span class="hljs-subst">{api_name}</span>"</span>)
    <span class="hljs-keyword">return</span> <span class="hljs-string">f"Data from <span class="hljs-subst">{api_name}</span>"</span>

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_user_profile</span>(<span class="hljs-params">user_id</span>):</span>
    print(<span class="hljs-string">f"Fetching profile for user <span class="hljs-subst">{user_id}</span>..."</span>)
    <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">1.5</span>)
    print(<span class="hljs-string">f"Profile loaded for user <span class="hljs-subst">{user_id}</span>"</span>)
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"user_id"</span>: user_id, <span class="hljs-string">"name"</span>: <span class="hljs-string">"John Doe"</span>}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    <span class="hljs-comment"># All tasks start and are managed concurrently</span>
    results = <span class="hljs-keyword">await</span> asyncio.gather(
        fetch_data_from_api(<span class="hljs-string">"Weather API"</span>, <span class="hljs-number">2</span>),
        fetch_data_from_api(<span class="hljs-string">"News API"</span>, <span class="hljs-number">1</span>),
        fetch_user_profile(<span class="hljs-number">12345</span>)
    )
    print(<span class="hljs-string">"\nAll operations completed!"</span>)
    print(<span class="hljs-string">"Results:"</span>, results)

asyncio.run(main())
</code></pre>
<p><strong>What happens during execution:</strong></p>
<ol>
<li><p>All three async functions are initiated at approximately the same time.</p>
</li>
<li><p>The event loop manages their execution, switching between tasks when one is waiting (during <code>await</code> statements).</p>
</li>
<li><p>While one task waits for simulated I/O, the event loop allows other tasks to make progress.</p>
</li>
<li><p>The task with the shortest delay completes first, even though all were started together.</p>
</li>
<li><p>No task blocks the others, resulting in efficient use of the single thread.</p>
</li>
</ol>
<p><strong>Key insight:</strong> Concurrency optimises responsiveness and resource utilisation. It doesn’t inherently make individual tasks complete faster. Instead, it allows multiple tasks to make progress during the same time period, particularly when those tasks involve waiting for external resources.</p>
<h2 id="heading-what-parallelism-looks-like-in-practice">What Parallelism Looks Like in Practice</h2>
<p><a target="_blank" href="https://www.researchgate.net/figure/Parallelism-mechanism-in-OpenMP-with-multiple-CPU-threads_fig3_311454812"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760010925777/c85a1976-96da-4f74-abba-62d1d0489d60.png" alt="Diagram illustrating the execution timeline of a parallel region using OpenMP. It shows how threads are forked and joined, with timestamps at the fork (`t_f`) and join (`t_j`), individual thread creation (`t_{i,s}`) and completion (`t_{i,c}`), as well as allocated memory per CPU/thread." class="image--center mx-auto" width="850" height="460" loading="lazy"></a></p>
<p>Parallelism concerns itself with genuine simultaneous execution. This approach leverages multiple CPU cores or processors to divide work and execute portions concurrently in real time.</p>
<p>Parallelism shines when dealing with CPU-intensive operations such as mathematical computations, image processing, video rendering, or training deep learning models.</p>
<h3 id="heading-python-example-implementing-parallelism-with-multiprocessing">Python Example: Implementing Parallelism with multiprocessing</h3>
<p>To better understand parallel execution, we’re going to make a program that carries out intensive calculations in a set of cores of CPUs. The given example relies on Python and the multiprocessing module to create different processes that are executed on different processor cores.</p>
<p>To work with a sufficiently complex example, we’ll compute the sum of the squares of millions of numbers. In contrast to the concurrent code sample, where we were waiting to receive I/O, we are actually doing some CPU-intensive work. You’ll notice the reduction in the time taken to execute the work when it’s shared by a number of cores.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> multiprocessing <span class="hljs-keyword">import</span> Process, current_process
<span class="hljs-keyword">import</span> time

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">compute_heavy_task</span>(<span class="hljs-params">task_name, iterations</span>):</span>
    <span class="hljs-string">"""Simulates a CPU-intensive operation"""</span>
    process_name = current_process().name
    print(<span class="hljs-string">f"<span class="hljs-subst">{task_name}</span> started on <span class="hljs-subst">{process_name}</span>"</span>)

    <span class="hljs-comment"># Simulate CPU-bound work</span>
    result = <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(iterations):
        result += i ** <span class="hljs-number">2</span>

    time.sleep(<span class="hljs-number">1</span>)  <span class="hljs-comment"># Additional simulated work</span>
    print(<span class="hljs-string">f"<span class="hljs-subst">{task_name}</span> completed on <span class="hljs-subst">{process_name}</span>. Result: <span class="hljs-subst">{result}</span>"</span>)
    <span class="hljs-keyword">return</span> result

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    start_time = time.time()

    <span class="hljs-comment"># Create separate processes for each task</span>
    p1 = Process(target=compute_heavy_task, args=(<span class="hljs-string">"Task 1"</span>, <span class="hljs-number">10000000</span>))
    p2 = Process(target=compute_heavy_task, args=(<span class="hljs-string">"Task 2"</span>, <span class="hljs-number">10000000</span>))
    p3 = Process(target=compute_heavy_task, args=(<span class="hljs-string">"Task 3"</span>, <span class="hljs-number">10000000</span>))

    <span class="hljs-comment"># Start all processes (they run on separate CPU cores)</span>
    p1.start()
    p2.start()
    p3.start()

    <span class="hljs-comment"># Wait for all processes to complete</span>
    p1.join()
    p2.join()
    p3.join()

    end_time = time.time()
    print(<span class="hljs-string">f"\nAll tasks completed in <span class="hljs-subst">{end_time - start_time:<span class="hljs-number">.2</span>f}</span> seconds"</span>)
</code></pre>
<p><strong>What happens during execution:</strong></p>
<ol>
<li><p>Three separate processes are spawned, each allocated to available CPU cores.</p>
</li>
<li><p>Each process runs independently with its own memory space and Python interpreter.</p>
</li>
<li><p>All three CPU-intensive calculations execute truly simultaneously across multiple cores.</p>
</li>
<li><p>The total runtime is determined by the longest-running task, not the cumulative sum of all tasks.</p>
</li>
<li><p>On a multi-core system, this completes approximately three times faster than sequential execution.</p>
</li>
</ol>
<p><strong>Key insight:</strong> Parallelism achieves actual speedup by distributing computational workload across multiple processors. This directly reduces total execution time for CPU-bound operations.</p>
<h2 id="heading-concurrency-vs-parallelism-a-detailed-comparison">Concurrency vs. Parallelism: A Detailed Comparison</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Concurrency</td><td>Parallelism</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Core Definition</strong></td><td>Managing and coordinating multiple tasks within overlapping time periods</td><td>Executing multiple tasks simultaneously across multiple processors</td></tr>
<tr>
<td><strong>Primary Goal</strong></td><td>Improve structure, responsiveness, and resource efficiency</td><td>Increase raw computational throughput and speed</td></tr>
<tr>
<td><strong>CPU Utilization</strong></td><td>Can work on single or multiple cores through interleaving</td><td>Requires multiple cores or processors for true parallelism</td></tr>
<tr>
<td><strong>Execution Model</strong></td><td>Task switching and scheduling</td><td>Simultaneous execution across hardware</td></tr>
<tr>
<td><strong>Optimal Use Case</strong></td><td>I/O-bound operations (network requests, file operations, database queries)</td><td>CPU-bound operations (mathematical computations, data processing, rendering)</td></tr>
<tr>
<td><strong>Common Implementation Techniques</strong></td><td>Async/await patterns, threads, coroutines, event loops</td><td>Multiprocessing, GPU computing, and distributed computing frameworks</td></tr>
<tr>
<td><strong>Performance Characteristic</strong></td><td>Reduces idle time and improves throughput without necessarily speeding up individual tasks</td><td>Directly reduces execution time by dividing the work</td></tr>
<tr>
<td><strong>Typical Applications</strong></td><td>Web servers, REST APIs, GUI applications, chat systems, and real-time notifications</td><td>Video encoding, scientific simulations, machine learning training, big data analytics</td></tr>
<tr>
<td><strong>Resource Overhead</strong></td><td>Lower (shared memory, lightweight context switching)</td><td>Higher (separate memory spaces, inter-process communication costs)</td></tr>
</tbody>
</table>
</div><h3 id="heading-when-to-use-each">When to Use Each:</h3>
<p>Use <strong>concurrency</strong> when you want to handle more tasks effectively within the same time period, particularly when those tasks spend time waiting for external resources.</p>
<p>Use <strong>parallelism</strong> when you want to complete tasks faster by leveraging multiple processors to divide the computational workload.</p>
<h2 id="heading-real-world-applications-and-use-cases">Real-World Applications and Use Cases</h2>
<h3 id="heading-concurrency-in-production-systems">Concurrency in Production Systems</h3>
<h4 id="heading-1-web-servers-and-apis">1. Web Servers and APIs</h4>
<p>Modern web frameworks like Node.js, Django with async views, and FastAPI handle thousands of simultaneous client connections. Each request may involve database queries, external API calls, or file operations. Concurrency allows the server to handle new requests while waiting for I/O operations from previous requests to complete.</p>
<h4 id="heading-2-real-time-communication">2. Real-Time Communication</h4>
<p>Chat applications, collaborative editing tools, and live streaming platforms manage multiple simultaneous connections. Messages must be received, processed, and broadcast to multiple clients concurrently without blocking any single connection.</p>
<h4 id="heading-3-mobile-applications">3. Mobile Applications</h4>
<p>Mobile apps perform background synchronization, push notification handling, and data caching while maintaining a responsive user interface. The UI thread remains free while background operations proceed concurrently.</p>
<h4 id="heading-4-microservices-orchestration">4. Microservices Orchestration</h4>
<p>Service meshes coordinate multiple API calls to different microservices, aggregating results efficiently without waiting for each call to complete sequentially.</p>
<h3 id="heading-parallelism-in-production-systems">Parallelism in Production Systems</h3>
<h4 id="heading-1-machine-learning-and-ai">1. Machine Learning and AI</h4>
<p>Training neural networks involves massive matrix computations that can be distributed across multiple GPU cores or even multiple machines. Frameworks like TensorFlow and PyTorch automatically parallelise operations across available hardware.</p>
<h4 id="heading-2-big-data-processing">2. Big Data Processing</h4>
<p>Distributed computing frameworks such as Apache Spark, Hadoop, and Dask divide large datasets across cluster nodes. Each node processes its portion of the data in parallel, enabling analysis of petabyte-scale datasets.</p>
<h4 id="heading-3-media-processing">3. Media Processing</h4>
<p>Video transcoding, image batch processing, and audio rendering leverage multiple CPU cores or GPUs. Each frame or segment can be processed independently in parallel.</p>
<h4 id="heading-4-scientific-computing">4. Scientific Computing</h4>
<p>Computational physics simulations, genome sequencing, and climate modelling require enormous computational resources. Parallelism across supercomputer clusters enables these calculations to complete in reasonable time frames.</p>
<h4 id="heading-5-financial-modelling">5. Financial Modelling</h4>
<p>Risk analysis and portfolio optimisation involve running thousands of scenarios. Parallel processing allows these computations to execute simultaneously, providing results quickly enough for real-time decision making.</p>
<h3 id="heading-hybrid-approaches">Hybrid Approaches</h3>
<p>In practice, sophisticated systems frequently combine both paradigms. Consider a modern web application:</p>
<ol>
<li><p>The web server handles client requests concurrently (handling multiple users simultaneously).</p>
</li>
<li><p>Each request may trigger parallel data processing tasks (such as image resizing across multiple cores).</p>
</li>
<li><p>The database connection pool manages concurrent query execution.</p>
</li>
<li><p>Background job workers process tasks in parallel (such as sending emails or generating reports).</p>
</li>
</ol>
<p>This layered approach leverages the strengths of both concurrency and parallelism to create systems that are both responsive and computationally efficient.</p>
<h2 id="heading-choosing-the-right-approach-for-your-problem">Choosing the Right Approach for Your Problem</h2>
<p>Understanding which paradigm to apply requires analysing the nature of your workload:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>If Your Task Is...</td><td>Choose...</td><td>Reasoning</td></tr>
</thead>
<tbody>
<tr>
<td><strong>I/O-bound</strong> (waiting for network, disk, or database operations)</td><td><strong>Concurrency</strong></td><td>Maximises efficiency by allowing other work to proceed during wait times. The bottleneck is not CPU computation but external resource availability.</td></tr>
<tr>
<td><strong>CPU-bound</strong> (heavy mathematical computation, data processing, rendering)</td><td><strong>Parallelism</strong></td><td>Distributes computational load across multiple processors, directly reducing execution time. The bottleneck is CPU capacity.</td></tr>
<tr>
<td><strong>Mixed workload</strong> (both I/O operations and intensive computation)</td><td><strong>Concurrency + Parallelism</strong></td><td>Concurrent handling of I/O operations combined with parallel processing of CPU-intensive segments provides optimal performance.</td></tr>
<tr>
<td><strong>Many small, independent tasks</strong></td><td><strong>Concurrency</strong> (if I/O) or <strong>Parallelism</strong> (if CPU)</td><td>Choose based on whether tasks are waiting or computing.</td></tr>
<tr>
<td><strong>Few large, divisible computations</strong></td><td><strong>Parallelism</strong></td><td>Split each computation across cores for maximum speedup.</td></tr>
</tbody>
</table>
</div><h3 id="heading-common-pitfall-to-avoid">Common Pitfall to Avoid</h3>
<p>A frequent mistake is attempting to use threading for CPU-bound tasks in languages with a Global Interpreter Lock (like Python's CPython) and expecting parallel speedups. In such cases, threads provide concurrency but not true parallelism.</p>
<p>The GIL ensures only one thread executes Python bytecode at a time, leading to context-switching overhead without genuine parallel execution. For CPU-bound work in Python, multiprocessing or C extensions are necessary for true parallelism.</p>
<h2 id="heading-why-this-distinction-matters-in-practice">Why This Distinction Matters in Practice</h2>
<p>Grasping the difference between concurrency and parallelism extends beyond writing faster code. It fundamentally influences how you architect systems and make technological decisions:</p>
<p>First of all, choosing the appropriate execution model for each component of your system leads to cleaner, more maintainable code. You avoid over-engineering solutions or applying the wrong tool to a problem.</p>
<p>Understanding these concepts also prevents wasteful patterns such as spawning unnecessary processes for I/O-bound work or using single-threaded approaches for parallelizable computations. This directly translates to reduced infrastructure costs.</p>
<p>Systems designed with proper concurrency models also scale horizontally more effectively. Those leveraging parallelism appropriately utilise hardware resources fully as you scale vertically.</p>
<p>In addition, you’ll get some key performance optimisations by choosing the right approach. When profiling reveals bottlenecks, knowing whether to optimise for concurrency or parallelism guides your refactoring efforts in the right direction.</p>
<p>Beyond this, in cloud environments where you pay for compute resources, efficient use of concurrency and parallelism directly affects operational costs. An efficiently concurrent system might handle 10x the load on the same hardware compared to a poorly designed synchronous alternative.</p>
<p>And these concepts are fundamental to backend engineering, distributed systems, DevOps, machine learning engineering, and systems programming. They appear frequently in technical interviews and are essential for senior engineering roles.</p>
<h2 id="heading-common-misconceptions-and-clarifications">Common Misconceptions and Clarifications</h2>
<h3 id="heading-using-threads-automatically-gives-me-parallelism">"Using threads automatically gives me parallelism."</h3>
<p>In reality, threads enable concurrency but do not guarantee parallel execution. In systems with a Global Interpreter Lock (like CPython) or on single-core machines, threads run concurrently but not in parallel. True parallelism requires multiple CPU cores and mechanisms that avoid locking constraints.</p>
<h3 id="heading-parallelism-is-always-faster-than-sequential-execution">"Parallelism is always faster than sequential execution."</h3>
<p>In fact, parallelism introduces overhead, including process creation, inter-process communication, and data synchronisation costs. For small tasks or I/O-bound operations, this overhead can outweigh benefits. Parallelism shows gains when the computational work justifies the overhead.</p>
<h3 id="heading-concurrency-and-parallelism-are-mutually-exclusive">"Concurrency and parallelism are mutually exclusive."</h3>
<p>As you’ve learned, modern high-performance systems routinely combine both. A web server can handle requests concurrently, with each request triggering parallel processing. Understanding how to layer these approaches is key to building sophisticated systems.</p>
<h3 id="heading-more-threads-or-processes-always-mean-better-performance">"More threads or processes always mean better performance."</h3>
<p>Beyond a certain point, adding more threads or processes leads to diminishing returns and even performance degradation due to increased context switching and resource contention. The optimal number depends on workload characteristics and available hardware.</p>
<h3 id="heading-asyncawait-makes-my-code-run-faster">“Async/await makes my code run faster."</h3>
<p>Async/await improves efficiency for I/O-bound operations by reducing idle time, but it does not speed up CPU-bound computations. It changes how waiting is handled, not how quickly individual operations execute.</p>
<h2 id="heading-practical-implementation-strategies">Practical Implementation Strategies</h2>
<h3 id="heading-how-to-implement-concurrency">How to Implement Concurrency</h3>
<p>To introduce concurrency into your programs, first you’ll need to find where time is wasted. Blocking operations that are held waiting on external resources are the best candidates to be put under concurrent execution.</p>
<p>Say you’re building a web scraper to fetch a bunch of data on a variety of sites. Every single HTTP request is most likely waiting until the server gets a response back. Other requests might be underway in your program instead of waiting around during this waiting period. These wait points are identifiable by profiling your application and searching the operations with network calls, file I/O, or database queries.</p>
<p>After you’ve discovered these wait points, the next big step will be to select the concurrency primitive. In Python, I/O-bound operations perform very well using the patterns of async/await with the support of the asyncio framework. It also comes with a minimal cost.</p>
<p>Take a situation when you have to retrieve user data in a REST API and query a database at the same time. With asyncio, you can write code that initiates both tasks almost simultaneously, and then have the event loop alternate between them during periods of waiting.</p>
<p>Here's a practical example:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> asyncio
<span class="hljs-keyword">import</span> aiohttp

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fetch_user_api</span>(<span class="hljs-params">user_id</span>):</span>
    <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> aiohttp.ClientSession() <span class="hljs-keyword">as</span> session:
        <span class="hljs-keyword">async</span> <span class="hljs-keyword">with</span> session.get(<span class="hljs-string">f'https://api.example.com/users/<span class="hljs-subst">{user_id}</span>'</span>) <span class="hljs-keyword">as</span> response:
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> response.json()

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query_database</span>(<span class="hljs-params">user_id</span>):</span>
    <span class="hljs-comment"># Simulating database query</span>
    <span class="hljs-keyword">await</span> asyncio.sleep(<span class="hljs-number">0.5</span>)
    <span class="hljs-keyword">return</span> {<span class="hljs-string">'preferences'</span>: <span class="hljs-string">'theme:dark'</span>, <span class="hljs-string">'notifications'</span>: <span class="hljs-literal">True</span>}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_complete_user_data</span>(<span class="hljs-params">user_id</span>):</span>
    api_data, db_data = <span class="hljs-keyword">await</span> asyncio.gather(
        fetch_user_api(user_id),
        query_database(user_id)
    )
    <span class="hljs-keyword">return</span> {**api_data, **db_data}
</code></pre>
<p>This gives a thorough look at how concurrency works in practice.</p>
<h3 id="heading-when-implementing-parallelism">When Implementing Parallelism</h3>
<p>Before committing parallelism into your system, you’ll need to profile the system and make sure that what causes your bottleneck is CPU-bound computation. Many developers think that their code requires parallelism when it should actually employ concurrency.</p>
<p>You can use profiling tools such as Python cProfile or line profilers to determine where time is being used or wasted in your program. When the time spent in computational loops is as large as compared to waiting in I/O, then parallelism can be beneficial.</p>
<p>To take an example, when processing images, the execution time in pixel manipulation algorithms consumes 90% of the execution time. This is a good sign that parallelism would be useful.</p>
<p>Deciding how to partition the work between multiple processors is sometimes a complex issue that you should consider carefully (in terms of dividing tasks into independent points). These chunks should be able to be processed individually without needing to communicate with each other on a regular basis.</p>
<p>Imagine that you have to examine the log files of several servers. Processing each file may happen on a different core, and the results will get added at the final stage.</p>
<p>Here's how you might structure this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> multiprocessing <span class="hljs-keyword">import</span> Pool
<span class="hljs-keyword">import</span> re

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">analyze_log_file</span>(<span class="hljs-params">filepath</span>):</span>
    error_count = <span class="hljs-number">0</span>
    <span class="hljs-keyword">with</span> open(filepath, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> f:
        <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> f:
            <span class="hljs-keyword">if</span> re.search(<span class="hljs-string">r'ERROR|CRITICAL'</span>, line):
                error_count += <span class="hljs-number">1</span>
    <span class="hljs-keyword">return</span> filepath, error_count

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    log_files = [<span class="hljs-string">'server1.log'</span>, <span class="hljs-string">'server2.log'</span>, <span class="hljs-string">'server3.log'</span>, <span class="hljs-string">'server4.log'</span>]

    <span class="hljs-keyword">with</span> Pool(processes=<span class="hljs-number">4</span>) <span class="hljs-keyword">as</span> pool:
        results = pool.map(analyze_log_file, log_files)

    <span class="hljs-keyword">for</span> filepath, count <span class="hljs-keyword">in</span> results:
        print(<span class="hljs-string">f'<span class="hljs-subst">{filepath}</span>: <span class="hljs-subst">{count}</span> errors found'</span>)
</code></pre>
<p>In this example, each log file is processed entirely on one core without needing to communicate with other processes until the final result aggregation.</p>
<h2 id="heading-tools-and-technologies-by-language">Tools and Technologies by Language</h2>
<p>Various programming languages offer different methods of achieving concurrency and parallelism with their own advantages and disadvantages. And when you understand the available tools in your language of choice, you’ll be able to make a wise architectural choice.</p>
<h3 id="heading-python">Python</h3>
<p>Python has a concurrent and parallel environment. For concurrent programming, the asyncio library offers a more modern syntax of async/await that’s ideal in I/O-bound tasks such as web scraping or API communication.</p>
<p>The threading module allows shared memory execution, but is restricted on CPU-bound tasks by the Global Interpreter Lock. The concurrent futures module is a high-level interface to concurrent task execution, which can be useful when you want to parallelize I/O operations without having to write the low-level code of asynchronous operations.</p>
<p>Sometimes you’ll need actual parallelism because your job requires a lot of CPU time. Multiprocessing starts individual Python processes, which don’t use the GIL at all.</p>
<p>In the case of data science and machine learning processes, distributed parallelism is offered in libraries such as joblib, ray, and dask and can run on your laptop up to a cluster of computers.</p>
<h3 id="heading-javascript-and-nodejs">JavaScript and Node.js</h3>
<p>The event loop architecture had concurrency as its foundation in JavaScript and Node.js. Asynchronous programming is now intuitive with native syntax and Promises being used as the standard model of dealing with I/O operations (like HTTP requests or file system access).</p>
<p>JavaScript is single-threaded, and Node.js is designed to execute single-thread programs that make good use of I/O bound concurrent tasks, such as web servers, which support thousands of parallel connections.</p>
<p>In cases of actual parallelism (for example, image processing or cryptographic tasks), worker threads enable you to execute JavaScript on multiple cores. The child processes module can launch individual instances of Node.js, and the cluster module allows you to launch a pool of workers to accept incoming connections and make the most of all CPU cores in a web server.</p>
<h3 id="heading-java">Java</h3>
<p>Java has mature and battle-tested concurrency and parallelism support. CompletableFuture offers a fluent interface to asynchronous operations, so it’s easier to sequence dependent asynchronous tasks together without any callback hell.</p>
<p>The ExecutorService model also provides detailed management of thread pools and task scheduling, which is necessary in developing high performance server programs. Parallelism Java thread pools are effective at handling worker threads to execute CPU-bound tasks, whereas ForkJoinPool uses work-stealing algorithms that are useful in divide-and-conquer problems.</p>
<p>Java 8 offers parallel streams, which allow you to process collections in parallel with a minimal amount of code rewrites – but you have to pay close attention to when they actually will or will not improve performance.</p>
<h3 id="heading-go">Go</h3>
<p>Go introduced concurrency as a first-class language: goroutines and channels. Goroutines are lightweight threads controlled by the Go runtime, which means that you can run thousands or even millions of operations concurrently with minimal overhead.</p>
<p>The philosophy of communication in Channels offers a secure means of communication between goroutines, and it includes the expression "do not communicate by sharing memory; share memory by communicating." Such a design makes concurrent programming more user-friendly and error-free.</p>
<p>In parallelism, Go automatically allocates goroutines to multiple CPU cores according to the GOMAXPROCS environment variable, and parallel execution is achieved automatically. This renders Go especially effective in the construction of parallel systems such as web servers, network tools, and distributed systems.</p>
<h3 id="heading-rust">Rust</h3>
<p>Rust provides concurrent and parallel programming with memory safety without performance degradation. The ownership system of the language eliminates all forms of data races at compile-time, which means that the entire category of concurrency bugs found in other languages doesn’t exist.</p>
<p>In the case of async operations, you can apply the syntax of Rust to operations of an asynchronous type with runtime libraries such as tokio or async-std and achieves similar performance to C++ without sacrificing safety.</p>
<p>The Rayon library makes parallelism of data exceedingly easy. At times, you can parallelise a calculation by substituting .iter() with .par_iter(). Rust thread pools and channels give you low-level control where necessary, and the type system keeps the threads safe, making sure that problems don’t arise in your code.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Concurrency and parallelism represent fundamental pillars of modern computing architecture. They are not interchangeable buzzwords, but are rather distinct paradigms that address different challenges:</p>
<p>Concurrency focuses on program structure and efficient task coordination. It allows systems to handle multiple operations within overlapping time periods, maximizing resource utilization and responsiveness.</p>
<p>Parallelism focuses on computational throughput and execution speed. It divides work across multiple processors to complete tasks faster through simultaneous execution.</p>
<p>The most powerful systems strategically combine both approaches, applying each where it provides the greatest benefit.</p>
<p>The next time you face a performance challenge, ask yourself these critical questions:</p>
<ol>
<li><p>Is my bottleneck caused by waiting (I/O-bound) or by computation (CPU-bound)?</p>
</li>
<li><p>Am I trying to handle more tasks simultaneously or complete tasks faster?</p>
</li>
<li><p>Do I need better resource utilisation or raw computational throughput?</p>
</li>
</ol>
<p>Your answers will guide you toward the right solution. Understanding when to apply concurrency, when to leverage parallelism, and when to combine them is what separates adequate solutions from exceptional ones. This knowledge empowers you to build systems that are not only fast but also efficient, scalable, and economically viable.</p>
<p>Master these concepts, and you will find yourself equipped to tackle increasingly complex engineering challenges with confidence and precision.</p>
<h3 id="heading-further-learning-resources">Further Learning Resources</h3>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=f6kdp27TYZs">"Go Concurrency Patterns" by Rob Pike (Google Tech Talk)</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=oV9rvDllKEg&amp;utm_source=chatgpt.com">"Concurrency is not Parallelism" by Rob Pike</a></p>
</li>
<li><p><a target="_blank" href="https://docs.python.org/3/library/asyncio.html">Python AsyncIO Official Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://realpython.com/python-concurrency/">Real Python: Concurrency and Parallelism in Python</a></p>
</li>
<li><p><a target="_blank" href="https://jcip.net/?utm_source=chatgpt.com">Java Concurrency in Practice by Brian Goetz</a></p>
</li>
<li><p><a target="_blank" href="https://spark.apache.org/docs/latest/">Apache Spark Documentation for Big Data Parallelism</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker Build Tutorial: Learn Contexts, Architecture, and Performance Optimization Techniques ]]>
                </title>
                <description>
                    <![CDATA[ Docker build is a fundamental concept every developer needs to understand. Whether you're containerizing your first application or optimizing existing Docker workflows, understanding Docker build contexts and Docker build architecture is essential fo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-build-tutorial-learn-contexts-architecture-and-performance-optimization-techniques/</link>
                <guid isPermaLink="false">68e559d8ac28fbe4acae92be</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Destiny Erhabor ]]>
                </dc:creator>
                <pubDate>Tue, 07 Oct 2025 18:20:08 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759861193876/871b72e7-9673-4572-b788-48f082a6b380.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Docker build is a fundamental concept every developer needs to understand. Whether you're containerizing your first application or optimizing existing Docker workflows, understanding Docker build contexts and Docker build architecture is essential for creating efficient, scalable containerized applications.</p>
<p>This comprehensive guide covers everything from basic concepts to advanced optimization techniques, helping you avoid common pitfalls and build better Docker images.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-docker-build">What is Docker Build?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-docker-build-architecture-how-it-all-works">Docker Build Architecture: How It All Works</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-docker-build-features">Docker Build Features</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-docker-build-context">Docker Build Context</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-types-of-docker-build-contexts">Types of Docker Build Contexts</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-common-docker-build-mistakes-and-how-to-fix-them">Common Docker Build Mistakes (And How to Fix Them)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-and-monitor-build-performance">How to Optimize and Monitor Build Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-docker-build-performance">Best Practices for Docker Build Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-troubleshooting-docker-build-issues">Troubleshooting Docker Build Issues</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-is-docker-build">What is Docker Build?</h2>
<p>Docker build is the process of creating a Docker image from a Dockerfile and a set of files called the <strong>build context</strong>. When you run <code>docker build</code>, you're instructing Docker to:</p>
<ol>
<li><p>Read your Dockerfile instructions</p>
</li>
<li><p>Gather the necessary files (build context)</p>
</li>
<li><p>Execute each instruction step-by-step</p>
</li>
<li><p>Create a final Docker image</p>
</li>
</ol>
<p>Think of it like following a recipe: the Dockerfile is your recipe, and the build context contains all the ingredients you might need.</p>
<h2 id="heading-docker-build-architecture-how-it-all-works">Docker Build Architecture: How It All Works</h2>
<p>Docker Build uses a client-server architecture where two separate components (<strong>Buildx and BuildKit</strong>) work together to build your Docker images. This is different from how many people think Docker works, as it's not just one monolithic program doing everything.</p>
<h3 id="heading-what-is-buildx-the-client">What is Buildx (The Client)?</h3>
<p>Buildx serves as the user interface that you interact with directly whenever you work with Docker builds. When you type <code>docker build .</code> in your terminal, you're actually communicating with Buildx, which acts as the intermediary between you and the actual build engine.</p>
<h4 id="heading-buildxs-primary-jobs">Buildx’s primary jobs:</h4>
<ul>
<li><p>Interprets your build command and options</p>
</li>
<li><p>Sends structured build requests to BuildKit</p>
</li>
<li><p>Manages multiple BuildKit instances (builders)</p>
</li>
<li><p>Handles authentication and secrets</p>
</li>
<li><p>Displays build progress to you</p>
</li>
</ul>
<h3 id="heading-what-is-buildkit-the-serverbuilder">What is BuildKit (The Server/Builder)</h3>
<p>BuildKit functions as the actual build engine that performs all the heavy lifting during the Docker build process. This powerful backend component receives the structured build requests from Buildx and immediately begins reading and interpreting your Dockerfiles line by line.</p>
<h4 id="heading-buildkits-primary-jobs">BuildKit’s primary jobs:</h4>
<ul>
<li><p>Receives build requests from Buildx</p>
</li>
<li><p>Reads and interprets Dockerfiles</p>
</li>
<li><p>Executes build instructions step by step</p>
</li>
<li><p>Manages build cache and layers</p>
</li>
<li><p>Requests only the files it needs from the client</p>
</li>
<li><p>Creates the final Docker image</p>
</li>
</ul>
<h3 id="heading-how-they-communicate">How They Communicate</h3>
<p>Here's what happens when you run <code>docker build .</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758733757378/d3322dad-efac-4c4a-b8f8-69f17a4920e8.png" alt="Diagram showing Docker build process with BuildKit, including sending build request with Dockerfile and build arguments, requesting and receiving package.json, running npm install, requesting and receiving src directory files, copying files, completing build, and optionally pushing to registry." class="image--center mx-auto" width="2947" height="2628" loading="lazy"></p>
<p>When you run <code>docker build</code>, the command initiates a multi-step process with BuildKit (as illustrated in the above image).</p>
<p>First, it sends a build request containing your Dockerfile, build arguments, export options, and cache options. BuildKit then intelligently requests only the files it needs when it needs them, starting with <code>package.json</code> to run <code>npm install</code> for dependency installation.</p>
<p>After that's complete, it requests the <code>src/</code> directory containing your application code and copies those files into the image with the <code>COPY</code> command.</p>
<p>Once all build steps are finished, BuildKit sends back the completed image. Optionally, you can then push this image to a container registry for distribution or deployment.</p>
<p>This on-demand file transfer approach is one of BuildKit's key optimizations: rather than sending your entire build context upfront, it only requests specific files as each build step needs them, making the build process more efficient.</p>
<h3 id="heading-key-communication-details">Key Communication Details</h3>
<p>Build request contains:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"dockerfile"</span>: <span class="hljs-string">"FROM node:18\nWORKDIR /app\n..."</span>,
  <span class="hljs-attr">"buildArgs"</span>: {<span class="hljs-attr">"NODE_ENV"</span>: <span class="hljs-string">"production"</span>},
  <span class="hljs-attr">"exportOptions"</span>: {<span class="hljs-attr">"type"</span>: <span class="hljs-string">"image"</span>, <span class="hljs-attr">"name"</span>: <span class="hljs-string">"my-app:latest"</span>},
  <span class="hljs-attr">"cacheOptions"</span>: {<span class="hljs-attr">"type"</span>: <span class="hljs-string">"registry"</span>, <span class="hljs-attr">"ref"</span>: <span class="hljs-string">"my-app:cache"</span>}
}
</code></pre>
<p>Resource requests:</p>
<ul>
<li><p>BuildKit asks: "I need the file at <code>./package.json</code>"</p>
</li>
<li><p>Buildx responds: Sends the actual file content</p>
</li>
<li><p>BuildKit asks: "I need the directory <code>./src/</code>"</p>
</li>
<li><p>Buildx responds: Sends all files in that directory</p>
</li>
</ul>
<h3 id="heading-why-this-architecture-exists">Why This Architecture Exists</h3>
<h4 id="heading-1-efficiency">1. Efficiency</h4>
<p>The old Docker builder had a major flaw: it always copied your entire build context upfront, regardless of what was actually needed. Even if your Dockerfile only used a few files, Docker would transfer hundreds of megabytes before starting the build.</p>
<p>BuildKit fixes this through on-demand file transfers. It only requests specific files at each step.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Old Docker Builder (legacy)</span>
<span class="hljs-comment"># Always copied ENTIRE context upfront</span>
$ docker build .
Sending build context to Docker daemon  245.7MB  <span class="hljs-comment"># Everything!</span>

<span class="hljs-comment"># New BuildKit Architecture  </span>
<span class="hljs-comment"># Only requests files when needed</span>
$ docker build .
<span class="hljs-comment">#1 [internal] load build definition from Dockerfile    0.1s</span>
<span class="hljs-comment">#2 [internal] load .dockerignore                       0.1s</span>
<span class="hljs-comment">#3 [1/4] FROM node:18                                  0.5s</span>
<span class="hljs-comment">#4 [internal] load build context                       0.1s</span>
<span class="hljs-comment">#4 transferring context: 234B  # Only package.json initially!</span>
<span class="hljs-comment">#5 [2/4] WORKDIR /app                                  0.2s  </span>
<span class="hljs-comment">#6 [3/4] COPY package*.json ./                         0.1s</span>
<span class="hljs-comment">#7 [4/4] RUN npm install                               5.2s</span>
<span class="hljs-comment">#8 [internal] load build context                       0.3s  </span>
<span class="hljs-comment">#8 transferring context: 2.1MB  # Now requests src/ files</span>
<span class="hljs-comment">#9 [5/4] COPY src/ ./src/                              0.2s</span>
</code></pre>
<h4 id="heading-2-scalability">2. Scalability</h4>
<p>The client-server architecture enables scalability features. Multiple Docker CLI clients can connect to the same BuildKit instance, and BuildKit can run on remote servers instead of your local machine. This means you could execute builds on a cloud server while controlling them from your laptop. Teams can also deploy multiple BuildKit instances for different teams or purposes, scaling from individual developers to large enterprises.</p>
<h4 id="heading-3-security">3. Security</h4>
<p>Security is improved by only requesting sensitive files when explicitly needed. BuildKit never sees files your Dockerfile doesn't reference, reducing the attack surface. It also handles credentials through separate, secure channels rather than mixing them with your build context, preventing secrets from being embedded in image layers or exposed in build logs.</p>
<h3 id="heading-real-world-example">Real-World Example</h3>
<p>Let's trace through a typical build step by step. You can find the full code available here: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/02-python-cache">02-python-cache</a>.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> python:<span class="hljs-number">3.9</span>-slim
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> requirements.txt .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> pip install -r requirements.txt</span>
<span class="hljs-keyword">COPY</span><span class="bash"> src/ ./src/</span>
<span class="hljs-keyword">COPY</span><span class="bash"> main.py .</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"python"</span>, <span class="hljs-string">"main.py"</span>]</span>
</code></pre>
<p>Let’s see what actually happens here:</p>
<ol>
<li><p>You run <code>docker build .</code></p>
</li>
<li><p>Buildx says to BuildKit:</p>
</li>
</ol>
<pre><code class="lang-bash">   <span class="hljs-string">"Here's a build request with this Dockerfile"</span>
</code></pre>
<ol start="3">
<li><p><strong>BuildKit processes</strong>: <code>FROM python:3.9-slim</code></p>
<ul>
<li>No client files needed, pulls base image</li>
</ul>
</li>
<li><p><strong>BuildKit processes</strong>: <code>COPY requirements.txt .</code></p>
<ul>
<li><p>BuildKit to Buildx: "I need <code>requirements.txt</code>"</p>
</li>
<li><p>Buildx to BuildKit: Sends the file content</p>
</li>
</ul>
</li>
<li><p><strong>BuildKit processes</strong>: <code>RUN pip install -r requirements.txt</code></p>
<ul>
<li>No client files needed, runs inside container</li>
</ul>
</li>
<li><p><strong>BuildKit processes</strong>: <code>COPY src/ ./src/</code></p>
<ul>
<li><p>BuildKit to Buildx: "I need all files in <code>src/</code> directory"</p>
</li>
<li><p>Buildx to BuildKit: Sends all files in src/</p>
</li>
</ul>
</li>
<li><p><strong>BuildKit processes</strong>: <code>COPY main.py .</code></p>
<ul>
<li><p>BuildKit to Buildx: "I need <code>main.py</code>"</p>
</li>
<li><p>Buildx to BuildKit: Sends the file</p>
</li>
</ul>
</li>
<li><p>BuildKit to Buildx: "Build complete, here's your image"</p>
</li>
</ol>
<p>From the illustration, you can see that BuildKit only requests what it needs, when it needs it. Not this entire context:</p>
<pre><code class="lang-bash">
my-app/
├── src/                 <span class="hljs-comment"># ← Only loaded when COPY src/ runs</span>
├── tests/              <span class="hljs-comment"># ← Never requested (not in Dockerfile)</span>
├── docs/               <span class="hljs-comment"># ← Never requested  </span>
├── node_modules/       <span class="hljs-comment"># ← Never requested (in .dockerignore)</span>
├── requirements.txt    <span class="hljs-comment"># ← Loaded early (first COPY)</span>
└── main.py            <span class="hljs-comment"># ← Loaded later (second COPY)</span>
</code></pre>
<h2 id="heading-docker-build-features">Docker Build Features</h2>
<h3 id="heading-named-contexts">Named Contexts</h3>
<p>👉 Demo project: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/07-named-contexts">07-named-contexts</a></p>
<p>Named contexts allow you to include files from multiple sources during a build while keeping them logically separated. This is useful when you need documentation, configuration files, or shared libraries from different directories or repositories in your build.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Build with additional named context</span>
docker build --build-context docs=./documentation .
</code></pre>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Use named context in Dockerfile</span>
<span class="hljs-keyword">FROM</span> alpine
<span class="hljs-keyword">COPY</span><span class="bash"> . /app</span>
<span class="hljs-comment"># Mount files from named context</span>
<span class="hljs-keyword">RUN</span><span class="bash"> --mount=from=docs,target=/docs \
    cp /docs/manual.pdf /app/</span>
</code></pre>
<h3 id="heading-build-secrets">Build Secrets</h3>
<p>👉 Demo project: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/06-build-secrets">06-build-secrets</a></p>
<p>Build secrets let you pass sensitive information (like API keys or passwords) to your build without including them in the final image or build history. The secrets are mounted temporarily during specific <code>RUN</code> commands and are never stored in image layers.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Pass secret to build</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"api_key=secret123"</span> | docker build --secret id=apikey,src=- .
</code></pre>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Use secret in Dockerfile</span>
<span class="hljs-keyword">FROM</span> alpine
<span class="hljs-keyword">RUN</span><span class="bash"> --mount=<span class="hljs-built_in">type</span>=secret,id=apikey \
    <span class="hljs-built_in">export</span> API_KEY=$(cat /run/secrets/apikey) &amp;&amp; \
    curl -H <span class="hljs-string">"Authorization: <span class="hljs-variable">$API_KEY</span>"</span> https://api.example.com/data</span>
</code></pre>
<h2 id="heading-docker-build-context">Docker Build Context</h2>
<h3 id="heading-what-is-a-build-context">What is a Build Context?</h3>
<p>The build context is the collection of files and directories that Docker can access during the build process. It's like gathering all your cooking ingredients on the counter before you start cooking.</p>
<pre><code class="lang-bash">docker build [OPTIONS] CONTEXT
                       ^^^^^^^
                       This is your build context
</code></pre>
<h3 id="heading-why-build-contexts-matter">Why Build Contexts Matter</h3>
<ol>
<li><p><strong>Security</strong>: Only files in the context can be accessed during build</p>
</li>
<li><p><strong>Performance</strong>: Large contexts slow down builds</p>
</li>
<li><p><strong>Functionality</strong>: Your Dockerfile can only COPY/ADD files from the context</p>
</li>
<li><p><strong>Efficiency</strong>: Understanding contexts helps you build faster, leaner images</p>
</li>
</ol>
<h2 id="heading-types-of-docker-build-contexts">Types of Docker Build Contexts</h2>
<h3 id="heading-1-local-directory-context-most-common">1. Local Directory Context (Most Common)</h3>
<p>👉 See code here: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/01-node-local-context">01-node-local-context</a></p>
<p>This is what you'll use in 90% of cases – pointing to a folder on your machine:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Use current directory</span>
docker build .

<span class="hljs-comment"># Use specific directory</span>
docker build /path/to/my/project

<span class="hljs-comment"># Use parent directory</span>
docker build ..
</code></pre>
<p><strong>Example Project Structure:</strong></p>
<pre><code class="lang-bash">my-webapp/
├── src/
│   ├── index.js
│   └── utils.js
├── public/
│   ├── index.html
│   └── styles.css
├── package.json
├── package-lock.json
├── Dockerfile
├── .dockerignore
└── README.md
</code></pre>
<p><strong>Corresponding Dockerfile:</strong></p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>

<span class="hljs-comment"># Copy package files first for better layer caching</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci --only=production</span>

<span class="hljs-comment"># Copy application source</span>
<span class="hljs-keyword">COPY</span><span class="bash"> src/ ./src/</span>
<span class="hljs-keyword">COPY</span><span class="bash"> public/ ./public/</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">3000</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"node"</span>, <span class="hljs-string">"src/index.js"</span>]</span>
</code></pre>
<h3 id="heading-2-remote-git-repository-context">2. Remote Git Repository Context</h3>
<p>You can build directly from Git repositories without cloning locally:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Build from GitHub main branch</span>
docker build https://github.com/&lt;username&gt;/project.git

<span class="hljs-comment"># Build from specific branch</span>
docker build https://github.com/&lt;username&gt;/project.git<span class="hljs-comment">#develop</span>

<span class="hljs-comment"># Build from specific directory in repo</span>
docker build https://github.com/&lt;username&gt;/project.git<span class="hljs-comment">#main:docker</span>

<span class="hljs-comment"># Build with authentication</span>
docker build --ssh default git@github.com:&lt;username&gt;/private-repo.git
</code></pre>
<p>This has various cases like CI/CD pipelines, building open-source projects, ensuring clean builds from source control, automated deployments, and so on.</p>
<h3 id="heading-3-remote-tarball-context">3. Remote Tarball Context</h3>
<p>You can also build from compressed archives hosted on web servers. A remote <strong>tarball</strong> is a <code>.tar.gz</code> or similar compressed archive file accessible via HTTP/HTTPS. This is useful when your source code is packaged and hosted on a web server, artifact repository, or CDN. Docker downloads and extracts the archive automatically, using its contents as the build context.</p>
<p>This approach works well for CI/CD pipelines where build artifacts are stored centrally, or when you want to build images from released versions of your code without cloning entire repositories.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Build from remote tarball</span>
docker build http://server.com/context.tar.gz

<span class="hljs-comment"># BuildKit downloads and extracts automatically</span>
docker build https://example.com/project-v1.2.3.tar.gz
</code></pre>
<h3 id="heading-4-empty-context-advanced">4. Empty Context (Advanced)</h3>
<p>When you don't need any files, you can pipe the Dockerfile directly:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create image without file context</span>
docker build -t hello-world - &lt;&lt;EOF
FROM alpine:latest
RUN <span class="hljs-built_in">echo</span> <span class="hljs-string">"Hello, World!"</span> &gt; /hello.txt
CMD cat /hello.txt
EOF
</code></pre>
<h2 id="heading-common-docker-build-mistakes-and-how-to-fix-them">Common Docker Build Mistakes (And How to Fix Them)</h2>
<h3 id="heading-mistake-1-wrong-context-directory">Mistake 1: Wrong Context Directory</h3>
<p>👉 Reproduced here: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/04-wrong-context">04-wrong-context</a></p>
<p>This mistake occurs when you run <code>docker build</code> from the wrong directory, causing the build context to be different from what your Dockerfile expects.</p>
<p>In the example, running <code>docker build frontend/</code> from the <code>/projects/</code> directory means the context is <code>/projects/frontend/</code>, but the Dockerfile tries to access <code>../shared/utils.js</code>, which is outside this context. Docker can only access files within the build context, so any attempt to reference files outside it will fail.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Project structure</span>
/projects/
├── frontend/
│   ├── Dockerfile
│   ├── src/
│   └── package.json
└── shared/
    └── utils.js

<span class="hljs-comment"># WRONG - Running from projects directory</span>
docker build frontend/
<span class="hljs-comment"># This won't work if Dockerfile tries to COPY ../shared/utils.js</span>
</code></pre>
<h4 id="heading-how-to-fix-wrong-context-directory">How to fix wrong context directory:</h4>
<p>The key is aligning your build context with what your Dockerfile needs.</p>
<ul>
<li><p><strong>Option 1</strong> changes your working directory so the context matches your Dockerfile's expectations. You run the build from inside <code>frontend/</code>, making that directory the context root.</p>
</li>
<li><p><strong>Option 2</strong> keeps you in the parent directory but explicitly sets it as the context (the <code>.</code> argument) while telling Docker where to find the Dockerfile with the <code>-f</code> flag. Now both <code>frontend/</code> and <code>shared/</code> are accessible since they're both within the <code>/projects/</code> context.</p>
</li>
</ul>
<pre><code class="lang-bash"><span class="hljs-comment"># Option 1: Run from correct directory</span>
<span class="hljs-built_in">cd</span> frontend
docker build .

<span class="hljs-comment"># Option 2: Use parent directory as context</span>
docker build -f frontend/Dockerfile .
</code></pre>
<h3 id="heading-mistake-2-including-massive-files">Mistake 2: Including Massive Files</h3>
<p>👉 Optimized version with <code>.dockerignore</code>: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/05-dockerignore-optimization">05-dockerignore-optimization</a></p>
<p>This mistake happens when your build context contains large, unnecessary files that slow down the build process.</p>
<p>Docker must transfer the entire context to the build daemon before starting, so including files like <code>node_modules</code> (which can be hundreds of MB), git history, build artifacts, logs, and database dumps makes builds painfully slow. These files are rarely needed in the final image and should be excluded.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># This context includes everything!</span>
my-app/
├── node_modules/        <span class="hljs-comment"># 200MB+ </span>
├── .git/               <span class="hljs-comment"># Version history</span>
├── dist/               <span class="hljs-comment"># Built files</span>
├── logs/               <span class="hljs-comment"># Log files</span>
├── temp/               <span class="hljs-comment"># Temporary files</span>
├── database.dump       <span class="hljs-comment"># 1GB database backup</span>
└── Dockerfile
</code></pre>
<h4 id="heading-how-to-fix-docker-build-massive-files">How to fix Docker build massive files:</h4>
<p>Use <code>.dockerignore</code> to exclude unnecessary files, dramatically reducing context size and build time. We’ll discuss this in more detail below.</p>
<h3 id="heading-mistake-3-inefficient-layer-caching">Mistake 3: Inefficient Layer Caching</h3>
<p>👉 See good practice code here: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/02-python-cache">02-python-cache</a></p>
<p>This mistake wastes Docker's layer caching system by copying frequently-changing files (like source code) before running expensive operations (like <code>npm install</code>). When you modify your source code, Docker invalidates the cache for that layer and all subsequent layers, forcing <code>npm install</code> to run again even though dependencies haven't changed. This can turn a 5-second build into a 5-minute build.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># BAD - Changes to source code rebuild npm install</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . /app</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
<h4 id="heading-how-to-fix-docker-build-inefficient-layer-caching">How to fix docker build inefficient layer caching:</h4>
<p>Copy dependency files first, install dependencies, then copy source code. This way, <code>npm install</code> only runs when <code>package.json</code> actually changes:</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># GOOD - npm install only rebuilds when package.json changes</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm install</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"npm"</span>, <span class="hljs-string">"start"</span>]</span>
</code></pre>
<h2 id="heading-how-to-optimize-and-monitor-build-performance">How to Optimize and Monitor Build Performance</h2>
<p>Understanding build performance metrics helps you identify bottlenecks and measure improvements.</p>
<h3 id="heading-how-to-optimize-docker-builds-with-dockerignore">How to Optimize Docker Builds with .dockerignore</h3>
<p>The <code>.dockerignore</code> file is your secret weapon for faster, more secure builds. It tells Docker which files to exclude from the build context.</p>
<h4 id="heading-creating-dockerignore-patterns">Creating .dockerignore Patterns</h4>
<p>Create a <code>.dockerignore</code> file in your project root. The syntax is similar to <code>.gitignore</code>, and you can use wildcards (<code>*</code>), match specific file extensions (<code>*.log</code>), exclude entire directories (<code>node_modules/</code>), or use negation patterns (<code>!important.txt</code>) to include files that would otherwise be excluded. Each line represents a pattern, and comments start with <code>#</code>.</p>
<p>Example of a .dockerignore file:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Dependencies</span>
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

<span class="hljs-comment"># Build outputs</span>
dist/
build/
*.tgz

<span class="hljs-comment"># Version control</span>
.git/
.gitignore
.svn/

<span class="hljs-comment"># IDE and editor files</span>
.vscode/
.idea/
*.swp
*.swo
*~

<span class="hljs-comment"># OS generated files</span>
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

<span class="hljs-comment"># Logs and databases</span>
*.<span class="hljs-built_in">log</span>
*.sqlite
*.db

<span class="hljs-comment"># Environment and secrets</span>
.env
.env.local
.env.*.<span class="hljs-built_in">local</span>
secrets/
*.key
*.pem

<span class="hljs-comment"># Documentation</span>
README.md
docs/
*.md

<span class="hljs-comment"># Test files</span>
<span class="hljs-built_in">test</span>/
tests/
*.test.js
coverage/

<span class="hljs-comment"># Temporary files</span>
tmp/
temp/
*.tmp
</code></pre>
<h3 id="heading-measuring-build-performance">Measuring Build Performance</h3>
<h4 id="heading-analyzing-build-time">Analyzing Build Time</h4>
<p>Understanding where your build spends time helps identify bottlenecks and optimization opportunities. The detailed progress output shows timing for each build step, cache hits/misses, and resource usage.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Enable BuildKit progress output</span>
DOCKER_BUILDKIT=1 docker build --progress=plain .

<span class="hljs-comment"># Use buildx for detailed timing</span>
docker buildx build --progress=plain .
</code></pre>
<h4 id="heading-profiling-context-transfer">Profiling Context Transfer</h4>
<p>Monitor context transfer time to understand how build context size affects overall performance. Profile which directories contribute most to help target <code>.dockerignore</code> optimizations.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Measure context transfer time</span>
time docker build --no-cache .

<span class="hljs-comment"># Profile context size by directory</span>
du -sh */ | sort -hr
</code></pre>
<h4 id="heading-measuring-dockerignore-impact">Measuring .dockerignore Impact</h4>
<p>Before <code>.dockerignore</code>, you'll notice that the <code>transfering context</code> size is 245.7MB in 15.2s:</p>
<pre><code class="lang-bash">$ docker build .
<span class="hljs-comment">#1 [internal] load build context</span>
<span class="hljs-comment">#1 transferring context: 245.7MB in 15.2s</span>
</code></pre>
<p>After adding the .dockerignore file, the context reduced to 2.1MB in 0.3s:</p>
<pre><code class="lang-bash">$ docker build .
<span class="hljs-comment">#1 [internal] load build context  </span>
<span class="hljs-comment">#1 transferring context: 2.1MB in 0.3s</span>
</code></pre>
<p><strong>Result</strong>: 99% reduction in context size and 50x faster context transfer!</p>
<h2 id="heading-best-practices-for-docker-build-performance">Best Practices for Docker Build Performance</h2>
<p>We've covered several optimization techniques throughout this guide. Here's a quick recap of the key practices, plus some additional strategies:</p>
<ol>
<li><p><strong>Layer Caching</strong> (covered in Mistake 3): Copy dependency files before source code to maximize cache reuse.</p>
</li>
<li><p><strong>Using .dockerignore</strong> (covered in Mistake 2): Exclude unnecessary files to reduce context size and improve build speed.</p>
</li>
<li><p><strong>Choosing the Right Context</strong> (covered earlier): Select appropriate context types (local, Git, tarball) based on your use case.</p>
</li>
</ol>
<p>Now let’s talk about some more ways you can improve performance:</p>
<h3 id="heading-use-multi-stage-builds">Use Multi-Stage Builds</h3>
<p>👉 Demo project: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples/03-multistage-node">03-multistage-node</a></p>
<p>Multi-stage builds let you use one image for building/compiling your application and a different, smaller image for running it. This dramatically reduces your final image size by excluding build tools, source code, and other unnecessary files from the production image.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Build stage</span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span> AS builder
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package*.json ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm ci</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>
<span class="hljs-keyword">RUN</span><span class="bash"> npm run build</span>

<span class="hljs-comment"># Production stage</span>
<span class="hljs-keyword">FROM</span> nginx:alpine
<span class="hljs-keyword">COPY</span><span class="bash"> --from=builder /app/dist /usr/share/nginx/html</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"nginx"</span>, <span class="hljs-string">"-g"</span>, <span class="hljs-string">"daemon off;"</span>]</span>
</code></pre>
<h3 id="heading-use-specific-base-images">Use Specific Base Images</h3>
<p>Generic base images like <code>ubuntu:latest</code> include many packages you don't need, making your images larger and slower to download. Specific images like <code>node:18-alpine</code> or distroless images contain only what's necessary for your application to run.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Large base image</span>
<span class="hljs-keyword">FROM</span> ubuntu:latest

<span class="hljs-comment"># Smaller, more specific base image  </span>
<span class="hljs-keyword">FROM</span> node:<span class="hljs-number">18</span>-alpine

<span class="hljs-comment"># Even smaller distroless image</span>
<span class="hljs-keyword">FROM</span> gcr.io/distroless/nodejs18-debian11
</code></pre>
<h3 id="heading-combine-run-commands">Combine RUN Commands</h3>
<p>Each <code>RUN</code> command creates a new layer in your image. Multiple <code>RUN</code> commands create multiple layers, increasing image size. Combining commands into a single <code>RUN</code> instruction creates just one layer, and you can clean up temporary files in the same step.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># Creates multiple layers</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get install -y curl</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get clean</span>

<span class="hljs-comment"># Single layer</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; \
    apt-get install -y curl &amp;&amp; \
    apt-get clean &amp;&amp; \
    rm -rf /var/lib/apt/lists/*</span>
</code></pre>
<h2 id="heading-troubleshooting-docker-build-issues">Troubleshooting Docker Build Issues</h2>
<h3 id="heading-issue-copy-failed-no-such-file-or-directory">Issue: "COPY failed: no such file or directory"</h3>
<p><strong>Problem</strong>: File not in build context<br><strong>What’s going wrong</strong>: Docker can only access files within the build context (the directory you specify in <code>docker build</code>). If your Dockerfile tries to <code>COPY</code> a file that doesn't exist in the context directory, the build fails. This often happens when running the build command from the wrong directory or when the file path is incorrect relative to the context root.</p>
<p><strong>Solution</strong>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Check what's in your context</span>
ls -la

<span class="hljs-comment"># Verify file path relative to context</span>
docker build -t debug . --progress=plain
</code></pre>
<h3 id="heading-issue-docker-build-is-extremely-slow">Issue: "Docker Build is extremely slow"</h3>
<p><strong>Problem</strong>: Large build context<br><strong>What’s going wrong</strong>: Docker must transfer your entire build context to the BuildKit daemon before building starts. If your context contains large files, directories like <code>node_modules</code>, or unnecessary files, this transfer can take minutes instead of seconds. The larger the context, the slower your builds become.</p>
<p><strong>Solution</strong>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Check context size</span>
du -sh .

<span class="hljs-comment"># Add more patterns to .dockerignore</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"large-directory/"</span> &gt;&gt; .dockerignore
<span class="hljs-built_in">echo</span> <span class="hljs-string">"*.zip"</span> &gt;&gt; .dockerignore
</code></pre>
<h3 id="heading-issue-cannot-locate-specified-dockerfile">Issue: "Cannot locate specified Dockerfile"</h3>
<p><strong>Problem</strong>: Dockerfile not in context root<br><strong>What’s going wrong</strong>: By default, Docker looks for a file named <code>Dockerfile</code> in the root of your build context. If your Dockerfile is in a subdirectory or has a different name, Docker can't find it. This is common in monorepo setups where Dockerfiles are organized in separate folders.</p>
<p><strong>Solution</strong>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Specify Dockerfile location</span>
docker build -f path/to/Dockerfile .

<span class="hljs-comment"># Or move Dockerfile to context root</span>
mv path/to/Dockerfile .
</code></pre>
<h3 id="heading-issue-cache-misses-on-unchanged-files">Issue: "Cache misses on unchanged files"</h3>
<p><strong>Problem</strong>: File timestamps or permissions changed<br><strong>What’s going wrong</strong>: Docker's layer caching relies on file checksums and metadata. Even if file content is unchanged, different timestamps or permissions can cause cache misses, forcing unnecessary rebuilds. This often happens after git operations, file system operations, or when files are copied between systems.</p>
<p><strong>Solution</strong>:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Check file modifications</span>
git status

<span class="hljs-comment"># Reset timestamps</span>
git ls-files -z | xargs -0 touch -r .git/HEAD
</code></pre>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Understanding Docker build contexts and architecture is essential for achieving faster builds. We’ve covered various techniques in this article, like optimized contexts and caching strategies, creating smaller images with efficient layering and multi-stage builds, maintaining better security with proper secret handling and minimal attack surface, and delivering an improved developer experience with faster iteration cycles.</p>
<p>👉 <strong>Full code examples are available on GitHub here:</strong> <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building/tree/main/beginner/docker/docker-build-architecture-examples">Docker build architecture examples</a></p>
<p>As always, I hope you enjoyed the article and learned something new. If you want, you can also follow me on <a target="_blank" href="https://www.linkedin.com/in/destiny-erhabor">LinkedIn</a> or <a target="_blank" href="https://twitter.com/caesar_sage">Twitter</a>.</p>
<p>For more hands-on projects, follow and star this repository: <a target="_blank" href="https://github.com/Caesarsage/Learn-DevOps-by-building">Learn-DevOps-by-building</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Choose the Best Programming Languages, Libraries, and Patterns ]]>
                </title>
                <description>
                    <![CDATA[ In my first few years learning software development and building applications, I was quite interested in finding the best programming language, platform, libraries, frameworks, patterns, and architectures available. I thought that by finding the best... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-choose-the-best-programming-languages-libraries-and-patterns/</link>
                <guid isPermaLink="false">6898d3414c52a26ffefb1693</guid>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ programming languages ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ryan Michael Kay ]]>
                </dc:creator>
                <pubDate>Sun, 10 Aug 2025 17:13:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754846007203/c9db729e-ebed-4726-8e3e-5414c8e2714d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In my first few years learning software development and building applications, I was quite interested in finding the best programming language, platform, libraries, frameworks, patterns, and architectures available. I thought that by finding the <em>best</em> things and focusing on those topics to the exclusion of others, I could avoid wasting precious time.</p>
<p>While I figured out early on that narrowing my focus by using a project-based learning approach (as opposed to the topic-by-topic laundry list approach) was important, finding the best tools for the job was a different matter. </p>
<p>If you happen to be searching for some of those things, then this article is for you. After over a decade of programming products, building client applications, answering thousands of questions from junior and intermediate developers, and wrestling with these questions myself, I will do my best to explain how to find the best <em>things</em>.</p>
<p>This article is intended for junior to intermediate level developers looking to get some practical answers to difficult problems. You will not need extensive programming experience to get through it and you may skip over any technical discussions specifics. Those are meant to be helpful pieces of information, but the core of this article is about how to make these decisions in general using what I call: The Law of Suitability.</p>
<p>The topics I will cover are:</p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-find-the-best-anything">How to Find the Best Anything</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-find-the-best-water-bottle">How to Find the Best Water Bottle</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-find-the-best-programming-language">How to Find the Best Programming Language</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-navigating-public-and-expert-opinions">Navigating Public and Expert Opinions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-low-level-vs-high-level">Low Level vs High Level</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tightly-structured-static-vs-loosely-structured-dynamic">Tightly Structured (Static) vs Loosely Structured (Dynamic)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-popularity-is-only-one-factor">Popularity Is Only One Factor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-popularity-is-not-a-guarantee-of-employment">Popularity Is Not A Guarantee Of Employment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hardware-amp-working-with-what-you-have">Hardware &amp; Working With What You Have</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-if-i-want-the-ai-to-code-for-me">What if I Want the AI to Code For Me?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-avoid-the-sunk-cost-fallacy">Avoid the Sunk-Cost Fallacy</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-find-the-best-libraries-and-frameworks">How to Find the Best Libraries and Frameworks</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-libraries-and-frameworks">What Are Libraries and Frameworks?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-choose-libraries-and-frameworks">How to Choose Libraries and Frameworks</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-find-the-best-programming-principles-and-practices">How to Find the Best Programming Principles and Practices</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-dry-dont-repeat-yourself">D.R.Y – Don’t Repeat Yourself</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-about-other-programming-principles">What About Other Programming Principles?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-a-note-about-patterns-and-architectures">A Note About Patterns and Architectures</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-find-the-best-software-architecture">How to Find the Best Software Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-design-pattern-trap">The Design Pattern Trap</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
<h2 id="heading-how-to-find-the-best-anything">How to Find the Best Anything</h2>
<p>I invite you to follow along with some basic non-technical examples which lay the groundwork for the rest of this article. The examples may sound silly to some, but I have layered in some conceptual patterns which you can apply to programming languages, tools, and concepts – as well as just about anything where terms like good, bad, best, or worst can apply.</p>
<h3 id="heading-how-to-find-the-best-water-bottle">How to Find the Best Water Bottle</h3>
<p>Suppose that you are looking to solve the problem of staying hydrated and wish to purchase a water bottle. </p>
<p>You consider that figuring out the best water bottle might involve looking into:</p>
<ul>
<li><p>Public opinions and reviews</p>
</li>
<li><p>Expert opinions and reviews</p>
</li>
<li><p>The manufacturer’s descriptions and reputation</p>
</li>
<li><p>Buying and testing water bottles (though preferably not all of them, as that costs too much time and money, generally)</p>
</li>
</ul>
<p>All of those things are fair for consideration. Something which is not well-tested in public presents uncertainty. Expert reviews can help inform your decision, but you have to consider the biases and motivations of such experts. You also should consider if the manufacturer has a history of quality, design, and customer support, or whether they’re simply maximizing profit.</p>
<p>After doing some research, you come up with these options:</p>
<ul>
<li><p>A plastic bottle of water from a vending machine</p>
</li>
<li><p>A high-tech metal bottle which you can even boil stuff in!</p>
</li>
<li><p>A simple but ethically sourced, refillable, BPA-free plastic water bottle</p>
</li>
</ul>
<p>However, you noticed that nobody seemed to universally agree about which option was the best. There were usually some common opinions, but it was never the case that every expert had the same evaluation or recommendation.</p>
<p>After reflecting on this, it became obvious that you need to consider how, when, and where you will be using this water bottle. In other words, you need to <strong>consider the context or situation</strong> of its usage.</p>
<p>Suppose three different contexts within which you have to make this decision:</p>
<ul>
<li><p>You are standing in a rest stop in Death Valley California (often thought to be the hottest place on Earth in summer) and there is a vending machine full of micro-plastic filled, old, generic, and cheap plastic bottles of water in front of you. But you have no other options and are very thirsty</p>
</li>
<li><p>You are in a camping store preparing for a camping trip to New Zealand with lots of hiking and not too much access to filtered water</p>
</li>
<li><p>You are looking on your favorite shopping website for something you can bring to work each day to avoid those dehydration headaches from not drinking enough water</p>
</li>
</ul>
<p>In summary, you shouldn’t ignore second-hand knowledge, expert opinions, popularity, ratings, reviews, testimonials, and even first-hand experience. But you will never find the best water bottle for every situation you find yourself in. The best “anything” depends on the problem you are trying to solve and the context (words like requirements or situation also apply here) of that problem. </p>
<p>In other words, none of these things have an absolute or fixed value – their value is always relative. I call that the <strong>Law of Suitability</strong>.</p>
<p>Let’s now discuss some examples which are directly related to software design and development. </p>
<h2 id="heading-how-to-find-the-best-programming-language">How to Find the Best Programming Language</h2>
<p>The Law of Suitability applies just as much to programming languages as it does to water bottles – even though the details and contexts are different. There is no such thing as the best programming language for every person, team, problem, or feature. </p>
<p>But I do have some specific details and contexts to offer which may help you answer that question for yourself. This section will cover concrete details and some general ideas on how to choose a programming language. These patterns also apply to frameworks, libraries, and most other aspects of programming.</p>
<p>If you are not interested in the topic of finding a programming language, feel free to skip to the next sections on topics like libraries, principles, and patterns.</p>
<h3 id="heading-navigating-public-and-expert-opinions">Navigating Public and Expert Opinions</h3>
<p>Firstly, you need to be skeptical of popularity and the opinions of experts and “influencers” here. </p>
<p>My general tip here is to be extremely cautious about anyone who makes one of these claims:</p>
<ul>
<li><p>“X” language is the best (though saying “X” language is my favourite is perfectly acceptable)</p>
</li>
<li><p>“X” language is the worst, is terrible, is dead, is garbage, is useless, and so on.</p>
</li>
</ul>
<p>There are three groups of people who generally make these sorts of statements:</p>
<ul>
<li><p>Actual experts who are voicing their personal preferences but presenting them as immutable facts (how I wish this was not so common in this industry)</p>
</li>
<li><p>Non-experts parroting opinions of the above group or who have not yet understood the Law of Suitability</p>
</li>
<li><p>Engagement farmers</p>
</li>
</ul>
<p>It’s also worth noting that people can be experts in a subset of problems but that doesn’t guarantee their opinions about all problems are expert level. </p>
<p>This doesn’t mean you should reflexively dismiss expert opinions in general. Consider both the track records of the person and the degree to which they pay attention to the context of their statement. </p>
<p>Let us look at two examples:</p>
<ul>
<li><p><em>Expert A</em> says: “Python has the best tooling, support, and ecosystem for ML development”</p>
</li>
<li><p><em>Expert B</em> says: “Python is the best programming language”</p>
</li>
</ul>
<p>While <em>Expert B</em> is obviously displaying a lack of precision (either deliberately or not) if you have followed me so far, <em>Expert A</em> is a different case. Whether or not the statements made by <em>Expert A</em> are true (for the record, I have written a bit of Python but no ML code), you can tell that they are considering details and context. Look for people like <em>Expert A</em>!</p>
<h3 id="heading-low-level-vs-high-level">Low Level vs High Level</h3>
<p>In simple terms, low level programming languages are difficult for humans to read and write. Also, they tend to be faster and have a lower memory footprint than high level languages. Conversely, high level languages are closer to human language which generally makes them easier for humans to work with.</p>
<p>I must confess, though, that I have seen plenty of examples of people writing unintelligible code in high level languages – please don’t do that.</p>
<p>Someone working on an embedded system might want to do so in a language like C or C++ to optimize performance or work around the limitations of memory and processing power.</p>
<p>But in enterprise systems, which need to run on a variety of platforms and which closely intermingle with business requirements, rules, and real world objects (thing products, users, and so on), lower level languages are not so popular. After all, upper and middle management generally tends to care about low level optimizations only to the extent it affects the user experience. </p>
<p>I love optimization in general, but never forget that everything is fast with small datasets (that is, when <em>n</em> is small) or high processing power. In simpler terms, sometimes human concerns like legibility are significantly more important than insignificant optimizations on efficiency.</p>
<h3 id="heading-tightly-structured-static-vs-loosely-structured-dynamic">Tightly Structured (Static) vs Loosely Structured (Dynamic)</h3>
<p>Ironically, the main downsides and benefits of a language like Java (which is very structured and verbose) versus a language like JavaScript (quite the opposite, depending on how you use it) are the same depending on the context. </p>
<p>Speaking of enterprise systems, using structures, types, interfaces, classes, threading, concurrency primitives, and similar programming constructs can have a variety of benefits. It can insulate you from safety while providing some flexibility via type hierarchies, interfaces, protocols, abstractions and so on.</p>
<p>Further, studying design patterns can teach you repeatable solutions to problems which have been encountered since the dawn of the general purpose computer – or shortly thereafter. </p>
<p>But the Law of Suitability still applies here. Maybe you just need to write a quick script to migrate some data from one SQL database to another. Maybe you know how to approach problems using a more functional approach that doesn’t require or discourages the use of objects, classes, or structs. Maybe you realize one day that trying to apply design patterns, architectures, hierarchies and similar constructs in every situation has actually created as many problems as they were solving. More on that later.</p>
<p>It’s also worth mentioning that most modern language designers and maintainers have understood that we developers like flexibility. Many of us want to avoid premature optimization and unnecessary structure but also don’t want our code to blow up because we accidentally told the program to add together 1.23356 + “Rhinoceros”. </p>
<p>The main point is that structure or a lack of structure is both a blessing and a curse, depending on where it is used.</p>
<h3 id="heading-popularity-is-only-one-factor">Popularity Is Only One Factor</h3>
<p>I’m not going to say that popularity is irrelevant and that you should start with the least popular hipster programming language you can find. No shade intended to hipster programming languages, but unpopularity is not generally a good thing in isolation, either.</p>
<p>The key point is that many people weighing in on programming languages (probably most) don’t have abundant experience working on a variety of platforms, languages, and settings. If someone has only ever written Python and enjoys doing so, they will naturally tend to regard it above others.</p>
<p>We humans have a tendency to find the first thing that works for us and then die on a hill defending it. But to take a more anecdotal approach here, I know a couple dozen intermediate to senior level developers who have extensive experience in Java and other programming languages. Despite Java still being ranked as one of the most popular languages globally, only one of those developers I know actually prefers to write in Java if they have the choice. </p>
<p>Don’t make the mistake of assuming that the first thing that works for many people will be the last thing you need to try out. I have from time to time experimented with languages such as Haskell, which taught me many valuable lessons about the benefits of making my code more functional (and functionally pure) in nature. </p>
<p>But I have zero intention of using Haskell as my go to solution for building GUI applications. </p>
<h3 id="heading-popularity-is-not-a-guarantee-of-employment">Popularity Is Not A Guarantee Of Employment</h3>
<p>One of the most common things you ought to consider is whether or not the language you pick will help you get a job – assuming that’s a concern. Influencers absolutely love to tell people to choose one particular language because it has the most public commits on GitHub, or another because it has the largest number of programmers using it (which is in practice something that’s impossible to say for sure). </p>
<p>Let me flip that on its head: suppose that the most common programming language and platform combination is JavaScript and web. Let’s further suppose that we have pretty concrete data on the number of job postings on the web which confirms that the largest volume of jobs available is for that combination. Let’s finally suppose that for whatever reason, you strongly dislike JavaScript and enjoyed building a website using PHP. </p>
<p>You will find voices who will tell you that PHP is a dead language and a dead end for job searching. </p>
<p>But if you go looking, you may notice that there is a good supply of job postings out there looking for PHP developers who can expand and maintain existing codebases. You might also have a much better chance of getting an interview because <em>the ratio of job postings to applications is significantly better for PHP developers</em> than JavaScript developers. In fact, my team recently hired a PHP developer!</p>
<h3 id="heading-hardware-amp-working-with-what-you-have">Hardware &amp; Working With What You Have</h3>
<p>This section is largely irrelevant to web developers, but may be extremely important for those looking to target specific hardware or operating systems. Simply put, if you don’t have a computer with Mac OS and XCode, you will have a very hard time developing an iOS app, for example. In my case, back in 2014, I chose Android development partly because I had studied a bit of Java – though a big consideration was that I had an Android phone. </p>
<p>There are some ways around this by paying to use a remote device (such as a remote Mac via an online service), but my experience with such services years ago is that they weren’t great.</p>
<p>Think about what resources you have and how that fits into what you want to build or whom you want to work for. If you have not much other than a cheap computer with a web browser, and you still want to build GUI applications, web development can be a great choice.</p>
<h3 id="heading-what-if-i-want-the-ai-to-code-for-me">What if I Want the AI to Code For Me?</h3>
<p>While this topic is worthy of a separate article, I don’t believe this is an unreasonable question to ask. A year ago, I would have told you that at best, the AI can write some basic code and help you learn some things which may or may not be wrong. </p>
<p>How things have changed! Though I would be bad at my job if I copy pasted code I didn’t understand or didn’t test, AI has absolutely become a force multiplier for me as a developer. </p>
<p>Back to the topic in question, how does this relate to choosing a programming language? Well, after telling you that popularity is not always a big deal, by the nature of how LLMs work, popularity is a factor. In terms of general use, languages like Python, JavaScript, and Java are likely to have the largest amount of training data. My experience has been that the languages I typically use, such as Kotlin, TypeScript, and Swift also do fine.</p>
<p>But there is a curious side effect of developing Android or iOS applications that I don’t experience so much in web development. The nature of these constantly changing platforms and SDKs, with tens of thousands of third party libraries, dozens of architectures, and endless opinions about best practices and anti-patterns, means that LLMs can have serious troubles with complexity or specificity. </p>
<p>I expect this issue to be fixed as LLM services improve correctness checking or other methods to reduce hallucinations.</p>
<h3 id="heading-avoid-the-sunk-cost-fallacy">Avoid the Sunk-Cost Fallacy</h3>
<p>Perhaps the most important point I can make in choosing a programming language is to avoid the sunk-cost fallacy. For the first several years of my part-time studies, I didn’t imagine learning a second language based on how difficult it was for me to learn Java.</p>
<p>Roughly 12 years later, I have written non-trivial code in Java, Kotlin, Swift, C++, TypeScript, and SQL. Further, I have dabbled with code in C, Python, JavaScript, Racket, Haskell, Objective C, Visual Basic, and C#. </p>
<p>It was not the case that I sought out to learn all of these things artificially – I don’t tend to learn things outside of the problems in front of me. It’s that these learning opportunities naturally unfolded along with my personal and professional interests. </p>
<p>Learning the fundamentals or approaching mastery of any general purpose programming language will have carry over to others. It’s true that someone learning Python or JavaScript without CS fundamentals is not going to have much of a clue how things work at the OS level or lower. </p>
<p>It’s also true that I have met several people who could probably code circles around me in C/C++/Assembly but never made it past building toy programs in University or College. </p>
<p>Just keep learning and try to find a balance between personal interest and professional goals.</p>
<h2 id="heading-how-to-find-the-best-libraries-and-frameworks">How to Find the Best Libraries and Frameworks</h2>
<p>The next few topics revolve around a question which we’ll revisit a couple of times before the end of this article: “<em>Does it solve more problems than it creates?</em>”</p>
<h3 id="heading-what-are-libraries-and-frameworks">What Are Libraries and Frameworks?</h3>
<p>Before we proceed, here’s a useful but not definitive definition about the relationship between libraries and frameworks. You’ll find other definitions, but there’s remarkably little consensus on topics like this in this industry.</p>
<p>For me, a library is code which you can take from somewhere and use it to build things. It could be anything from a single line to a large and complex sub-system – usually something in between. I could give you a long and pedantic definition, but that’s not appropriate for this context (suitability!). </p>
<p>One example could be Java’s Math (java.lang.Math) library, which provides you with the following: “<em>The class Math contains methods for performing basic numeric operations such as the elementary exponential, logarithm, square root, and trigonometric functions.</em>”</p>
<p>Some people use the term framework interchangeably with library, and I don’t have any problems with that. When I think of a framework, I’m thinking about something which you build stuff around and is not necessarily to do with solving a specific problem domain (such as mathematics). </p>
<p>An example of this would be RxJava, which is a rather complex framework you can use to bind together and manage data flows across an entire application. I’ve used this framework in almost a dozen applications which did very different things in principle.</p>
<p>I do consider a framework to be a library, fundamentally – they just have a different set of goals and often a larger footprint.</p>
<h3 id="heading-how-to-choose-libraries-and-frameworks">How to Choose Libraries and Frameworks</h3>
<p>When I think about choosing libraries and frameworks, I ask myself these questions:</p>
<ul>
<li><p>Does it solve more problems than it creates compared to writing my own solution?</p>
</li>
<li><p>Is it well-maintained (regularly worked on, responsive authors, backed by tech companies)?</p>
</li>
<li><p>Does it have good documentation (less of a problem now that we can leverage AI for this purpose)?</p>
</li>
<li><p>What kind of footprint does it have?</p>
</li>
</ul>
<p>Let’s take two examples. I won’t refer to the specific platform or name of these libraries to avoid offending anyone. But they were/are both used in mobile development (though they are solving common GUI problems on any platform).</p>
<p>Firstly, one of my favorite libraries had one job: It loads images into the UI. </p>
<p>Although devices are more powerful than they used to be, it can still be a problem to load large images on smart phones for display. Mobile operating systems can be aggressive about killing programs (that is, processes) which use up too much of the system’s resources.</p>
<p>This library handles all aspects of loading images that I’m concerned about:</p>
<ul>
<li><p>Loading the image into a particular widget</p>
</li>
<li><p>Displaying an appropriate loading indicator </p>
</li>
<li><p>Displaying an optional error or fallback state that tells the user something went wrong</p>
</li>
<li><p>Handling the complexities of asynchronously loading in (via URL/URI), processing, and compressing potentially large streams of bits (that is, image data)</p>
</li>
<li><p>Doesn’t inflate the packaged application’s size unnecessarily</p>
</li>
<li><p>Doesn’t change its public API frequently (think changing function names which cause people’s implementations to break when updating versions)</p>
</li>
<li><p>It solves problems that I’m not interested in solving</p>
</li>
</ul>
<p>Secondly, one of my least favorite libraries also had one job: Pagination. Pagination, or paging, in this case refers to loading data in <em>chunks</em> into an application. This is an extremely common pattern in shopping cart or social media applications. </p>
<p>The library I am thinking of approaches that problem like so:</p>
<ul>
<li><p>Tightly couples every layer of your client application (from front end to back end) to its dependencies</p>
</li>
<li><p>This tight coupling makes testing difficult without jumping through some hoops</p>
</li>
<li><p>Handles the core problem of pagination well unless you need customization or specialized cases</p>
</li>
<li><p>Frequently changed its public facing API</p>
</li>
<li><p>Solved (in general) a problem which I am quite happy to write my own solution for</p>
</li>
<li><p>Did not play well with other frameworks due to a restrictive set of types and lack of flexibility</p>
</li>
<li><p>Was constantly updated for a couple of years then ditched and marked deprecated</p>
</li>
<li><p>Did not inflate the packaged applications size too much but certainly more than my own solution would</p>
</li>
</ul>
<p>As you can see, even something which solves a core problem reasonably well, can still fail this simple test of: Does it solve more problems than it creates? Having written pagination code by myself on a couple of occasions now, I would have to be pretty strongly convinced not to. </p>
<h2 id="heading-how-to-find-the-best-programming-principles-and-practices">How to Find the Best Programming Principles and Practices</h2>
<p>There are more best practices and principles than I care to describe in detail. What I will do is explain why I treat programming principles as being distinct from immutable/unbreakable laws. Similarly to my goal of finding the best programming language, I wanted to find the best principles in order to write the best code. </p>
<p>The problem is that any programming principle I’ve come across has also been subject to the Law of Suitability. I’ll discuss one example from personal experience and point out that the question we asked above, “does it solve more problems than creates,” also applies here.</p>
<h3 id="heading-dry-dont-repeat-yourself">D.R.Y – Don’t Repeat Yourself</h3>
<p>This principle can be summarized with the idea that if you find duplicated code, you should pull it into a separate module (file, function, class, library, and so on). Without getting into the weeds, the act of pulling the duplicated code into a separate module can be thought of as a process of abstraction. </p>
<p>To be fair to the creators and proponents of this idea, it’s more nuanced than that. But many developers never bother to dig that deep into nuances – nor should they have to. I ran into the nuances simply by applying this idea more than I should have.</p>
<p>There a couple of cases where code duplication is sometimes preferable:</p>
<ul>
<li><p>You have a set of similar modules (say similar widgets or business rules) but they get used in different places for different reasons</p>
</li>
<li><p>You have a set of similar modules which might change for different reasons (for example, rapidly changing demands from product teams and clients with different priorities) </p>
</li>
<li><p>You’re deliberately grouping certain modules that work together in distinct packages, files, or directories to insulate modules/groupings from affecting each other</p>
</li>
<li><p>You find that you need to add details about one particular implementation into your abstraction, but those details don’t apply to other implementations (that is, it’s a bad abstraction)</p>
</li>
</ul>
<p>All of the things I listed above are summaries of things I have run into in the past. The key take away is that I broadly agree with avoiding code duplication. I also know of some cases where I prefer it. Suitability!</p>
<h3 id="heading-what-about-other-programming-principles">What About Other Programming Principles?</h3>
<p>In general, you can think of all programming principles like YAGNI, DRY, SRP (and other aspects of SOLID), and even software development methodologies like AGILE and Waterfall in the same way. Contextually, you can use them as guidelines to help avoid some common problems. But a person of sufficient creativity and experience can come up with a situation where following any of these principles creates more problems than it solves.</p>
<p>In many cases, you need to apply these things too much in order to understand what too much means in practical terms. Just be careful not to swing too far in the other direction when one of these principles really breaks down in front of you. I’ve also made that mistake and had to re-adjust.</p>
<p>To date, I haven’t come across a programming principle which is universally true. There are some which come close to that, but I can always imagine a situation where they’re not the best approach. Take a good one like: Always write the simplest code you can write. In other words, don’t add extra complexity without a reason. </p>
<p>Well, suppose you have a not great value system or incentive structure which encourages inflating your work artificially. Need I say more?</p>
<h2 id="heading-a-note-about-patterns-and-architectures">A Note About Patterns and Architectures</h2>
<p>I’ll now discuss the topic of patterns and architectures in software systems with respect to the Law of Suitability. Software architecture is the only thing I consider myself an expert in, and I have read multiple books on design patterns. I always try to provide some useful info on these topics when I get the chance.</p>
<h3 id="heading-how-to-find-the-best-software-architecture">How to Find the Best Software Architecture</h3>
<p>To summarize entire articles, courses, and public talks I have given on this topic: The best software architecture depends on project and personal requirements. </p>
<p>One way to grasp the main idea is to ask yourself whether the best architecture for a hospital is also a good fit for a 2-bedroom apartment. The obvious answer is that we might expect a few commonalities (doors, windows, bathrooms of some kind, and so on) among these different sets of requirements. But the ideal, or even just a good architecture for a 2 bedroom apartment cannot possibly be the same for a hospital. </p>
<p>In short, you will never find an architecture which works well for all projects and requirements.</p>
<p>Here is a list of architectures I have some familiarity with:</p>
<ul>
<li><p>Model-View-Controller</p>
</li>
<li><p>Model-View-Presenter</p>
</li>
<li><p>Model-View-ViewModel</p>
</li>
<li><p>VIPER</p>
</li>
<li><p>Clean Architecture (Robert C. Martin style)</p>
</li>
<li><p>Model-View-Intent</p>
</li>
</ul>
<p>To make matters more confusing, there are multiple different ways to implement these architectures – almost as many ways as developers implementing them! M-V-VM is one of the more common architectures in mobile development, and I can think of at least five different variations on how to achieve what some people think of as a single architecture.</p>
<p>Here are my general suggestions for working with these architectures:</p>
<ul>
<li><p>Be wary of adding unnecessary complexity with the more complex architectures (particularly Clean Architecture, as many people get this horribly wrong)</p>
</li>
<li><p>Don’t try to make the project requirements fit the architecture – work the other way around (the best indicator for this is noticing that something you’re trying to implement is made unnecessarily difficult because of the architecture you’re using)</p>
</li>
<li><p>Don’t be afraid of applying different approaches in different features of the same application instead of blindly applying the same pattern just for consistency’s sake</p>
</li>
</ul>
<h3 id="heading-the-design-pattern-trap">The Design Pattern Trap</h3>
<p>One of the most common engagement farming tactics I see on social media is to post lists of design patterns “that you must know” in order to get a job or to scare junior programmers into buying your low-quality, copy-pasted content of each pattern.</p>
<p>Don’t get me wrong, I loved studying design patterns and I use a couple key patterns in most GUI applications I build. The Observer (a.k.a. Publisher-Subscriber or Pub-Sub) Pattern really shines when you need to glue together a bunch of asynchronous data sources. I love seeing library developers give me a nice Builder Pattern to work with their APIs. I think understanding the basics of patterns like the Bridge or Facade can teach you how to hide details behind abstractions, which is actually simpler than the big scary words describing these things make it sound.</p>
<p>But I spend very little time in my day to day work thinking about or in design patterns. Instead, I’m always thinking about the kinds of attitudes and principles that give rise to these patterns: </p>
<ul>
<li><p>Promoting loosely coupled code (separating the creation and usage of dependencies and parameters, reasonable usage of abstraction)</p>
</li>
<li><p>Writing classes, interfaces, protocols, and functions which do one thing (though this “one thing” might be a macroscopic goal instead of a microscopic operation)</p>
</li>
<li><p>Avoiding complexity wherever possible (a common source of this complexity is over-use of abstractions)</p>
</li>
<li><p>Not pretending that every complex problem has a simple solution (that is, as simple as it can be but no simpler)</p>
</li>
<li><p>Avoiding pre-mature optimization</p>
</li>
</ul>
<p>Again, I will only apply these principles and attitudes to the extent that I find they solve more problems than they create. Design patterns, when applied too rigorously, can break many of those principles – particularly when it comes to avoiding complexity and pre-mature optimization. </p>
<p>Don’t try to make your project requirements fit your patterns. Instead, think about which patterns might suit your project requirements and deviate as necessary.</p>
<h2 id="heading-summary">Summary</h2>
<p>My goal with this piece was to provide three things:</p>
<ul>
<li><p>A practical overview of choosing a programming language and avoiding the traps the people can fall into when navigating these sorts of topics</p>
</li>
<li><p>A philosophical but pragmatic framework which you can use to evaluate the suitability of anything – with emphasis on learning and developing software</p>
</li>
<li><p>A breakdown of how I approach other topics like tools, architectures, and patterns</p>
</li>
</ul>
<p>While it can be important to consider things like job opportunities and your current hardware, don’t discount personal interest as a driving factor. From what little I remember about studying cognition (learning how to learn), interest is tightly coupled to motivation and memory. We can’t always exclusively do what interests us, but I suggest you look for intersections between personal and practical concerns as often as you can. </p>
<p>In closing, I encourage you to think about other areas where you might be able to explore the principles of suitability and the problems of tribalistic thinking. Change is constant and value is relative.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Zero-Trust Authentication in Your Web Apps ]]>
                </title>
                <description>
                    <![CDATA[ Your biggest security problem might be inside your own network. Hackers don't break in anymore - they just log in with stolen passwords. Old security systems trusted anyone who got inside the network. But now there's no clear "inside" or "outside." P... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-zero-trust-authentication-in-your-web-apps/</link>
                <guid isPermaLink="false">6893afcc4ff769448b46934a</guid>
                
                    <category>
                        <![CDATA[ zerotrust ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ authentication ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #cybersecurity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tope Fasasi ]]>
                </dc:creator>
                <pubDate>Wed, 06 Aug 2025 19:41:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754503273007/1b04e262-05de-4fac-be47-56c01eb44446.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Your biggest security problem might be inside your own network. Hackers don't break in anymore - they just log in with stolen passwords. Old security systems trusted anyone who got inside the network. But now there's no clear "inside" or "outside." People work from home, use cloud services, and fall for fake emails. Attackers can pretend to be real users for weeks without being caught.</p>
<p>Zero-Trust Authentication fixes this. Instead of trusting people once they log in, it checks every person, every device, and every request, every single time. The rule is simple: "Trust no one, verify everything."</p>
<p>This isn't just theory – it works. Companies using zero-trust security have smaller breaches, meet compliance rules easier, and control who sees what data. This matters because <a target="_blank" href="https://www.securityweek.com/cost-of-data-breach-in-2024-4-88-million-says-latest-ibm-study/">95% of data breaches happen due to human mistakes, and the average breach now costs $4.88 million</a>.</p>
<p>In this article, you will learn how to build a complete Zero-Trust Authentication system into your web app step by step. From multi-factor authentication (MFA) to behavioral anomaly detection, we will discuss the architecture decisions, code examples, and some real-world approaches you are likely able to implement right away.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-zero-trust-authentication">What Is Zero-Trust Authentication?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-architecture-overview">Architecture Overview</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-multi-factor-authentication-mfa">Multi-factor Authentication (MFA)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-jwt-token-management">JWT Token Management</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-session-security">Session Security</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-role-based-access-control-rbac">Role-Based Access Control (RBAC)</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-using-middleware-to-enforce-rbac">Using Middleware to Enforce RBAC</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-access-control-logic">Testing Access Control Logic</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-continuous-verification">Continuous Verification</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-behavioral-analysis">Behavioral Analysis</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-up-authentication">Step-Up Authentication</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-security-monitoring">Security Monitoring</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-automating-threat-response">Automating Threat Response</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before implementing zero-trust, make sure your stack aligns with frequent calls for token checks, volumes of logging, and the additional auth step, all without impairing system performance on the users' end.</p>
<p>You should at least have knowledge of:</p>
<ul>
<li><p>JWT and secure session handling</p>
</li>
<li><p>MFA, specifically understanding TOTP</p>
</li>
<li><p>Basic understanding of middleware design</p>
</li>
</ul>
<p>Audit your system: examine login flows, token handling, protected routes, session termination, and identify weak spots like long sessions or unprotected routes.</p>
<h2 id="heading-what-is-zero-trust-authentication">What Is Zero-Trust Authentication?</h2>
<p><a target="_blank" href="https://www.civilsdaily.com/news/what-is-zero-trust-authentication-zta/">Zero-Trust Authentication</a> (ZTA) redefines how access is granted in contemporary applications. It doesn't take network location or a single login event into account – it demands the continuous validation of an identity, context, and intent.</p>
<p>Whereas perimeter-based models consider anyone inside a network "safe," zero-trust presumes every request can be compromised. This means that access decisions are made in real time over verified identity, device posture, and behavioral signals. In short, it’s a "security-first" approach designed for a cloud-native, threat-aware world.</p>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<p>Building a ZTA system means checking everyone and everything, all the time. The architecture you can see below demonstrates this "never trust, always verify" approach in action:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752183554393/4cfda450-14d8-49e3-944b-a0e4654a3dcc.png" alt="Zero Trust Security architecture diagram showing trust boundary encompassing internal network components, with external cloud services and internet connections, illustrating key zero trust principles" class="image--center mx-auto" width="779" height="401" loading="lazy"></p>
<p>Image source: <a target="_blank" href="https://www.civilsdaily.com/news/what-is-zero-trust-authentication-zta/">civilsdaily</a></p>
<p>Here's how it works:</p>
<ul>
<li><p>Every request gets checked: When anyone tries to access your network (from office, home, or mobile), they hit the authentication layer first. No exceptions.</p>
</li>
<li><p>Identity + context verification: The system doesn't just check passwords. It looks at who you are, what device you're using, where you're connecting from, and what you're trying to access.</p>
</li>
<li><p>Continuous protection: Once inside, the system keeps watching. It protects your data, devices, networks, people, and workloads through constant monitoring and access controls.</p>
</li>
<li><p>The big change: Traditional security created a "trusted inside" and "untrusted outside." Zero-trust eliminates this boundary. Whether you're connecting to cloud services (AWS, Office 365) or internal systems, every request goes through the same verification process.</p>
</li>
</ul>
<h2 id="heading-multi-factor-authentication-mfa">Multi-factor Authentication (MFA)</h2>
<p><a target="_blank" href="https://support.microsoft.com/en-gb/topic/what-is-multifactor-authentication-e5e39437-121c-be60-d123-eda06bddf661">MFA</a> is the foundation of zero-trust security. It requires users to prove who they are with multiple pieces of evidence before getting access. In ZTA, even the strongest password isn't enough on its own.</p>
<p>To begin, start with a strong password, then add a second factor. For example, <a target="_blank" href="https://en.wikipedia.org/wiki/Time-based_one-time_password">Time-based One-Time Password (TOTP)</a> is the most secure. TOTP is the best second factor because it works offline and doesn't rely on SMS or email (which can be intercepted). Apps like Google Authenticator generate a new code every 30 seconds.</p>
<p>Here’s an example of what that would look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> speakeasy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'speakeasy'</span>);
<span class="hljs-keyword">const</span> QRCode = <span class="hljs-built_in">require</span>(<span class="hljs-string">'qrcode'</span>);

<span class="hljs-comment">// Generate TOTP secret for new user</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateTOTPSecret</span>(<span class="hljs-params">userEmail</span>) </span>{
  <span class="hljs-keyword">const</span> secret = speakeasy.generateSecret({
    <span class="hljs-attr">name</span>: userEmail,
    <span class="hljs-attr">issuer</span>: <span class="hljs-string">'YourApp'</span>,
    <span class="hljs-attr">length</span>: <span class="hljs-number">32</span>
  });

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">secret</span>: secret.base32,
    <span class="hljs-attr">qrCodeUrl</span>: secret.otpauth_url
  };
}
</code></pre>
<p>When a new user signs up, this function creates a unique secret key just for them. The <code>name</code> is their email, <code>issuer</code> is your app name, and <code>length: 32</code> makes it extra secure. It returns two things: the secret key (in base32 format) and a special URL that creates a QR code for easy setup.</p>
<p>To verify the code from their app, you check it against the stored secret:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Verify TOTP token</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyTOTP</span>(<span class="hljs-params">token, secret</span>) </span>{
  <span class="hljs-keyword">return</span> speakeasy.totp.verify({
    <span class="hljs-attr">secret</span>: secret,
    <span class="hljs-attr">token</span>: token,
    <span class="hljs-attr">window</span>: <span class="hljs-number">2</span>,
    <span class="hljs-attr">encoding</span>: <span class="hljs-string">'base32'</span>
  });
}
</code></pre>
<p>When the user enters their 6-digit code, this function checks if it's correct. The <code>window: 2</code> is smart – it allows for timing differences (like if their phone clock is slightly off). It returns true if the code is valid, false if not.</p>
<p>SMS verification can serve as a backup option. It’s less secure than TOTP but can work as a backup. Always limit how many SMS codes someone can request to prevent abuse:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// SMS verification with rate limiting</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendSMSVerification</span>(<span class="hljs-params">phoneNumber, userId</span>) </span>{
  <span class="hljs-keyword">const</span> attempts = <span class="hljs-keyword">await</span> getRecentSMSAttempts(userId);
  <span class="hljs-keyword">if</span> (attempts &gt;= <span class="hljs-number">3</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Too many SMS attempts. Please try again later.'</span>);
  }

  <span class="hljs-keyword">const</span> code = generateRandomCode(<span class="hljs-number">6</span>);
  <span class="hljs-keyword">await</span> storeSMSCode(userId, code, <span class="hljs-number">300</span>); <span class="hljs-comment">// 5-minute expiry</span>

  <span class="hljs-keyword">await</span> smsProvider.send(phoneNumber, <span class="hljs-string">`Your verification code: <span class="hljs-subst">${code}</span>`</span>);
}
</code></pre>
<p>Before sending an SMS, it checks how many times this user has already requested codes. If they've tried 3 times, it blocks them (prevents spam/abuse). If they're under the limit, it creates a random 6-digit code, saves it for 5 minutes (300 seconds), then sends it via SMS.</p>
<p>But what happens if a user loses their phone or authenticator app? Backup codes provide emergency access:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Generate backup codes</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">generateBackupCodes</span>(<span class="hljs-params">userId</span>) </span>{
  <span class="hljs-keyword">const</span> codes = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">10</span>; i++) {
    codes.push(generateRandomCode(<span class="hljs-number">8</span>));
  }

  <span class="hljs-keyword">const</span> hashedCodes = codes.map(<span class="hljs-function"><span class="hljs-params">code</span> =&gt;</span> hashCode(code));
  storeBackupCodes(userId, hashedCodes);

  <span class="hljs-keyword">return</span> codes; <span class="hljs-comment">// Only show to user once</span>
}
</code></pre>
<p>This creates 10 emergency backup codes (each 8 characters long). The <code>for</code> loop runs 10 times, creating a new random code each time. Before storing them in the database, it "hashes" them (scrambles them for security). Then it returns the original codes to show the user once, but stores the scrambled versions so even if someone hacks your database, they can't see the real codes.</p>
<h2 id="heading-jwt-token-management">JWT Token Management</h2>
<p>JSON Web Tokens (JWTs) are stateless authentication in a zero-trust system. Using them safely is critical because you need to carefully think through payload design, implement short expiration policies, and implement token rotation and blocklisting that could prevent token theft, token reuse, or privilege escalation.</p>
<p>Let's walk through how to securely implement and manage JWTs in your web application.</p>
<p>First, define a minimal and secure structure for your access tokens. Only add information that’s necessary for making authorization decisions, and never put anything sensitive even if it is encrypted.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// JWT payload structure</span>
<span class="hljs-keyword">const</span> tokenPayload = {
  <span class="hljs-attr">sub</span>: userId,           <span class="hljs-comment">// Subject (user ID)</span>
  <span class="hljs-attr">email</span>: userEmail,      <span class="hljs-comment">// User identifier</span>
  <span class="hljs-attr">roles</span>: userRoles,      <span class="hljs-comment">// User roles array</span>
  <span class="hljs-attr">permissions</span>: userPermissions, <span class="hljs-comment">// Specific permissions</span>
  <span class="hljs-attr">iat</span>: <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>), <span class="hljs-comment">// Issued at</span>
  <span class="hljs-attr">exp</span>: <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>) + <span class="hljs-number">900</span>, <span class="hljs-comment">// Expires in 15 minutes</span>
  <span class="hljs-attr">jti</span>: generateUniqueId(), <span class="hljs-comment">// JWT ID for blocklisting</span>
  <span class="hljs-attr">aud</span>: <span class="hljs-string">'your-app'</span>,       <span class="hljs-comment">// Audience</span>
  <span class="hljs-attr">iss</span>: <span class="hljs-string">'your-auth-service'</span> <span class="hljs-comment">// Issuer</span>
};
</code></pre>
<p>In the code above, the payload consists of the user identity, roles, permissions, and metadata such as the issued time (<code>iat</code>), expiration (<code>exp</code>), and unique token ID (<code>jti</code>). While <code>aud</code> and <code>iss</code> describe the token's origin and audience for validation, <code>jti</code> is used for revocation. Thus, it keeps the payload as lean as possible to minimize exposure and overhead.</p>
<p>For security and usability, it’s better to use access tokens with a short lifespan and refresh tokens with a considerably longer duration, which minimizes the window for potential utilization of compromised tokens while providing a smooth user session.</p>
<p>Let's take this example:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Token generation service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TokenService</span> </span>{
  generateTokenPair(user) {
    <span class="hljs-keyword">const</span> accessToken = jwt.sign(
      <span class="hljs-built_in">this</span>.createAccessTokenPayload(user),
      process.env.JWT_SECRET,
      { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'15m'</span>, <span class="hljs-attr">algorithm</span>: <span class="hljs-string">'HS256'</span> }
    );

    <span class="hljs-keyword">const</span> refreshToken = jwt.sign(
      { <span class="hljs-attr">sub</span>: user.id, <span class="hljs-attr">type</span>: <span class="hljs-string">'refresh'</span> },
      process.env.REFRESH_SECRET,
      { <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'7d'</span>, <span class="hljs-attr">algorithm</span>: <span class="hljs-string">'HS256'</span> }
    );

    <span class="hljs-keyword">return</span> { accessToken, refreshToken };
  }

  <span class="hljs-keyword">async</span> refreshAccessToken(refreshToken) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);

      <span class="hljs-comment">// Check if refresh token is blocklisted</span>
      <span class="hljs-keyword">if</span> (<span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.isTokenBlocklisted(decoded.jti)) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Token has been revoked'</span>);
      }

      <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(decoded.sub);
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.generateTokenPair(user);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Invalid refresh token'</span>);
    }
  }
}
</code></pre>
<p><code>generateTokenPair</code> will generate two signed JWTs – that is, an access token with a 15-minute expiration and a refresh token with a validity of 7 days. The refresh tokens are verified to grant new ones and are checked against a blocklist. This ensures that revoked tokens can’t be reused, even if they’re still technically valid.</p>
<p>If you choose, a sliding session can be implemented to reduce friction by renewing tokens for an active user without violating your expiration strategy.</p>
<p>Now, let's implement a <a target="_blank" href="https://stackoverflow.com/questions/48189866/sliding-session-on-web-api-request">sliding session</a> that automatically refreshes JWTs when they're close to expiring and the user is still active.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Sliding session implementation</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">extendSessionIfActive</span>(<span class="hljs-params">token</span>) </span>{
  <span class="hljs-keyword">const</span> decoded = jwt.decode(token);
  <span class="hljs-keyword">const</span> timeUntilExpiry = decoded.exp - <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Date</span>.now() / <span class="hljs-number">1000</span>);

  <span class="hljs-comment">// If token expires within 5 minutes and user is active, refresh</span>
  <span class="hljs-keyword">if</span> (timeUntilExpiry &lt; <span class="hljs-number">300</span> &amp;&amp; <span class="hljs-keyword">await</span> isUserActive(decoded.sub)) {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(decoded.sub);
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.generateTokenPair(user);
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}
</code></pre>
<p>The above function checks for token expiration. If the token expires within 5 minutes and the user continues to interact, a new access token pair is issued. This way, the session is kept alive during real activity but still forces expiration for idle users.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Token blocklist service</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TokenBlocklistService</span> </span>{
  <span class="hljs-keyword">async</span> blocklistToken(token) {
    <span class="hljs-keyword">const</span> decoded = jwt.decode(token);
    <span class="hljs-keyword">const</span> expiresAt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(decoded.exp * <span class="hljs-number">1000</span>);

    <span class="hljs-comment">// Store in Redis with automatic expiry</span>
    <span class="hljs-keyword">await</span> redis.setex(
      <span class="hljs-string">`blocklist:<span class="hljs-subst">${decoded.jti}</span>`</span>,
      <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, <span class="hljs-built_in">Math</span>.floor((expiresAt - <span class="hljs-built_in">Date</span>.now()) / <span class="hljs-number">1000</span>)),
      <span class="hljs-string">'revoked'</span>
    );
  }

  <span class="hljs-keyword">async</span> isTokenBlocklisted(jti) {
    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> redis.get(<span class="hljs-string">`blocklist:<span class="hljs-subst">${jti}</span>`</span>);
    <span class="hljs-keyword">return</span> result !== <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<p>In the above code, when users log out or tokens are compromised, the <code>jti</code> is stored in <a target="_blank" href="https://redis.io/docs/latest/">Redis</a> with an expiration time of the remaining life of the token. You can block future uses of a token by checking if its ID exists on the blocklist. This allows for instant invalidation, even though JWTs are stateless.</p>
<h2 id="heading-session-security">Session Security</h2>
<p>In zero-trust environments, <a target="_blank" href="https://www.descope.com/learn/post/session-management">session management</a> goes far beyond keeping users logged in. A session must be treated as a constantly evaluated contract between the user, their device, and the system – and should be revoked the moment trust breaks down.</p>
<p>Here, we’ll build a session system that incorporates adaptive <a target="_blank" href="https://www.prove.com/blog/trust-score">trust scoring</a>, dynamic timeouts, real-time visibility, and <a target="_blank" href="https://www.researchgate.net/publication/354720916_Revocation_Mechanisms_for_Blockchain_Applications_A_Review">revocation mechanisms</a> – all aligned with zero-trust principles.</p>
<p>For example, when a user successfully authenticates, you don’t just store a session ID. Instead, you collect contextual metadata to evaluate ongoing risk. The function below demonstrates how to initialize a session that’s both secure and context-aware.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Comprehensive session creation</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createSecureSession</span>(<span class="hljs-params">userId, deviceInfo, clientInfo</span>) </span>{
  <span class="hljs-keyword">const</span> sessionId = generateSecureSessionId();

  <span class="hljs-keyword">const</span> session = {
    <span class="hljs-attr">id</span>: sessionId,
    <span class="hljs-attr">userId</span>: userId,
    <span class="hljs-attr">deviceFingerprint</span>: generateDeviceFingerprint(deviceInfo),
    <span class="hljs-attr">ipAddress</span>: clientInfo.ipAddress,
    <span class="hljs-attr">userAgent</span>: clientInfo.userAgent,
    <span class="hljs-attr">location</span>: <span class="hljs-keyword">await</span> resolveLocation(clientInfo.ipAddress),
    <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
    <span class="hljs-attr">lastActivity</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
    <span class="hljs-attr">trustScore</span>: calculateInitialTrustScore(deviceInfo, clientInfo),
    <span class="hljs-attr">securityLevel</span>: determineSecurityLevel(userId, deviceInfo)
  };

  <span class="hljs-keyword">await</span> storeSession(session);
  <span class="hljs-keyword">return</span> session;
}
</code></pre>
<p>Many other tools are tracking concerning details during session creation. The device fingerprint, IP address, geolocation, and browser agent data are collected. These metadata are used to compute a trust score, and finally, a security level is assigned to the session to be used for dynamically adjusting policies later.</p>
<p>With this contextual information captured during session creation, the system can spot suspicious behavior during the sessions and, in turn, adapt policies like re-authentication of users or termination of the session.</p>
<p>Not all sessions should be treated equally. If a user logs in via an unfamiliar device or risky location, they should have less time for their session lifespan compared to a trusted setup's time. The following implementation changes timeout periods on the basis of trust and risk factors:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Adaptive session timeout</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SessionTimeoutManager</span> </span>{
  calculateTimeoutPeriod(session) {
    <span class="hljs-keyword">const</span> baseTimeout = <span class="hljs-number">30</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">// 30 minutes</span>
    <span class="hljs-keyword">const</span> trustMultiplier = session.trustScore / <span class="hljs-number">100</span>;
    <span class="hljs-keyword">const</span> securityMultiplier = <span class="hljs-built_in">this</span>.getSecurityMultiplier(session.securityLevel);

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.max(
      <span class="hljs-number">5</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// Minimum 5 minutes</span>
      baseTimeout * trustMultiplier * securityMultiplier
    );
  }

  <span class="hljs-keyword">async</span> checkSessionValidity(sessionId) {
    <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> getSession(sessionId);
    <span class="hljs-keyword">if</span> (!session) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    <span class="hljs-keyword">const</span> now = <span class="hljs-built_in">Date</span>.now();
    <span class="hljs-keyword">const</span> timeout = <span class="hljs-built_in">this</span>.calculateTimeoutPeriod(session);

    <span class="hljs-comment">// Check both idle timeout and absolute timeout</span>
    <span class="hljs-keyword">const</span> idleExpired = (now - session.lastActivity) &gt; timeout;
    <span class="hljs-keyword">const</span> absoluteExpired = (now - session.createdAt) &gt; <span class="hljs-number">8</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>; <span class="hljs-comment">// 8 hours max</span>

    <span class="hljs-keyword">return</span> !idleExpired &amp;&amp; !absoluteExpired;
  }
}
</code></pre>
<p>The above code keeps session duration adaptable to the risk context at hand. The timeout is calculated by adjusting the base value according to trust and security level, while imposing minimum and maximum bounds.</p>
<p>The system then periodically intervenes to see if the session has become invalid due to inactivity (idle timeout) or simply outlives its initial duration (absolute timeout). This provides a more flexible yet enforceable way of mitigating the risk behind stale or hijacked sessions.</p>
<p>Zero-trust should also mean visibility across all access points. The user should be able to view all active sessions associated with their account, and security systems should also allow them to control these sessions in fine-grained detail. The following code lets you manage those active sessions across devices.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Cross-device session management</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SessionManager</span> </span>{
  <span class="hljs-keyword">async</span> getUserSessions(userId) {
    <span class="hljs-keyword">const</span> sessions = <span class="hljs-keyword">await</span> getActiveSessionsForUser(userId);

    <span class="hljs-keyword">return</span> sessions.map(<span class="hljs-function"><span class="hljs-params">session</span> =&gt;</span> ({
      <span class="hljs-attr">id</span>: session.id,
      <span class="hljs-attr">deviceType</span>: <span class="hljs-built_in">this</span>.identifyDeviceType(session.userAgent),
      <span class="hljs-attr">location</span>: session.location,
      <span class="hljs-attr">lastActivity</span>: session.lastActivity,
      <span class="hljs-attr">current</span>: session.id === currentSessionId
    }));
  }

  <span class="hljs-keyword">async</span> revokeSession(sessionId, requestingSessionId) {
    <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> getSession(sessionId);
    <span class="hljs-keyword">if</span> (!session) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Session not found'</span>);

    <span class="hljs-comment">// Verify requesting session has permission</span>
    <span class="hljs-keyword">const</span> requestingSession = <span class="hljs-keyword">await</span> getSession(requestingSessionId);
    <span class="hljs-keyword">if</span> (requestingSession.userId !== session.userId) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Unauthorized'</span>);
    }

    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.terminateSession(sessionId);
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logSecurityEvent(<span class="hljs-string">'session_revoked'</span>, session);
  }
}
</code></pre>
<p>Here, users fetch a list of their active sessions along with identifying information such as device type and location. Any session can be securely revoked by the user who owns it, preventing unauthorized access if the session ID is compromised.</p>
<p>This also allows the user to detect suspicious activities in time. All revocations are logged for auditing purposes to enable post-incident investigations as well as compliance reports.</p>
<p>When a trust breaks due to credential theft, suspicious activity, or user-level actions such as password reset, all sessions have to be immediately revoked. This example guarantees a full revocation, promptly applied to all devices:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Real-time session revocation</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SessionRevocationService</span> </span>{
  <span class="hljs-keyword">async</span> revokeAllUserSessions(userId, reason) {
    <span class="hljs-keyword">const</span> sessions = <span class="hljs-keyword">await</span> getActiveSessionsForUser(userId);

    <span class="hljs-comment">// Blocklist all tokens for this user</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(sessions.map(<span class="hljs-function"><span class="hljs-params">session</span> =&gt;</span> 
      <span class="hljs-built_in">this</span>.blocklistSessionTokens(session.id)
    ));

    <span class="hljs-comment">// Notify all active clients</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all(sessions.map(<span class="hljs-function"><span class="hljs-params">session</span> =&gt;</span> 
      <span class="hljs-built_in">this</span>.notifySessionTermination(session.id, reason)
    ));

    <span class="hljs-comment">// Clear session data</span>
    <span class="hljs-keyword">await</span> clearUserSessions(userId);

    <span class="hljs-comment">// Log security event</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logSecurityEvent(<span class="hljs-string">'all_sessions_revoked'</span>, {
      userId,
      reason,
      <span class="hljs-attr">sessionCount</span>: sessions.length
    });
  }
}
</code></pre>
<p>The above code permits full-scale revocation. It blocklists all session tokens, sends out termination notices to active clients (for example, through WebSockets), clears the session records on the server-side, and logs the event for auditing. It is an instantaneous and complete response to compromised accounts or states where user risk is very high. It is the foremost component of real-time zero-trust enforcement in any serious authentication system.</p>
<h2 id="heading-role-based-access-control-rbac">Role-Based Access Control (RBAC)</h2>
<p>Identity verification determines what users can access once they’re logged in. As the basis for any system that is aware of permissions and follows least privilege, <a target="_blank" href="https://en.wikipedia.org/wiki/Role-based_access_control">RBAC</a> doesn’t grant access on an individual basis – it groups users into roles that define the operations they are permitted to perform.</p>
<p>Before assigning roles to users, you need a structured system to define what each role can do. A set of granular permissions is first identified and then aggregated under these roles, optionally allowing inheritance and hierarchy. The code below shows how to build a basic permission system:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// RBAC permission system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PermissionSystem</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.permissions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
    <span class="hljs-built_in">this</span>.roles = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
    <span class="hljs-built_in">this</span>.roleHierarchy = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Map</span>();
  }

  <span class="hljs-comment">// Define granular permissions</span>
  definePermission(name, description, resource, action) {
    <span class="hljs-built_in">this</span>.permissions.set(name, {
      name,
      description,
      resource,
      action,
      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    });
  }

  <span class="hljs-comment">// Create role with inherited permissions</span>
  createRole(name, description, parentRole = <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">const</span> role = {
      name,
      description,
      <span class="hljs-attr">permissions</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(),
      <span class="hljs-attr">createdAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    };

    <span class="hljs-comment">// Inherit permissions from parent role</span>
    <span class="hljs-keyword">if</span> (parentRole &amp;&amp; <span class="hljs-built_in">this</span>.roles.has(parentRole)) {
      <span class="hljs-keyword">const</span> parent = <span class="hljs-built_in">this</span>.roles.get(parentRole);
      role.permissions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(parent.permissions);
      <span class="hljs-built_in">this</span>.roleHierarchy.set(name, parentRole);
    }

    <span class="hljs-built_in">this</span>.roles.set(name, role);
    <span class="hljs-keyword">return</span> role;
  }

  <span class="hljs-comment">// Add permission to role</span>
  addPermissionToRole(roleName, permissionName) {
    <span class="hljs-keyword">const</span> role = <span class="hljs-built_in">this</span>.roles.get(roleName);
    <span class="hljs-keyword">if</span> (!role) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Role not found'</span>);

    <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.permissions.has(permissionName)) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Permission not found'</span>);
    }

    role.permissions.add(permissionName);
  }
}
</code></pre>
<p>The code above lets you specify fine-grained permissions like <code>documents.read.own</code> and organizes them into roles such as <code>employee</code> or <code>manager</code> that you can independently reuse. You can define roles to inherit from other roles, which avoids redundancy and promotes a consistent, scalable access control logic.</p>
<p>As a general rule to avoid privilege creep, permissions should always be as fine-grained as possible. This lets the application refine access decisions to specific actions or scopes: for example, allowing users to read only their documents versus reading all documents for their team.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Fine-grained permission definitions</span>
<span class="hljs-keyword">const</span> permissions = {
  <span class="hljs-comment">// User management</span>
  <span class="hljs-string">'users.read'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span> },
  <span class="hljs-string">'users.create'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'create'</span> },
  <span class="hljs-string">'users.update'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'update'</span> },
  <span class="hljs-string">'users.delete'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'users'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'delete'</span> },

  <span class="hljs-comment">// Document management</span>
  <span class="hljs-string">'documents.read.own'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'own'</span> },
  <span class="hljs-string">'documents.read.team'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'team'</span> },
  <span class="hljs-string">'documents.read.all'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'all'</span> },
  <span class="hljs-string">'documents.create'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'create'</span> },
  <span class="hljs-string">'documents.update.own'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'update'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'own'</span> },
  <span class="hljs-string">'documents.delete.own'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'documents'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'delete'</span>, <span class="hljs-attr">scope</span>: <span class="hljs-string">'own'</span> },

  <span class="hljs-comment">// System administration</span>
  <span class="hljs-string">'system.logs.read'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'system'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'read'</span>, <span class="hljs-attr">subresource</span>: <span class="hljs-string">'logs'</span> },
  <span class="hljs-string">'system.config.update'</span>: { <span class="hljs-attr">resource</span>: <span class="hljs-string">'system'</span>, <span class="hljs-attr">action</span>: <span class="hljs-string">'update'</span>, <span class="hljs-attr">subresource</span>: <span class="hljs-string">'config'</span> }
};
</code></pre>
<p>With an array of permissions at its disposal, the app can undertake very precise access control decisions. Instead of merely addressing the binary "is admin" question, this capability enables the system to answer questions such as "can this user delete their own document but not others?"</p>
<p>Static roles are often insufficient. You may want to give people temporary or conditional access, for example, when the team lead takes over for a manager or when a user approves a higher access level for the sake of incident response.</p>
<p>To support these cases, the RBAC system must allow dynamic role assignment – that is, the ability to assign roles on the basis of time, context, or an external trigger such as a security workflow.</p>
<p>The code below assigns a temporary role to a user, notes the exact time at which the role was assigned to the user, and periodically revokes the right after some fixed amount of time. Also, it has a method to calculate a user's complete set of active rights, depending on their permanent rights, temporary rights, and role-based contextual rights.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Dynamic role assignment system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DynamicRoleAssignment</span> </span>{
  <span class="hljs-keyword">async</span> assignTemporaryRole(userId, roleName, duration, reason) {
    <span class="hljs-keyword">const</span> assignment = {
      userId,
      roleName,
      <span class="hljs-attr">assignedAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
      <span class="hljs-attr">expiresAt</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-built_in">Date</span>.now() + duration * <span class="hljs-number">1000</span>),
      reason,
      <span class="hljs-attr">active</span>: <span class="hljs-literal">true</span>
    };

    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.storeRoleAssignment(assignment);
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logRoleAssignment(assignment);

    <span class="hljs-comment">// Schedule automatic revocation</span>
    <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">this</span>.revokeExpiredAssignment(assignment.id);
    }, duration * <span class="hljs-number">1000</span>);

    <span class="hljs-keyword">return</span> assignment;
  }

  <span class="hljs-keyword">async</span> getUserEffectivePermissions(userId, context = {}) {
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(userId);
    <span class="hljs-keyword">const</span> permanentRoles = user.roles || [];
    <span class="hljs-keyword">const</span> temporaryRoles = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getActiveTemporaryRoles(userId);
    <span class="hljs-keyword">const</span> contextualRoles = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getContextualRoles(userId, context);

    <span class="hljs-keyword">const</span> allRoles = [...permanentRoles, ...temporaryRoles, ...contextualRoles];
    <span class="hljs-keyword">const</span> permissions = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> roleName <span class="hljs-keyword">of</span> allRoles) {
      <span class="hljs-keyword">const</span> rolePermissions = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getRolePermissions(roleName);
      rolePermissions.forEach(<span class="hljs-function"><span class="hljs-params">permission</span> =&gt;</span> permissions.add(permission));
    }

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Array</span>.from(permissions);
  }
}
</code></pre>
<p>This allows for more flexible security configurations. Temporary roles that are granted have an automatic expiration. The context roles may be added dynamically depending on contextual factors such as location or type of device. Permanent roles are combined with temporary and context roles to compute the aggregate permission set for the user on a per-request basis, which maintains flexibility without compromising control.</p>
<h3 id="heading-using-middleware-to-enforce-rbac">Using Middleware to Enforce RBAC</h3>
<p>The RBAC policies have to be enforced before any request reaches a protected route or protected data. <a target="_blank" href="https://aws.amazon.com/what-is/middleware/">Middleware</a> is a good place to run such checks in the scope of a web application. We’ll now look into how the reusable middleware function for authorization works.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Authorization middleware</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createAuthorizationMiddleware</span>(<span class="hljs-params">requiredPermission</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">async</span> (req, res, next) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Extract user from validated JWT</span>
      <span class="hljs-keyword">const</span> user = req.user;
      <span class="hljs-keyword">if</span> (!user) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Authentication required'</span> });
      }

      <span class="hljs-comment">// Get user's effective permissions</span>
      <span class="hljs-keyword">const</span> context = {
        <span class="hljs-attr">ipAddress</span>: req.ip,
        <span class="hljs-attr">userAgent</span>: req.get(<span class="hljs-string">'User-Agent'</span>),
        <span class="hljs-attr">resourceId</span>: req.params.id,
        <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
      };

      <span class="hljs-keyword">const</span> permissions = <span class="hljs-keyword">await</span> roleSystem.getUserEffectivePermissions(
        user.id,
        context
      );

      <span class="hljs-comment">// Check if user has required permission</span>
      <span class="hljs-keyword">if</span> (!permissions.includes(requiredPermission)) {
        <span class="hljs-keyword">await</span> logUnauthorizedAccess(user.id, requiredPermission, context);
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">403</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Insufficient permissions'</span> });
      }

      <span class="hljs-comment">// Add permissions to request for downstream use</span>
      req.userPermissions = permissions;
      next();
    } <span class="hljs-keyword">catch</span> (error) {
      res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">'Authorization check failed'</span> });
    }
  };
}

<span class="hljs-comment">// Usage in routes</span>
app.get(<span class="hljs-string">'/api/users'</span>, 
  authenticateToken,
  createAuthorizationMiddleware(<span class="hljs-string">'users.read'</span>),
  getUsersController
);
</code></pre>
<p>In the code above, the middleware will validate user identities in real-time, check if adequate permissions are granted, and allow or deny access accordingly. It’s a central mechanism for enforcing access rules in a uniform way across your routes, and it even records unauthorized attempts for auditing.</p>
<h3 id="heading-testing-access-control-logic">Testing Access Control Logic</h3>
<p>Once you’ve implemented the RBAC system, testing becomes a must. You want to guarantee that permissions are inherited properly, that access is actually denied when a user isn’t authorized, and that your roles behave as designed in the real world as well as in edge-case scenarios.</p>
<p>The following example uses a testing framework to demonstrate the verification of two fundamental behaviors: inheritance of permissions from parent roles and rejection of unauthorized access.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// RBAC testing suite</span>
describe(<span class="hljs-string">'RBAC System'</span>, <span class="hljs-function">() =&gt;</span> {
  test(<span class="hljs-string">'should inherit permissions from parent roles'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> manager = <span class="hljs-keyword">await</span> roleSystem.createRole(<span class="hljs-string">'manager'</span>, <span class="hljs-string">'Team Manager'</span>, <span class="hljs-string">'employee'</span>);
    <span class="hljs-keyword">await</span> roleSystem.addPermissionToRole(<span class="hljs-string">'manager'</span>, <span class="hljs-string">'team.manage'</span>);

    <span class="hljs-keyword">const</span> permissions = <span class="hljs-keyword">await</span> roleSystem.getRolePermissions(<span class="hljs-string">'manager'</span>);
    expect(permissions).toContain(<span class="hljs-string">'documents.read.own'</span>); <span class="hljs-comment">// From employee</span>
    expect(permissions).toContain(<span class="hljs-string">'team.manage'</span>); <span class="hljs-comment">// Manager-specific</span>
  });

  test(<span class="hljs-string">'should deny access without proper permissions'</span>, <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> user = { <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>, <span class="hljs-attr">roles</span>: [<span class="hljs-string">'employee'</span>] };
    <span class="hljs-keyword">const</span> req = { user, <span class="hljs-attr">params</span>: { <span class="hljs-attr">id</span>: <span class="hljs-string">'doc123'</span> } };
    <span class="hljs-keyword">const</span> res = { <span class="hljs-attr">status</span>: jest.fn().mockReturnThis(), <span class="hljs-attr">json</span>: jest.fn() };

    <span class="hljs-keyword">const</span> middleware = createAuthorizationMiddleware(<span class="hljs-string">'documents.delete.all'</span>);
    <span class="hljs-keyword">await</span> middleware(req, res, <span class="hljs-function">() =&gt;</span> {}); <span class="hljs-comment">// Middleware call simulating request</span>

    expect(res.status).toHaveBeenCalledWith(<span class="hljs-number">403</span>);
  });
});
</code></pre>
<p>The tests represent the positive and negative validations of the access rules. The first test determines whether inherited permissions flow freely from the parent to child roles. The second test blocks any user without the required permission, returning a status code appropriately.</p>
<p>Over time, you can enrich test coverage to include temporary role assignments, contextual conditions, and session-aware behavior to alert you to any regressions before they start affecting production access.</p>
<h2 id="heading-continuous-verification">Continuous Verification</h2>
<p>Modern access security is not a one-shot check but an ongoing process. A strong system must continuously verify user identity and context throughout the ongoing session while adapting to newly emerging risk signals.</p>
<p>In <a target="_blank" href="https://spot.io/resources/gitops/continuous-verification/">continuous verification</a>, it’s an assurance that access stays appropriate while the user behavior, device posture, or environment changes mid-session.</p>
<p>To uniquely identify a device, you can combine subtle traits like browser settings, hardware specs, and plugin data. This forms a device “fingerprint,” which helps flag new or suspicious devices attempting access.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Advanced device fingerprinting</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DeviceFingerprintService</span> </span>{
  generateFingerprint(deviceInfo) {
    <span class="hljs-keyword">const</span> components = [
      deviceInfo.userAgent,
      deviceInfo.screenResolution,
      deviceInfo.timezone,
      deviceInfo.language,
      deviceInfo.platform,
      deviceInfo.hardwareConcurrency,
      deviceInfo.memorySize,
      deviceInfo.availableFonts?.join(<span class="hljs-string">','</span>),
      deviceInfo.plugins?.map(<span class="hljs-function"><span class="hljs-params">p</span> =&gt;</span> p.name).join(<span class="hljs-string">','</span>),
      deviceInfo.webglRenderer,
      deviceInfo.audioContext
    ];

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.hashComponents(components);
  }

  calculateTrustScore(currentFingerprint, knownFingerprints) {
    <span class="hljs-keyword">if</span> (knownFingerprints.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">50</span>; <span class="hljs-comment">// Neutral for new device</span>
    <span class="hljs-keyword">const</span> similarities = knownFingerprints.map(<span class="hljs-function"><span class="hljs-params">known</span> =&gt;</span>
      <span class="hljs-built_in">this</span>.calculateSimilarity(currentFingerprint, known)
    );
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">100</span>, <span class="hljs-built_in">Math</span>.max(...similarities) * <span class="hljs-number">100</span>);
  }

  <span class="hljs-keyword">async</span> updateDeviceTrust(userId, deviceFingerprint, securityEvents) {
    <span class="hljs-keyword">const</span> device = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getOrCreateDevice(userId, deviceFingerprint);
    <span class="hljs-keyword">let</span> trustAdjustment = <span class="hljs-number">0</span>;

    securityEvents.forEach(<span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
      <span class="hljs-keyword">switch</span> (event.type) {
        <span class="hljs-keyword">case</span> <span class="hljs-string">'successful_login'</span>: trustAdjustment += <span class="hljs-number">5</span>; <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> <span class="hljs-string">'failed_login'</span>: trustAdjustment -= <span class="hljs-number">10</span>; <span class="hljs-keyword">break</span>;
        <span class="hljs-keyword">case</span> <span class="hljs-string">'suspicious_activity'</span>: trustAdjustment -= <span class="hljs-number">25</span>; <span class="hljs-keyword">break</span>;
      }
    });

    device.trustScore = <span class="hljs-built_in">Math</span>.max(<span class="hljs-number">0</span>, <span class="hljs-built_in">Math</span>.min(<span class="hljs-number">100</span>, device.trustScore + trustAdjustment));
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.updateDevice(device);
    <span class="hljs-keyword">return</span> device.trustScore;
  }
}
</code></pre>
<p>Generating a fingerprint hash from device traits, this service uses historical events to dynamically adjust the device's trust score. Step-up authentication may be prompted by low scores, or access may be denied altogether.</p>
<h3 id="heading-behavioral-analysis">Behavioral Analysis</h3>
<p>People tend to use apps rather consistently – they type a certain way, move the mouse in a particular manner, or browse varied content. <a target="_blank" href="https://zimperium.com/glossary/behavioral-analysis">Behavioral analysis</a> tries to detect that anomaly by comparing ongoing activities to known ones.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Behavioral analysis system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BehaviorAnalysisService</span> </span>{
  <span class="hljs-keyword">async</span> analyzeUserBehavior(userId, currentSession) {
    <span class="hljs-keyword">const</span> historicalBehavior = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getUserBehaviorProfile(userId);
    <span class="hljs-keyword">const</span> anomalies = [];

    <span class="hljs-keyword">const</span> typingAnomaly = <span class="hljs-built_in">this</span>.analyzeTypingPatterns(
      currentSession.typingData,
      historicalBehavior.typingProfile
    );
    <span class="hljs-keyword">if</span> (typingAnomaly.score &gt; <span class="hljs-number">0.7</span>) {
      anomalies.push({ <span class="hljs-attr">type</span>: <span class="hljs-string">'typing_pattern'</span>, <span class="hljs-attr">score</span>: typingAnomaly.score, <span class="hljs-attr">details</span>: typingAnomaly.details });
    }

    <span class="hljs-keyword">const</span> navigationAnomaly = <span class="hljs-built_in">this</span>.analyzeNavigationPatterns(
      currentSession.navigationData,
      historicalBehavior.navigationProfile
    );
    <span class="hljs-keyword">if</span> (navigationAnomaly.score &gt; <span class="hljs-number">0.6</span>) {
      anomalies.push({ <span class="hljs-attr">type</span>: <span class="hljs-string">'navigation_pattern'</span>, <span class="hljs-attr">score</span>: navigationAnomaly.score, <span class="hljs-attr">details</span>: navigationAnomaly.details });
    }

    <span class="hljs-keyword">const</span> timeAnomaly = <span class="hljs-built_in">this</span>.analyzeTimePatterns(
      currentSession.timestamp,
      historicalBehavior.timeProfile
    );
    <span class="hljs-keyword">if</span> (timeAnomaly.score &gt; <span class="hljs-number">0.5</span>) {
      anomalies.push({ <span class="hljs-attr">type</span>: <span class="hljs-string">'time_pattern'</span>, <span class="hljs-attr">score</span>: timeAnomaly.score, <span class="hljs-attr">details</span>: timeAnomaly.details });
    }

    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">overallRiskScore</span>: <span class="hljs-built_in">this</span>.calculateOverallRisk(anomalies),
      anomalies,
      <span class="hljs-attr">recommendations</span>: <span class="hljs-built_in">this</span>.generateRecommendations(anomalies)
    };
  }

  analyzeTypingPatterns(currentData, historicalProfile) {
    <span class="hljs-keyword">if</span> (!currentData || !historicalProfile) <span class="hljs-keyword">return</span> { <span class="hljs-attr">score</span>: <span class="hljs-number">0</span> };
    <span class="hljs-keyword">const</span> dwellTimeVariance = <span class="hljs-built_in">this</span>.calculateVariance(currentData.dwellTimes, historicalProfile.averageDwellTime);
    <span class="hljs-keyword">const</span> flightTimeVariance = <span class="hljs-built_in">this</span>.calculateVariance(currentData.flightTimes, historicalProfile.averageFlightTime);
    <span class="hljs-keyword">const</span> score = <span class="hljs-built_in">Math</span>.max(dwellTimeVariance, flightTimeVariance);
    <span class="hljs-keyword">return</span> { score, <span class="hljs-attr">details</span>: { dwellTimeVariance, flightTimeVariance, <span class="hljs-attr">sampleSize</span>: currentData.keystrokes.length } };
  }
}
</code></pre>
<p>This will detect suspicious changes in user behavior and typing characteristics as early warning indicators of session hijacking or insider threat.</p>
<p>Access from a new country or city can either be harmless or highly suspicious. Comparing login geography against historical patterns helps flag impossible travel or access from banned regions.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Location-based access control</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LocationAccessControl</span> </span>{
  <span class="hljs-keyword">async</span> validateLocationAccess(userId, ipAddress, session) {
    <span class="hljs-keyword">const</span> location = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.resolveLocation(ipAddress);
    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> getUserById(userId);
    <span class="hljs-keyword">const</span> historicalLocations = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getUserLocations(userId);
    <span class="hljs-keyword">const</span> locationRisk = <span class="hljs-built_in">this</span>.assessLocationRisk(location, historicalLocations);

    <span class="hljs-keyword">const</span> lastLocation = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getLastKnownLocation(userId);
    <span class="hljs-keyword">if</span> (lastLocation) {
      <span class="hljs-keyword">const</span> impossibleTravel = <span class="hljs-built_in">this</span>.checkImpossibleTravel(lastLocation, location, session.lastActivity);
      <span class="hljs-keyword">if</span> (impossibleTravel.detected) {
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.logSecurityEvent(<span class="hljs-string">'impossible_travel'</span>, {
          userId, <span class="hljs-attr">fromLocation</span>: lastLocation, <span class="hljs-attr">toLocation</span>: location,
          <span class="hljs-attr">timeWindow</span>: impossibleTravel.timeWindow,
          <span class="hljs-attr">minimumTravelTime</span>: impossibleTravel.minimumTravelTime
        });
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'impossible_travel'</span>, <span class="hljs-attr">requiresStepUp</span>: <span class="hljs-literal">true</span> };
      }
    }

    <span class="hljs-keyword">if</span> (user.allowedCountries &amp;&amp; !user.allowedCountries.includes(location.country)) {
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'country_restriction'</span>, <span class="hljs-attr">requiresStepUp</span>: <span class="hljs-literal">true</span> };
    }

    <span class="hljs-keyword">const</span> highRiskCountries = [<span class="hljs-string">'XX'</span>, <span class="hljs-string">'YY'</span>, <span class="hljs-string">'ZZ'</span>];
    <span class="hljs-keyword">if</span> (highRiskCountries.includes(location.country)) {
      <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'high_risk_location'</span>, <span class="hljs-attr">requiresStepUp</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">additionalVerification</span>: [<span class="hljs-string">'sms'</span>, <span class="hljs-string">'email'</span>] };
    }

    <span class="hljs-keyword">return</span> { <span class="hljs-attr">allowed</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">riskScore</span>: locationRisk, location };
  }

  checkImpossibleTravel(fromLocation, toLocation, lastActivity) {
    <span class="hljs-keyword">const</span> distance = <span class="hljs-built_in">this</span>.calculateDistance(fromLocation, toLocation);
    <span class="hljs-keyword">const</span> timeElapsed = <span class="hljs-built_in">Date</span>.now() - lastActivity;
    <span class="hljs-keyword">const</span> maximumSpeed = <span class="hljs-number">900</span>; <span class="hljs-comment">// km/h</span>
    <span class="hljs-keyword">const</span> minimumTravelTime = (distance / maximumSpeed) * <span class="hljs-number">3600000</span>;
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">detected</span>: timeElapsed &lt; minimumTravelTime, <span class="hljs-attr">timeWindow</span>: timeElapsed, minimumTravelTime, distance };
  }
}
</code></pre>
<p>This logic prevents abuse via VPNs or stolen credentials by requiring step-up verification when impossible travel or unusual locations are detected.</p>
<h3 id="heading-step-up-authentication">Step-Up Authentication</h3>
<p><a target="_blank" href="https://doubleoctopus.com/security-wiki/authentication/step-up-authentication/">Step-up security</a> introduces friction only when truly needed. With lower risk considered, users move freely. When risk levels rises, they're asked for stronger proofs, such as biometrics or hardware tokens.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Step-up authentication system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StepUpAuthenticationService</span> </span>{
  <span class="hljs-keyword">async</span> evaluateStepUpRequirement(userId, requestContext, resourceSensitivity) {
    <span class="hljs-keyword">const</span> riskFactors = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.calculateRiskFactors(userId, requestContext);
    <span class="hljs-keyword">const</span> stepUpRequired = <span class="hljs-built_in">this</span>.shouldRequireStepUp(riskFactors, resourceSensitivity);

    <span class="hljs-keyword">if</span> (stepUpRequired.required) {
      <span class="hljs-keyword">return</span> {
        <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">methods</span>: <span class="hljs-built_in">this</span>.selectAuthenticationMethods(riskFactors, stepUpRequired.level),
        <span class="hljs-attr">expiresIn</span>: <span class="hljs-built_in">this</span>.calculateStepUpDuration(stepUpRequired.level),
        <span class="hljs-attr">reason</span>: stepUpRequired.reason
      };
    }

    <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">false</span> };
  }

  <span class="hljs-keyword">async</span> calculateRiskFactors(userId, context) {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">deviceTrust</span>: <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getDeviceTrustScore(userId, context.deviceFingerprint),
      <span class="hljs-attr">locationRisk</span>: <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getLocationRiskScore(userId, context.ipAddress),
      <span class="hljs-attr">behaviorAnomaly</span>: <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getBehaviorAnomalyScore(userId, context.sessionData),
      <span class="hljs-attr">timeSinceLastAuth</span>: <span class="hljs-built_in">Date</span>.now() - context.lastAuthTime,
      <span class="hljs-attr">resourceSensitivity</span>: context.resourceSensitivity || <span class="hljs-string">'medium'</span>
    };
  }

  shouldRequireStepUp(riskFactors, sensitivity) {
    <span class="hljs-keyword">let</span> score = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">if</span> (riskFactors.deviceTrust &lt; <span class="hljs-number">70</span>) score += <span class="hljs-number">30</span>;
    <span class="hljs-keyword">if</span> (riskFactors.deviceTrust &lt; <span class="hljs-number">40</span>) score += <span class="hljs-number">20</span>;
    <span class="hljs-keyword">if</span> (riskFactors.locationRisk &gt; <span class="hljs-number">0.6</span>) score += <span class="hljs-number">25</span>;
    <span class="hljs-keyword">if</span> (riskFactors.locationRisk &gt; <span class="hljs-number">0.8</span>) score += <span class="hljs-number">15</span>;
    <span class="hljs-keyword">if</span> (riskFactors.behaviorAnomaly &gt; <span class="hljs-number">0.5</span>) score += <span class="hljs-number">20</span>;
    <span class="hljs-keyword">if</span> (riskFactors.behaviorAnomaly &gt; <span class="hljs-number">0.7</span>) score += <span class="hljs-number">10</span>;
    <span class="hljs-keyword">const</span> hours = riskFactors.timeSinceLastAuth / (<span class="hljs-number">1000</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>);
    <span class="hljs-keyword">if</span> (hours &gt; <span class="hljs-number">8</span>) score += <span class="hljs-number">10</span>;
    <span class="hljs-keyword">if</span> (hours &gt; <span class="hljs-number">24</span>) score += <span class="hljs-number">15</span>;

    score *= { <span class="hljs-attr">low</span>: <span class="hljs-number">0.7</span>, <span class="hljs-attr">medium</span>: <span class="hljs-number">1.0</span>, <span class="hljs-attr">high</span>: <span class="hljs-number">1.3</span>, <span class="hljs-attr">critical</span>: <span class="hljs-number">1.6</span> }[sensitivity] || <span class="hljs-number">1.0</span>;

    <span class="hljs-keyword">if</span> (score &gt;= <span class="hljs-number">80</span>) <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'high'</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'high_risk_detected'</span> };
    <span class="hljs-keyword">if</span> (score &gt;= <span class="hljs-number">50</span>) <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'medium'</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'moderate_risk_detected'</span> };
    <span class="hljs-keyword">if</span> (score &gt;= <span class="hljs-number">25</span>) <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'low'</span>, <span class="hljs-attr">reason</span>: <span class="hljs-string">'low_risk_detected'</span> };
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">required</span>: <span class="hljs-literal">false</span> };
  }

  selectAuthenticationMethods(riskFactors, level) {
    <span class="hljs-keyword">const</span> methods = [];
    <span class="hljs-keyword">if</span> (level === <span class="hljs-string">'high'</span>) {
      methods.push(<span class="hljs-string">'hardware_token'</span>, <span class="hljs-string">'biometric'</span>);
      <span class="hljs-keyword">if</span> (riskFactors.deviceTrust &lt; <span class="hljs-number">30</span>) methods.push(<span class="hljs-string">'admin_approval'</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (level === <span class="hljs-string">'medium'</span>) {
      methods.push(<span class="hljs-string">'totp'</span>, <span class="hljs-string">'sms'</span>);
      <span class="hljs-keyword">if</span> (riskFactors.locationRisk &gt; <span class="hljs-number">0.7</span>) methods.push(<span class="hljs-string">'email_verification'</span>);
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (level === <span class="hljs-string">'low'</span>) {
      methods.push(<span class="hljs-string">'totp'</span>);
    }
    <span class="hljs-keyword">return</span> methods;
  }
}
</code></pre>
<p>The service uses this balancing technique between critical resources and risks while keeping normal workflows intact when things look safe.</p>
<h2 id="heading-security-monitoring">Security Monitoring</h2>
<p>Security monitoring provides the observability layer that’s essential for detecting, analyzing, and responding to threats in real time. A strong system must log every authentication event, highlight anomalies, and allow for rapid and automated response to threats. This phase further builds trust by constantly evaluating access patterns and acting on them when signals of risk emerge.</p>
<p>Logging is visibility at its base. These days, every authentication attempt, be it successful, failed, or suspicious, needs to be logged with exhaustive context. This very information helps forensic analysis, alerting, and compliance reporting.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Comprehensive authentication event logging</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationLogger</span> </span>{
  <span class="hljs-keyword">async</span> logAuthenticationEvent(eventType, userId, context, result) {
    <span class="hljs-keyword">const</span> logEntry = {
      <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString(),
      eventType,
      userId,
      <span class="hljs-attr">sessionId</span>: context.sessionId,
      <span class="hljs-attr">ipAddress</span>: context.ipAddress,
      <span class="hljs-attr">userAgent</span>: context.userAgent,
      <span class="hljs-attr">deviceFingerprint</span>: context.deviceFingerprint,
      <span class="hljs-attr">location</span>: context.location,
      <span class="hljs-attr">authenticationMethod</span>: context.authMethod,
      <span class="hljs-attr">result</span>: result.success ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'failure'</span>,
      <span class="hljs-attr">failureReason</span>: result.failureReason,
      <span class="hljs-attr">riskScore</span>: result.riskScore,
      <span class="hljs-attr">additionalFactorsRequired</span>: result.stepUpRequired,
      <span class="hljs-attr">processingTime</span>: result.processingTime,
      <span class="hljs-attr">correlationId</span>: context.correlationId
    };

    <span class="hljs-comment">// Store in multiple destinations for redundancy</span>
    <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all([
      <span class="hljs-built_in">this</span>.writeToDatabase(logEntry),
      <span class="hljs-built_in">this</span>.sendToLogAggregator(logEntry),
      <span class="hljs-built_in">this</span>.updateRealTimeMetrics(logEntry)
    ]);

    <span class="hljs-comment">// Trigger real-time alerts for critical events</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isCriticalEvent(logEntry)) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.triggerSecurityAlert(logEntry);
    }
  }

  isCriticalEvent(logEntry) {
    <span class="hljs-keyword">const</span> criticalConditions = [
      logEntry.result === <span class="hljs-string">'failure'</span> &amp;&amp; logEntry.failureReason === <span class="hljs-string">'brute_force_detected'</span>,
      logEntry.riskScore &gt; <span class="hljs-number">80</span>,
      logEntry.eventType === <span class="hljs-string">'impossible_travel_detected'</span>,
      logEntry.eventType === <span class="hljs-string">'account_takeover_suspected'</span>
    ];

    <span class="hljs-keyword">return</span> criticalConditions.some(<span class="hljs-function"><span class="hljs-params">condition</span> =&gt;</span> condition);
  }

  <span class="hljs-keyword">async</span> generateSecurityReport(userId, timeRange) {
    <span class="hljs-keyword">const</span> events = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.getAuthenticationEvents(userId, timeRange);

    <span class="hljs-keyword">const</span> analysis = {
      <span class="hljs-attr">totalEvents</span>: events.length,
      <span class="hljs-attr">successfulLogins</span>: events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.result === <span class="hljs-string">'success'</span>).length,
      <span class="hljs-attr">failedAttempts</span>: events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.result === <span class="hljs-string">'failure'</span>).length,
      <span class="hljs-attr">uniqueDevices</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(events.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.deviceFingerprint)).size,
      <span class="hljs-attr">uniqueLocations</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(events.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.location?.country)).size,
      <span class="hljs-attr">averageRiskScore</span>: events.reduce(<span class="hljs-function">(<span class="hljs-params">sum, e</span>) =&gt;</span> sum + e.riskScore, <span class="hljs-number">0</span>) / events.length,
      <span class="hljs-attr">timePatterns</span>: <span class="hljs-built_in">this</span>.analyzeTimePatterns(events),
      <span class="hljs-attr">locationPatterns</span>: <span class="hljs-built_in">this</span>.analyzeLocationPatterns(events),
      <span class="hljs-attr">devicePatterns</span>: <span class="hljs-built_in">this</span>.analyzeDevicePatterns(events)
    };

    <span class="hljs-keyword">return</span> analysis;
  }
}
</code></pre>
<p>In the above code, the class logs detailed authentication events such as the approximate device and location from which it was initiated, the authentication methods used, and the risk score.</p>
<p>From a security perspective, it’s envisaged to generate security reports with the advantage of flagging critical events such as brute-force attempts or logins from suspicious geographies that can send real-time alerts.</p>
<p>Monitoring authentication events isn’t enough – the system must be able to interpret patterns and flag suspicious behavior. This detection system combines static rule-based checks with dynamic anomaly detection powered by machine learning. It identifies threats like brute-force attacks, credential stuffing, and unusual geographic access, then escalates them automatically for further action.</p>
<p>The following code performs real-time threat detection by analyzing recent authentication events and contextual data. Here's what it does, broken down clearly:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Suspicious activity detection system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SuspiciousActivityDetector</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.detectionRules = <span class="hljs-built_in">this</span>.initializeDetectionRules();
    <span class="hljs-built_in">this</span>.mlModel = <span class="hljs-built_in">this</span>.loadAnomalyDetectionModel();
  }

  <span class="hljs-keyword">async</span> analyzeActivity(userId, recentEvents, context) {
    <span class="hljs-keyword">const</span> suspiciousPatterns = [];

    <span class="hljs-comment">// Rule-based detection</span>
    <span class="hljs-keyword">const</span> ruleViolations = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.checkDetectionRules(userId, recentEvents);
    suspiciousPatterns.push(...ruleViolations);

    <span class="hljs-comment">// ML-based anomaly detection</span>
    <span class="hljs-keyword">const</span> anomalies = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.detectAnomalies(userId, recentEvents, context);
    suspiciousPatterns.push(...anomalies);

    <span class="hljs-comment">// Threat intelligence correlation</span>
    <span class="hljs-keyword">const</span> threatMatches = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.correlateThreatIntelligence(context);
    suspiciousPatterns.push(...threatMatches);

    <span class="hljs-keyword">if</span> (suspiciousPatterns.length &gt; <span class="hljs-number">0</span>) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.escalateSuspiciousActivity(userId, suspiciousPatterns);
    }

    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">suspicious</span>: suspiciousPatterns.length &gt; <span class="hljs-number">0</span>,
      <span class="hljs-attr">patterns</span>: suspiciousPatterns,
      <span class="hljs-attr">riskScore</span>: <span class="hljs-built_in">this</span>.calculateSuspiciousActivityRisk(suspiciousPatterns)
    };
  }

  initializeDetectionRules() {
    <span class="hljs-keyword">return</span> [
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'brute_force_detection'</span>,
        <span class="hljs-attr">condition</span>: <span class="hljs-function">(<span class="hljs-params">events</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> failedAttempts = events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span>
            e.result === <span class="hljs-string">'failure'</span> &amp;&amp;
            <span class="hljs-built_in">Date</span>.now() - <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e.timestamp).getTime() &lt; <span class="hljs-number">300000</span> <span class="hljs-comment">// 5 minutes</span>
          );
          <span class="hljs-keyword">return</span> failedAttempts.length &gt;= <span class="hljs-number">5</span>;
        },
        <span class="hljs-attr">severity</span>: <span class="hljs-string">'high'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'temporary_lockout'</span>
      },
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'credential_stuffing'</span>,
        <span class="hljs-attr">condition</span>: <span class="hljs-function">(<span class="hljs-params">events</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> recentFailures = events.filter(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span>
            e.result === <span class="hljs-string">'failure'</span> &amp;&amp;
            <span class="hljs-built_in">Date</span>.now() - <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(e.timestamp).getTime() &lt; <span class="hljs-number">3600000</span> <span class="hljs-comment">// 1 hour</span>
          );
          <span class="hljs-keyword">const</span> uniqueUsernames = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(recentFailures.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.username));
          <span class="hljs-keyword">return</span> uniqueUsernames.size &gt;= <span class="hljs-number">10</span>;
        },
        <span class="hljs-attr">severity</span>: <span class="hljs-string">'medium'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'rate_limiting'</span>
      },
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'suspicious_location_pattern'</span>,
        <span class="hljs-attr">condition</span>: <span class="hljs-function">(<span class="hljs-params">events</span>) =&gt;</span> {
          <span class="hljs-keyword">const</span> locations = events.map(<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> e.location?.country).filter(<span class="hljs-built_in">Boolean</span>);
          <span class="hljs-keyword">const</span> uniqueCountries = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>(locations);
          <span class="hljs-keyword">return</span> uniqueCountries.size &gt;= <span class="hljs-number">3</span> &amp;&amp; events.length &gt;= <span class="hljs-number">5</span>;
        },
        <span class="hljs-attr">severity</span>: <span class="hljs-string">'medium'</span>,
        <span class="hljs-attr">action</span>: <span class="hljs-string">'enhanced_verification'</span>
      }
    ];
  }

  <span class="hljs-keyword">async</span> detectAnomalies(userId, events, context) {
    <span class="hljs-keyword">const</span> features = <span class="hljs-built_in">this</span>.extractFeatures(events, context);
    <span class="hljs-keyword">const</span> anomalyScore = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.mlModel.predict(features);

    <span class="hljs-keyword">if</span> (anomalyScore &gt; <span class="hljs-number">0.7</span>) {
      <span class="hljs-keyword">return</span> [{
        <span class="hljs-attr">type</span>: <span class="hljs-string">'ml_anomaly'</span>,
        <span class="hljs-attr">score</span>: anomalyScore,
        <span class="hljs-attr">features</span>: features,
        <span class="hljs-attr">description</span>: <span class="hljs-string">'Machine learning model detected anomalous behavior pattern'</span>
      }];
    }

    <span class="hljs-keyword">return</span> [];
  }
}
</code></pre>
<p>This class applies multiple techniques to detect threats. It first evaluates authentication history using static rules for brute-force attempts, large-scale credential reuse, or location anomalies. It then passes <a target="_blank" href="https://www.fullstory.com/blog/behavioral-data/">behavioral data</a> through a trained ML model to spot subtle patterns missed by rules. If any suspicious pattern is detected, it returns a structured risk report and initiates escalation.</p>
<h3 id="heading-automating-threat-response">Automating Threat Response</h3>
<p>Most times, systems respond in real-time. Automated threat response follows predefined actions and includes locking an account, alerting users, or blocking an IP, among others, when a high-risk event occurs.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Automated threat response system</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AutomatedThreatResponse</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">this</span>.responsePlaybooks = <span class="hljs-built_in">this</span>.initializeResponsePlaybooks();
    <span class="hljs-built_in">this</span>.escalationPolicies = <span class="hljs-built_in">this</span>.loadEscalationPolicies();
  }

  <span class="hljs-keyword">async</span> processSecurityEvent(event) {
    <span class="hljs-keyword">const</span> threatLevel = <span class="hljs-built_in">this</span>.assessThreatLevel(event);
    <span class="hljs-keyword">const</span> applicablePlaybooks = <span class="hljs-built_in">this</span>.selectPlaybooks(event, threatLevel);

    <span class="hljs-keyword">const</span> responses = [];
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> playbook <span class="hljs-keyword">of</span> applicablePlaybooks) {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.executePlaybook(playbook, event);
      responses.push(response);
    }

    <span class="hljs-keyword">if</span> (threatLevel === <span class="hljs-string">'critical'</span> || responses.some(<span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> !r.success)) {
      <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.escalateToHuman(event, responses);
    }

    <span class="hljs-keyword">return</span> {
      event,
      threatLevel,
      responses,
      <span class="hljs-attr">timestamp</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>()
    };
  }

  initializeResponsePlaybooks() {
    <span class="hljs-keyword">return</span> [
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'brute_force_response'</span>,
        <span class="hljs-attr">triggers</span>: [<span class="hljs-string">'brute_force_detected'</span>],
        <span class="hljs-attr">actions</span>: [
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'temporary_lockout'</span>, <span class="hljs-attr">duration</span>: <span class="hljs-number">900</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'rate_limiting'</span>, <span class="hljs-attr">factor</span>: <span class="hljs-number">10</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'notify_user'</span>, <span class="hljs-attr">method</span>: <span class="hljs-string">'email'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'log_security_event'</span>, <span class="hljs-attr">level</span>: <span class="hljs-string">'high'</span> }
        ]
      },
      {
        <span class="hljs-attr">name</span>: <span class="hljs-string">'account_takeover_response'</span>,
        <span class="hljs-attr">triggers</span>: [<span class="hljs-string">'impossible_travel'</span>, <span class="hljs-string">'behavior_anomaly_high'</span>],
        <span class="hljs-attr">actions</span>: [
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'terminate_all_sessions'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'require_password_reset'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'notify_user'</span>, <span class="hljs-attr">method</span>: <span class="hljs-string">'multiple'</span> },
          { <span class="hljs-attr">type</span>: <span class="hljs-string">'freeze_account'</span>, <span class="hljs-attr">duration</span>: <span class="hljs-number">7200</span> }
        ]
      }
    ];
  }

  <span class="hljs-keyword">async</span> executePlaybook(playbook, event) {
    <span class="hljs-keyword">const</span> execution = {
      <span class="hljs-attr">playbookName</span>: playbook.name,
      <span class="hljs-attr">eventId</span>: event.id,
      <span class="hljs-attr">actions</span>: [],
      <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>
    };

    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> action <span class="hljs-keyword">of</span> playbook.actions) {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.executeAction(action, event);
        execution.actions.push(result);
        <span class="hljs-keyword">if</span> (!result.success) {
          execution.success = <span class="hljs-literal">false</span>;
          <span class="hljs-keyword">break</span>;
        }
      } <span class="hljs-keyword">catch</span> (err) {
        execution.success = <span class="hljs-literal">false</span>;
        execution.error = err.message;
      }
    }

    <span class="hljs-keyword">return</span> execution;
  }

  <span class="hljs-keyword">async</span> executeAction(action, event) {
    <span class="hljs-keyword">switch</span> (action.type) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">'temporary_lockout'</span>:
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.lockoutUser(event.userId, action.duration);
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">type</span>: action.type };
      <span class="hljs-keyword">case</span> <span class="hljs-string">'notify_user'</span>:
        <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.notifyUser(event.userId, action.method, event);
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">type</span>: action.type };
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> { <span class="hljs-attr">success</span>: <span class="hljs-literal">false</span>, <span class="hljs-attr">type</span>: action.type, <span class="hljs-attr">error</span>: <span class="hljs-string">'Unknown action'</span> };
    }
  }
}
</code></pre>
<p>Here, the system uses playbooks – predefined actions to be taken in response to threats. For example, locks user from further brute-force attempts for some time and sends them an email notification. Freezing the account and ending all sessions are some reactive measures you can take if suspicious behavior indicates a takeover. These measures ensure fast and consistent action to mitigate damage even before humans can get involved.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Zero-trust authentication creates a strong line of distinction going against classic perimeter-based security. It must be painstakingly planned, implemented in layers, and constantly improved. This article offers a structured path, from basic MFA to intelligent behavioral monitoring and automated threat response.</p>
<p>Complementing the improvement of security, zero-trust promises better user experience, compliance readiness, and decreased incident risk. When organizations maintain a perpetual position of zero trust, we can see an actual positive impact on their ability to detect, prevent, and respond to threats in real time.</p>
<p>To have long-term success with this approach, you’ll need to continuously monitor your setup, perform periodic assessments, and be responsive to evolving attack patterns. Feedback loops and performance data are essential to keep the system secure yet user-friendly.</p>
<p>As threats grow more sophisticated, so must our defenses. ZTA provides a durable foundation – ready to evolve with emerging technologies like adaptive biometrics and AI-driven risk engines. Organizations investing in it today will be better equipped to meet tomorrow’s security and usability demands.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Embedded Swift: A Modern Approach to Low-Level Programming ]]>
                </title>
                <description>
                    <![CDATA[ Embedded programming has long been dominated by C and C++, powering everything from microcontrollers to real-time systems. While these languages offer unmatched low-level control, they also introduce persistent challenges, manual memory management, u... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/embedded-swift-a-modern-approach-to-low-level-programming/</link>
                <guid isPermaLink="false">688d5fc7d30be1cecdacf767</guid>
                
                    <category>
                        <![CDATA[ embedded systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Firmware Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Swift ]]>
                    </category>
                
                    <category>
                        <![CDATA[ C ]]>
                    </category>
                
                    <category>
                        <![CDATA[ C++ ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ memory-management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ programming languages ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Soham Banerjee ]]>
                </dc:creator>
                <pubDate>Sat, 02 Aug 2025 00:45:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754090186842/80a42dca-f2c4-49de-b704-2e90134c6397.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Embedded programming has long been dominated by C and C++, powering everything from microcontrollers to real-time systems. While these languages offer unmatched low-level control, they also introduce persistent challenges, manual memory management, unsafe pointer operations, and subtle logic bugs stemming from weak type systems and undefined behavior.</p>
<p>With the release of Swift 6 and its new Embedded Swift compilation mode, developers now have access to a modern, memory-safe, and performant alternative that’s tailored specifically for resource-constrained systems.</p>
<p>While languages like Rust have also emerged to address these issues, Embedded Swift brings the clarity and safety of Swift to microcontroller environments, without giving up on determinism, binary size, or hardware access.</p>
<p>This article introduces Embedded Swift and explores how it compares to traditional C/C++ development. We’ll cover its key features, programming and memory models, how to set up the toolchain for STM32 microcontrollers, and how to link Swift with existing C drivers.</p>
<p>Along the way, we’ll examine performance trade-offs, growing ecosystem support, and the broader industry movement toward memory-safe languages. As I hope you’ll see, Swift is a serious contender in the future of embedded development.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the most out of this article, you should have a basic understanding of programming in Swift and C. Familiarity with embedded hardware platforms and firmware development concepts will also be helpful.</p>
<p>If you're new to embedded systems, consider reviewing this <a target="_blank" href="https://www.freecodecamp.org/news/learn-embedded-systems-firmware-basics-handbook-for-devs/">introductory guide to embedded firmware</a> to build foundational knowledge before diving into Embedded Swift.</p>
<h2 id="heading-scope">Scope</h2>
<p>This article is intended as a practical introduction to Embedded Swift. It covers:</p>
<ul>
<li><p>An overview of Embedded Swift and its key language features</p>
</li>
<li><p>Swift’s programming and memory model in an embedded context</p>
</li>
<li><p>Setting up the Embedded Swift toolchain on macOS for STM32 microcontrollers</p>
</li>
<li><p>Interoperability with C code and linking to existing low-level drivers</p>
</li>
<li><p>A look at memory and instruction-level performance</p>
</li>
<li><p>Future directions and use cases for Embedded Swift</p>
</li>
</ul>
<p>Note that this article does not provide a full tutorial on the Swift language itself. While the primary focus is on STM32, similar principles apply to other supported platforms such as ESP32, Raspberry Pi Pico, and nRF52.</p>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-swift-what-is-embedded-swift">What is Swift? What is Embedded Swift?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-swift-programming-model">Swift Programming Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-swift-memory-management">Swift Memory Management</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-memory-and-instruction-cycle-comparison">Memory and Instruction Cycle Comparison</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-setup-embedded-swift">How to Setup Embedded Swift</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-c-swift-linkages">C-Swift Linkages:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-future-work">Future Work</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-is-swift-what-is-embedded-swift">What is Swift? What is Embedded Swift?</h2>
<p>Swift is a modern programming language developed by Apple that combines the performance of compiled languages with the expressiveness and safety of modern language design. While Swift was originally created for iOS and macOS development, it has evolved into a powerful general-purpose language used in server-side development, systems programming, and increasingly, embedded systems.</p>
<p>Embedded Swift is a special compilation mode introduced in Swift 6 that brings the benefits of Swift to resource-constrained platforms like microcontrollers. It lets developers use a safe, high-level language while still producing compact, deterministic, and performant binaries suitable for embedded applications.</p>
<h3 id="heading-key-features-of-swift">Key Features of Swift</h3>
<p>Embedded Swift retains many of the powerful language features that make Swift an attractive alternative to C/C++ in embedded development:</p>
<p><strong>Type Safety</strong>: Swift uses a strong static type system, which prevents many programming errors at compile time. Unlike C, where type mismatches can result in undefined behavior, Swift ensures all types are used correctly before code even runs.</p>
<p><strong>Strict Type Checking</strong>: Swift doesn't allow implicit type conversions that could lose data or cause unexpected behavior. For example:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// This won't compile in Swift</span>
<span class="hljs-keyword">let</span> integer: <span class="hljs-type">Int</span> = <span class="hljs-number">42</span>
<span class="hljs-keyword">let</span> decimal: <span class="hljs-type">Double</span> = <span class="hljs-number">3.14</span>
<span class="hljs-keyword">let</span> result = integer + decimal  <span class="hljs-comment">// Error: Cannot convert value of type 'Int' to expected argument type 'Double'</span>

<span class="hljs-comment">// You must be explicit about conversions</span>
<span class="hljs-keyword">let</span> result = <span class="hljs-type">Double</span>(integer) + decimal  <span class="hljs-comment">// Correct</span>
</code></pre>
<p><strong>Non-nullable Types by Default</strong>: In C, pointers can be null by default, which introduces risk. In Swift, variables cannot be nil unless explicitly marked as optionals:</p>
<pre><code class="lang-swift"><span class="hljs-keyword">var</span> name: <span class="hljs-type">String</span> = <span class="hljs-string">"John"</span>
name = <span class="hljs-literal">nil</span>  <span class="hljs-comment">// Compile error - String cannot be nil</span>

<span class="hljs-keyword">var</span> optionalName: <span class="hljs-type">String?</span> = <span class="hljs-string">"John"</span>
optionalName = <span class="hljs-literal">nil</span>  <span class="hljs-comment">// This is allowed</span>
</code></pre>
<h4 id="heading-memory-safety-via-arc-covered-in-detail-later">Memory Safety via ARC (Covered in detail later):</h4>
<p>Swift manages memory automatically using Automatic Reference Counting (ARC). Unlike manual memory management in C/C++, ARC handles object lifecycles efficiently without unpredictable garbage collection pauses. We'll cover ARC and its impact in embedded contexts in a dedicated section later.</p>
<p><strong>Modern Syntax</strong>:<br>Swift's syntax is clean, consistent, and designed for readability. It supports modern paradigms including:</p>
<ul>
<li><p>Functional programming (map, filter, reduce)</p>
</li>
<li><p>Generics (type-safe abstractions)</p>
</li>
<li><p>Protocol-Oriented Programming (discussed in the next section)</p>
</li>
</ul>
<p>These features allow you to write more expressive and maintainable code compared to procedural C or inheritance-heavy C++.</p>
<p><strong>Performance</strong>:<br>Swift is designed to perform on par with C++ in many scenarios. Optimizations such as inlining, dead code elimination, and static dispatch help ensure that high-level abstractions don’t compromise performance. In embedded mode, Swift disables features like runtime reflection and dynamic dispatch to further reduce overhead.</p>
<p>To fully leverage Swift for embedded development, it's important to understand its programming model. Unlike C’s procedural approach or C++’s class-heavy design, Swift promotes protocol-oriented programming and composition, which offers both flexibility and safety in embedded system design.</p>
<h2 id="heading-swift-programming-model">Swift Programming Model</h2>
<p>Swift embraces a multi-paradigm programming model that blends object-oriented, functional, and protocol-oriented programming, all underpinned by strong type safety and memory safety.</p>
<p>For embedded developers coming from C or C++, this model may feel different at first. But it provides a more modular and testable way to build complex systems, something especially valuable in embedded applications where hardware abstraction and strict reliability are critical.</p>
<h3 id="heading-protocol-oriented-programming-pop">Protocol-Oriented Programming (POP)</h3>
<p>Swift emphasizes protocols over inheritance, encouraging developers to define behaviors through protocols and implement them using value types like <code>struct</code> and <code>enum</code>, rather than relying heavily on classes.</p>
<p>This philosophy favors composition over inheritance, allowing you to build complex functionality by combining smaller, well-defined components.</p>
<p>Key Concepts<strong>:</strong></p>
<ul>
<li><p><code>protocol</code> defines required behavior.</p>
</li>
<li><p>Protocol extensions provide default behavior.</p>
</li>
<li><p>Prefer value semantics using <code>struct</code>.</p>
</li>
</ul>
<p>Example<strong>:</strong></p>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">protocol</span> <span class="hljs-title">Speakable</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">speak</span><span class="hljs-params">()</span></span>
}

<span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">Speakable</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">speak</span><span class="hljs-params">()</span></span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Default sound"</span>)
    }
}

<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Dog</span>: <span class="hljs-title">Speakable</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">speak</span><span class="hljs-params">()</span></span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Woof!"</span>)
    }
}
</code></pre>
<p>Embedded Swift uses protocols with static dispatch. With static dispatch, the compiler knows the exact memory address of the function to call and can generate a direct jump instruction. There's no runtime lookup, no indirection, and no uncertainty.</p>
<h4 id="heading-why-pop-matters-for-embedded-systems">Why POP Matters for Embedded Systems</h4>
<p>First, you get flexible hardware extraction. Protocols make it easy to define interfaces for hardware components, allowing for mock implementations during testing or platform-specific variations.</p>
<p>Second, you have nice low overhead. Embedded Swift uses static dispatch for protocols, meaning there’s no runtime lookup, and calls are resolved at compile time for maximum performance.</p>
<p>Also, <code>struct</code> and <code>enum</code> types avoid heap allocations, making code more efficient and predictable in low-memory environments.</p>
<p>Now that we’ve explored how Swift’s programming model enables safer and more modular embedded code, let’s turn to another critical piece of the puzzle: memory management. Swift’s use of Automatic Reference Counting (ARC) replaces manual memory handling and offers important benefits, and tradeoffs, for embedded systems.</p>
<h2 id="heading-swift-memory-management">Swift Memory Management</h2>
<p>One of Swift’s most impactful features, especially in the context of embedded systems, is its use of Automatic Reference Counting (ARC) for memory management. Unlike C/C++, where memory must be manually allocated and freed using <code>malloc</code> and <code>free</code>, Swift automates this process while maintaining deterministic performance.</p>
<p>This automation significantly reduces the risk of common memory-related bugs like leaks, dangling pointers, or use-after-free errors, all of which are notorious in low-level C code.</p>
<h3 id="heading-how-arc-works">How ARC works</h3>
<p>Swift supports ARC not only for the Cocoa Touch API's but for all APIs, providing a streamlined approach to memory management. Unlike garbage collection systems that can cause unpredictable pauses, ARC works deterministically at compile time and runtime to manage memory.</p>
<p>ARC automatically tracks and manages the lifetime of objects in memory based on how many references point to them.</p>
<ul>
<li><p>Reference Counting: Every object has a counter that tracks how many strong references point to it.</p>
</li>
<li><p>Retain / Release: The compiler inserts <code>retain</code> and <code>release</code> calls automatically during assignment and deinitialization.</p>
</li>
<li><p>Immediate Deallocation: When the reference count reaches zero, the object is deallocated immediately.</p>
</li>
<li><p>Deterministic: Unlike garbage collectors, ARC doesn’t introduce unpredictable pauses or runtime scanning.</p>
</li>
</ul>
<p>Swift offers multiple reference types to give you precise control over memory behavior and prevent cycles:</p>
<p><strong>Strong References</strong> (default)</p>
<ul>
<li><p>Keeps the referenced object alive.</p>
</li>
<li><p>Used in most cases.</p>
</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MotorController</span> </span>{
    <span class="hljs-keyword">var</span> sensor: <span class="hljs-type">SensorData?</span>  <span class="hljs-comment">// Strong reference</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">updateReading</span><span class="hljs-params">(newData: SensorData)</span></span> {
        <span class="hljs-keyword">self</span>.sensor = newData  <span class="hljs-comment">// Previous sensor data automatically deallocated</span>
    }
}
</code></pre>
<p><strong>Weak References</strong></p>
<ul>
<li><p>Used to break reference cycles (especially in two-way object relationships).</p>
</li>
<li><p>Automatically becomes <code>nil</code> when the referenced object is deallocated.</p>
</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Device</span> </span>{
    <span class="hljs-keyword">var</span> controller: <span class="hljs-type">MotorController?</span>

    <span class="hljs-keyword">deinit</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Device deallocated"</span>)
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MotorController</span> </span>{
    <span class="hljs-keyword">weak</span> <span class="hljs-keyword">var</span> device: <span class="hljs-type">Device?</span>  <span class="hljs-comment">// ← Weak reference breaks the cycle</span>

    <span class="hljs-keyword">deinit</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"MotorController deallocated"</span>)
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">breakCycle</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">let</span> device = <span class="hljs-type">Device</span>()
    <span class="hljs-keyword">let</span> controller = <span class="hljs-type">MotorController</span>()

    device.controller = controller
    controller.device = device  <span class="hljs-comment">// ← This is now a weak reference</span>

    <span class="hljs-comment">// When this function ends, both objects are properly deallocated</span>
}

breakCycle()
<span class="hljs-comment">// Output:</span>
<span class="hljs-comment">// Device deallocated</span>
<span class="hljs-comment">// MotorController deallocated</span>
</code></pre>
<p><strong>Unowned References</strong></p>
<ul>
<li><p>Non-optional version of <code>weak</code>.</p>
</li>
<li><p>Assumes the object will never be deallocated while still in use.</p>
</li>
<li><p>More lightweight than <code>weak</code>, but unsafe if misused.</p>
</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SensorSystem</span> </span>{
    <span class="hljs-keyword">unowned</span> <span class="hljs-keyword">let</span> controller: <span class="hljs-type">MotorController</span>  <span class="hljs-comment">// unowned reference</span>

    <span class="hljs-keyword">init</span>(controller: <span class="hljs-type">MotorController</span>) {
        <span class="hljs-keyword">self</span>.controller = controller
    }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MotorController</span> </span>{
    <span class="hljs-keyword">var</span> sensorSystem: <span class="hljs-type">SensorSystem?</span>

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">setupSensors</span><span class="hljs-params">()</span></span> {
        sensorSystem = <span class="hljs-type">SensorSystem</span>(controller: <span class="hljs-keyword">self</span>)
    }

    <span class="hljs-keyword">deinit</span> {
        <span class="hljs-built_in">print</span>(<span class="hljs-string">"MotorController deallocated"</span>)
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">testUnowned</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">let</span> controller = <span class="hljs-type">MotorController</span>()
    controller.setupSensors()
    <span class="hljs-comment">// sensorSystem deallocates before controller ends</span>
}

testUnowned()
<span class="hljs-comment">// Output: MotorController deallocated</span>
</code></pre>
<h3 id="heading-arc-overhead-in-embedded-systems">ARC Overhead in Embedded Systems</h3>
<p>While ARC provides safety benefits, it does introduce some overhead compared to manual memory management:</p>
<h4 id="heading-memory-overhead">Memory Overhead:</h4>
<p>ARC-managed class instances in Swift typically include an additional 4 or 8 bytes to store reference count metadata, depending on the system architecture, 4 bytes on 32-bit systems and 8 bytes on 64-bit systems. This metadata allows the runtime to track how many active references exist to a given object and deallocate it when no references remain. When developers use weak or unowned references, the memory footprint increases further. These references require additional data structures, such as side tables or tracking mechanisms, to manage object liveness and cleanup. In the case of weak references specifically, Swift maintains zeroing weak reference tables that automatically null out pointers once the referenced object is deallocated, ensuring memory safety.</p>
<h4 id="heading-cpu-overhead">CPU Overhead:</h4>
<p>ARC introduces some runtime overhead due to retain and release operations, which are inserted automatically during reference assignments. These operations involve incrementing or decrementing the reference count and are especially common in code that passes objects between functions or stores them in collections. To ensure thread safety, these updates are typically implemented using atomic operations, which add further instruction cycles. In complex object graphs, ARC may also engage in cycle detection and cleanup through the use of weak references to prevent memory leaks caused by strong reference cycles. While Swift's ARC provides deterministic and efficient memory management, it does so with both memory and CPU costs that developers should consider carefully, especially in performance-critical embedded systems.</p>
<h3 id="heading-type-safety-and-error-prevention">Type Safety and Error Prevention</h3>
<p>Swift's type system prevents many common errors that plague C/C++ programs:</p>
<ul>
<li><p><strong>Buffer Overflows</strong>: Swift arrays are bounds-checked, preventing buffer overflow vulnerabilities that are common in C.</p>
</li>
<li><p><strong>Null Pointer Dereferences</strong>: Swift's optional types make null pointer dereferences impossible at compile time.</p>
</li>
<li><p><strong>Use After Free</strong>: Swift's ownership model prevents use-after-free errors that can cause crashes or security vulnerabilities.</p>
</li>
</ul>
<p>Now that we’ve covered Swift's memory model and ARC behavior, let’s explore how it compares to C in terms of memory usage and instruction cycles, a crucial aspect when evaluating Embedded Swift for real-world deployment.</p>
<h2 id="heading-memory-and-instruction-cycle-comparison">Memory and Instruction Cycle Comparison</h2>
<p>Understanding the performance characteristics of Swift versus C is essential for embedded systems, where every instruction cycle and byte of memory matters. While Swift brings advantages like safety and expressiveness, these benefits come with certain trade-offs in terms of memory usage and runtime behavior that embedded developers must evaluate carefully.</p>
<h3 id="heading-memory-management">Memory Management:</h3>
<p>Swift uses Automatic Reference Counting (ARC) to manage memory. ARC tracks the number of references to each object and deallocates it when no references remain. This eliminates the need for explicit <code>free()</code> calls but introduces overhead.</p>
<p>C, in contrast, uses manual memory management. Developers allocate memory using <code>malloc</code> and release it using <code>free</code>, or rely on the stack for most short-lived data.</p>
<p>The table below provides the memory management comparison between Swift and C:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Feature</strong></td><td><strong>Swift (ARC)</strong></td><td><strong>C (Manual)</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Memory strategy</td><td>Automatic reference counting</td><td>Manual with <code>malloc</code>/<code>free</code></td></tr>
<tr>
<td>Overhead per object</td><td>4–8 bytes (for ref count)</td><td>None for stack; variable for heap</td></tr>
<tr>
<td>Deallocation</td><td>Deterministic, triggered by ARC</td><td>Developer-controlled</td></tr>
<tr>
<td>Weak reference support</td><td>Requires additional metadata</td><td>Not built-in</td></tr>
<tr>
<td>Thread safety</td><td>Atomic operations in ARC</td><td>Not guaranteed</td></tr>
<tr>
<td>Layout control</td><td>Limited, compiler-managed</td><td>Full control (via structs/pointers)</td></tr>
</tbody>
</table>
</div><p>Swift ensures safety through deterministic cleanup and predictable memory usage. But this comes at the cost of added memory and CPU overhead.</p>
<p>C’s approach offers complete control over memory layout and minimal runtime cost, but increases the risk of memory leaks and fragmentation without disciplined practices.</p>
<h3 id="heading-instruction-cycle-analysis">Instruction Cycle Analysis</h3>
<p>The safety features in Swift, such as bounds checking, optional unwrapping, and ARC updates, translate into additional CPU instructions. While this can impact performance, the Swift compiler is aggressive about optimization in release builds. For example, inlining and ARC elision can remove much of the overhead in performance-critical paths.</p>
<p>C has no built-in safety checks, allowing it to generate highly efficient, predictable code. Developers can even use inline assembly for tight control over performance.</p>
<p>The table below provides the instruction cycle comparison between Swift and C:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Instruction-Level Feature</strong></td><td><strong>Swift</strong></td><td><strong>C</strong></td></tr>
</thead>
<tbody>
<tr>
<td>Reference count updates</td><td>2–4 instructions per assignment</td><td>N/A</td></tr>
<tr>
<td>Bounds checking</td><td>1–3 instructions per array access</td><td>None</td></tr>
<tr>
<td>Optional unwrapping</td><td>1–2 instructions per check</td><td>N/A</td></tr>
<tr>
<td>Method dispatch</td><td>Protocols introduce indirection</td><td>Direct calls or function pointers</td></tr>
<tr>
<td>Optimization potential</td><td>ARC elision, inlining, dead code removal</td><td>Full manual control, inline assembly</td></tr>
<tr>
<td>Predictability</td><td>High in optimized builds, with some abstraction overhead</td><td>Very high, minimal abstraction</td></tr>
</tbody>
</table>
</div><p>Although Swift inserts extra instructions for safety, much of this cost can be mitigated through compiler optimization.</p>
<p>C has no such features by default, making it ideal for applications where performance must be tightly controlled and the developer is willing to take full responsibility for safety.</p>
<h3 id="heading-instruction-count-comparison-swift-vs-c-loop-performance">Instruction Count Comparison: Swift vs C Loop Performance</h3>
<p>When evaluating Swift and C for embedded use, it's helpful to analyze instruction-level performance on basic operations, such as a loop that processes an array of floating-point numbers. This gives us a concrete sense of the computational cost of each language's safety and abstraction features.</p>
<p>Let’s consider a simple example: summing an array of <code>Float</code> values and returning the average. In Swift, the code uses a high-level <code>for-in</code> loop over an array:</p>
<p>Simple loop performance:</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Swift loop with safety checks</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">processData</span><span class="hljs-params">(<span class="hljs-number">_</span> data: [Float])</span></span> -&gt; <span class="hljs-type">Float</span> {
    <span class="hljs-keyword">var</span> sum: <span class="hljs-type">Float</span> = <span class="hljs-number">0.0</span>
    <span class="hljs-keyword">for</span> value <span class="hljs-keyword">in</span> data {  <span class="hljs-comment">// Iterator with bounds checking</span>
        sum += value     <span class="hljs-comment">// Safe arithmetic</span>
    }
    <span class="hljs-keyword">return</span> sum / <span class="hljs-type">Float</span>(data.<span class="hljs-built_in">count</span>)  <span class="hljs-comment">// Safe division</span>
}
<span class="hljs-comment">// Estimated: ~8-10 instructions per iteration</span>
</code></pre>
<p>Although elegant and safe, this loop includes several safety mechanisms:</p>
<ol>
<li><p>Bounds checking on every array access</p>
</li>
<li><p>Reference counting if <code>data</code> is passed as a reference type</p>
</li>
<li><p>Overflow protection in debug mode</p>
</li>
<li><p>Optional handling or runtime checks if <code>data</code> might be empty</p>
</li>
</ol>
<p>These checks introduce runtime overhead, resulting in an estimated 8–10 instructions per iteration on most platforms (depending on optimization level and target architecture). In release builds, Swift aggressively inlines and strips redundant checks, but some level of abstraction cost remains, especially compared to raw memory access in C.</p>
<p>Now, compare that to its equivalent in C:</p>
<pre><code class="lang-c"><span class="hljs-comment">// C loop without safety checks</span>
<span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">process_data</span><span class="hljs-params">(<span class="hljs-keyword">float</span>* data, <span class="hljs-keyword">int</span> count)</span> </span>{
    <span class="hljs-keyword">float</span> sum = <span class="hljs-number">0.0f</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; count; i++) {  <span class="hljs-comment">// Direct pointer arithmetic</span>
        sum += data[i];                <span class="hljs-comment">// Direct memory access</span>
    }
    <span class="hljs-keyword">return</span> sum / count;  <span class="hljs-comment">// Direct division (no safety check)</span>
}
<span class="hljs-comment">// Estimated: ~4-5 instructions per iteration</span>
</code></pre>
<p>This version performs direct memory access with pointer arithmetic, no bounds checks, and no type safety. The C code is lower-level, with fewer runtime checks, and compiles down to just 4–5 instructions per iteration, depending on the target CPU and compiler flags. It is lean and fast, ideal for cycles-per-instruction-critical scenarios.</p>
<p>The table below shows the comparison of single loop performance between Swift and C:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Aspect</td><td>Swift</td><td>C</td></tr>
</thead>
<tbody>
<tr>
<td>Array access</td><td>Bounds-checked</td><td>Direct pointer access</td></tr>
<tr>
<td>Loop iteration</td><td>High-level iterator abstraction</td><td>Raw loop with pointer increment</td></tr>
<tr>
<td>Instruction count (per loop)</td><td>~8–10 (in debug), ~6–8 (in release)</td><td>~4–5</td></tr>
<tr>
<td>Division</td><td>Safe (avoids divide-by-zero in dev)</td><td>Direct</td></tr>
<tr>
<td>Overflow behavior</td><td>Checked in debug, unchecked in release</td><td>Unchecked</td></tr>
<tr>
<td>Readability and safety</td><td>High</td><td>Low</td></tr>
<tr>
<td>Performance</td><td>Lower (but optimizable)</td><td>Higher (manual)</td></tr>
</tbody>
</table>
</div><p>Now that we’ve compared Swift and C in terms of memory and cycle costs, let’s move into the practical side: how to set up Embedded Swift on an STM32 platform and get started with real-world development.</p>
<h2 id="heading-how-to-setup-embedded-swift">How to Setup Embedded Swift</h2>
<p>In this section, we'll walk through how to configure and use Embedded Swift for development on STM32 microcontrollers. STM32 is a popular family of ARM Cortex-M–based microcontrollers, commonly used in industrial, consumer, and IoT applications.</p>
<h3 id="heading-prerequisites-1">Prerequisites</h3>
<p><strong>Required Software:</strong></p>
<ul>
<li><p>Swift Development Snapshot (includes the Embedded Swift toolchain)</p>
</li>
<li><p>Swiftly - Easiest way to manage and install swift toolchains</p>
</li>
<li><p>Swiftc - Swift Compiler command-line tool</p>
</li>
<li><p>Python3 - Required to run scripts to convert Mach-O to binary files</p>
</li>
<li><p>Git (to clone sample repositories) like <a target="_blank" href="https://github.com/swiftlang/swift-embedded-examples">https://github.com/swiftlang/swift-embedded-examples</a></p>
</li>
<li><p>A Unix-like development environment (macOS is currently best supported)</p>
</li>
</ul>
<p><strong>Target Hardware:</strong> This guide focuses on STM32 microcontrollers, which are widely used in embedded applications and have excellent community support.</p>
<p>This guide walks you through the full setup process, from installing the required Swift toolchain to flashing the final binary onto your board. We’ll begin by installing the Swift Development Snapshot using Swiftly, a simple command-line utility for managing Swift toolchains. From there, we’ll configure the build system, set up the correct board variant, customize the build script, and compile the Swift and C source code into a binary. Finally, we’ll flash the firmware onto the STM32 using standard tools</p>
<h3 id="heading-install-swift-development-snapshot">Install Swift Development Snapshot</h3>
<p>The easiest way to install and manage Embedded Swift toolchains is by using the swiftly tool, which simplifies downloading and using Swift snapshots.</p>
<h4 id="heading-macos-installation">macOS Installation:</h4>
<p>The below steps will help install the Swift embedded toolchain:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Using Swiftly (Recommended)</span>
curl -O https://download.swift.org/swiftly/darwin/swiftly.pkg
installer -pkg swiftly.pkg -target CurrentUserHomeDirectory
~/.swiftly/bin/swiftly init --quiet-shell-followup
<span class="hljs-built_in">source</span> <span class="hljs-string">"<span class="hljs-variable">${SWIFTLY_HOME_DIR:-<span class="hljs-variable">$HOME</span>/.swiftly}</span>/env.sh"</span>

<span class="hljs-comment"># Install and use development snapshot</span>
swiftly install main-snapshot
swiftly use main-snapshot

<span class="hljs-comment"># Verify installation</span>
swift --version
</code></pre>
<p>You can clone this Github example repository:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/swiftlang/swift-embedded-examples.git 
<span class="hljs-built_in">cd</span> swift-embedded-examples/projects/stm32-blink
</code></pre>
<p>The stm32-blink contains:</p>
<ul>
<li><p>Swift code that toggles GPIOs</p>
</li>
<li><p>A C startup file with vector table</p>
</li>
<li><p>A build.sh script that uses swiftc, clang, and a custom linker setup</p>
</li>
</ul>
<h3 id="heading-setup-the-stm32-board">Setup the STM32 Board</h3>
<p>Tell the build script which STM32 board is being used:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> STM_BOARD=STM32F746G_DISCOVERY
</code></pre>
<p>You can add your own board variant by defining the appropriate memory map and compiler flags in the script.</p>
<h3 id="heading-modify-buildsh-optional">Modify build.sh (Optional)</h3>
<p>Ensure the script correctly locates the following:</p>
<ul>
<li><p>swiftc: should point to the toolchain you installed with Swiftly</p>
</li>
<li><p>clang: can be macOS’s default Clang</p>
</li>
<li><p>libBuiltin.a, crt0.s, and macho2bin.py: used to provide minimal runtime support and convert output to flashable binaries</p>
</li>
</ul>
<p>If needed, update these paths:</p>
<pre><code class="lang-bash">SWIFT_EXEC=<span class="hljs-variable">${SWIFT_EXEC:-$(swiftly which swiftc)}</span>
CLANG_EXEC=<span class="hljs-variable">${CLANG_EXEC:-$(xcrun -f clang)}</span>
PYTHON_EXEC=<span class="hljs-variable">${PYTHON_EXEC:-$(which python3)}</span>
</code></pre>
<p>Ensure the linker flags match your target’s flash and RAM sizes.</p>
<h3 id="heading-build-and-flash-the-project">Build and Flash the Project:</h3>
<p>Run:</p>
<pre><code class="lang-bash">./build.sh
</code></pre>
<p>This compiles Swift and C code, links them, and produces a blink.bin file.</p>
<p>If successful, you’ll see:</p>
<pre><code class="lang-bash">.build/blink.bin  <span class="hljs-comment"># ready to flash Step 6: Flash the Firmware to STM32</span>
</code></pre>
<p>Use ST-Link tools or openocd to flash your board. Example using st-flash:</p>
<pre><code class="lang-bash">brew install stlink
st-flash write .build/blink.bin 0x8000000
</code></pre>
<p>You should now see an LED blinking.</p>
<p><a target="_blank" href="https://docs.swift.org/embedded/documentation/embedded/stm32baremetalguide">Here’s</a> a more detailed step by step approach to writing a bare metal code on STM32. For comprehensive installation guides covering other platforms (Raspberry Pi Pico, ESP32, nRF52), detailed IDE configuration, troubleshooting, and advanced examples, you can check out the official documentation:</p>
<ul>
<li><p>Complete Setup Guide: <a target="_blank" href="https://docs.swift.org/embedded/documentation/embedded/installembeddedswift/">Install Embedded Swift</a></p>
</li>
<li><p>Platform Examples: <a target="_blank" href="https://github.com/apple/swift-embedded-examples">Swift Embedded Examples Repository</a></p>
</li>
<li><p>Getting Started Tutorial: <a target="_blank" href="https://docs.swift.org/embedded/documentation/embedded">Embedded Swift on Microcontrollers</a></p>
</li>
</ul>
<p>Now that we’ve set up Embedded Swift and explored how to build and run an example project, let’s look at a critical real-world scenario: interfacing Swift with low-level C drivers.</p>
<h2 id="heading-c-swift-linkages">C-Swift Linkages</h2>
<p>In many embedded projects, low-level hardware drivers are written in C because of its close-to-metal control and widespread ecosystem support. Embedded Swift supports seamless interoperability with C, which lets you reuse existing C libraries and drivers, write hardware control logic in C, and implement higher-level application logic in Swift.</p>
<p>This hybrid model lets you combine Swift’s safety and productivity with C’s hardware-level control, with no runtime overhead or object translation.</p>
<p>Let’s walk through an example where a low-level sensor driver is implemented in C and the application logic is written in Swift.</p>
<h3 id="heading-c-header-file-sensordriverh">C Header File (sensor_driver.h):</h3>
<p>This C header file defines the public interface for a low-level sensor driver. It includes standard fixed-width integer types and declares four functions:</p>
<ul>
<li><p>sensor_init(): Initializes the hardware sensor</p>
</li>
<li><p>sensor_read_temperature() and sensor_read_humidity(): Read raw sensor values</p>
</li>
<li><p>sensor_delay_ms(): Delays execution for a given number of milliseconds</p>
</li>
</ul>
<p>This interface acts as a bridge between Swift and C. Swift will link to these functions by name, no wrappers or bindings required.</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">ifndef</span> SENSOR_DRIVER_H</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SENSOR_DRIVER_H</span>

<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdint.h&gt;</span></span>

<span class="hljs-comment">// Low-level sensor driver functions</span>
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">sensor_init</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">uint32_t</span> <span class="hljs-title">sensor_read_temperature</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">uint32_t</span> <span class="hljs-title">sensor_read_humidity</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">sensor_delay_ms</span><span class="hljs-params">(<span class="hljs-keyword">uint32_t</span> milliseconds)</span></span>;

<span class="hljs-meta">#<span class="hljs-meta-keyword">endif</span></span>
</code></pre>
<h3 id="heading-c-implementation-sensordriverc">C Implementation (sensor_driver.c):</h3>
<p>This implementation assumes the sensor is memory-mapped at a fixed address (<code>0x40001000</code>). Each register, temperature, humidity, and control, is accessed by offset from that base address.</p>
<p>The <code>sensor_init</code>() function writes <code>0x01</code> to the control register, presumably enabling or starting the sensor hardware.</p>
<p>The <code>sensor_read_temperature()</code> method and <code>sensor_read_humidity()</code> method reads from memory-mapped registers and return the raw ADC values from the sensor.</p>
<p>The <code>sensor_delay_ms()</code> method performs a simple busy-wait loop using nop (no-operation) instructions to approximate a delay. This is suitable for short, coarse-grained delays in bare-metal contexts.</p>
<pre><code class="lang-c"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">"sensor_driver.h"</span></span>

<span class="hljs-comment">// Hardware register addresses</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> SENSOR_BASE_ADDR    0x40001000</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> TEMP_REG_OFFSET     0x00</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> HUMIDITY_REG_OFFSET 0x04</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">define</span> CONTROL_REG_OFFSET  0x08</span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">sensor_init</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{
    <span class="hljs-comment">// Initialize sensor hardware</span>
    <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">uint32_t</span>* control_reg = (<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">uint32_t</span>*)(SENSOR_BASE_ADDR + CONTROL_REG_OFFSET);
    *control_reg = <span class="hljs-number">0x01</span>; <span class="hljs-comment">// Enable sensor</span>
}

<span class="hljs-function"><span class="hljs-keyword">uint32_t</span> <span class="hljs-title">sensor_read_temperature</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{
    <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">uint32_t</span>* temp_reg = (<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">uint32_t</span>*)(SENSOR_BASE_ADDR + TEMP_REG_OFFSET);
    <span class="hljs-keyword">return</span> *temp_reg;
}

<span class="hljs-function"><span class="hljs-keyword">uint32_t</span> <span class="hljs-title">sensor_read_humidity</span><span class="hljs-params">(<span class="hljs-keyword">void</span>)</span> </span>{
    <span class="hljs-keyword">volatile</span> <span class="hljs-keyword">uint32_t</span>* humidity_reg = (<span class="hljs-keyword">volatile</span> <span class="hljs-keyword">uint32_t</span>*)(SENSOR_BASE_ADDR + HUMIDITY_REG_OFFSET);
    <span class="hljs-keyword">return</span> *humidity_reg;
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">sensor_delay_ms</span><span class="hljs-params">(<span class="hljs-keyword">uint32_t</span> milliseconds)</span> </span>{
    <span class="hljs-comment">// Simple delay implementation</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint32_t</span> i = <span class="hljs-number">0</span>; i &lt; milliseconds * <span class="hljs-number">1000</span>; i++) {
        __asm__(<span class="hljs-string">"nop"</span>);
    }
}
</code></pre>
<h3 id="heading-swift-code-using-c-driver">Swift Code Using C Driver:</h3>
<p>To use these C functions from Swift, you declare them using <code>@_silgen_name</code>, which tells the Swift compiler to link directly to these symbol names at runtime.</p>
<p>The <code>SensorController</code> class encapsulates sensor-related logic. In its <code>init()</code> method, it calls the <code>sensor_init()</code> function defined in C to initialize the sensor hardware.</p>
<p>The <code>readSensors()</code> method reads the raw values from the C driver, converts them into human-readable units using helper functions, stores them internally, and returns the processed values.</p>
<p>The <code>convertTemperature()</code> and <code>convertHumidity()</code> conversion methods apply a basic linear formula to turn raw ADC values into temperature in Celsius and humidity in percentage, respectively. These formulas would be based on the specific sensor’s datasheet.</p>
<p>The <code>checkThresholds()</code> method applies simple threshold logic, a good example of where Swift’s readability and type safety shine. You could easily expand this logic to include error bounds, state machines, or alerts.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Import C driver functions</span>

<span class="hljs-comment">/*
These declarations match the C function signatures exactly. 
They allow Swift to invoke the C functions as if they were native Swift functions 
— with zero overhead.
*/</span>
@_silgen_name(<span class="hljs-string">"sensor_init"</span>)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sensor_init</span><span class="hljs-params">()</span></span>

@_silgen_name(<span class="hljs-string">"sensor_read_temperature"</span>)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sensor_read_temperature</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UInt32</span>

@_silgen_name(<span class="hljs-string">"sensor_read_humidity"</span>)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sensor_read_humidity</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">UInt32</span>

@_silgen_name(<span class="hljs-string">"sensor_delay_ms"</span>)
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sensor_delay_ms</span><span class="hljs-params">(<span class="hljs-number">_</span> ms: UInt32)</span></span>

<span class="hljs-comment">// Swift sensor controller using C driver</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SensorController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> lastTemperature: <span class="hljs-type">Float</span> = <span class="hljs-number">0.0</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">var</span> lastHumidity: <span class="hljs-type">Float</span> = <span class="hljs-number">0.0</span>

    <span class="hljs-keyword">init</span>() {
        <span class="hljs-comment">// Initialize the C driver</span>
        sensor_init()
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">readSensors</span><span class="hljs-params">()</span></span> -&gt; (temperature: <span class="hljs-type">Float</span>, humidity: <span class="hljs-type">Float</span>) {
        <span class="hljs-comment">// Read raw values from C driver</span>
        <span class="hljs-keyword">let</span> rawTemp = sensor_read_temperature()
        <span class="hljs-keyword">let</span> rawHumidity = sensor_read_humidity()

        <span class="hljs-comment">// Convert raw values to meaningful units in Swift</span>
        <span class="hljs-keyword">let</span> temperature = convertTemperature(rawValue: rawTemp)
        <span class="hljs-keyword">let</span> humidity = convertHumidity(rawValue: rawHumidity)

        <span class="hljs-comment">// Store for comparison</span>
        lastTemperature = temperature
        lastHumidity = humidity

        <span class="hljs-keyword">return</span> (temperature: temperature, humidity: humidity)
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">convertTemperature</span><span class="hljs-params">(rawValue: UInt32)</span></span> -&gt; <span class="hljs-type">Float</span> {
        <span class="hljs-comment">// Convert raw ADC value to Celsius</span>
        <span class="hljs-keyword">return</span> (<span class="hljs-type">Float</span>(rawValue) * <span class="hljs-number">3.3</span> / <span class="hljs-number">4095.0</span> - <span class="hljs-number">0.5</span>) * <span class="hljs-number">100.0</span>
    }

    <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">convertHumidity</span><span class="hljs-params">(rawValue: UInt32)</span></span> -&gt; <span class="hljs-type">Float</span> {
        <span class="hljs-comment">// Convert raw ADC value to percentage</span>
        <span class="hljs-keyword">return</span> <span class="hljs-type">Float</span>(rawValue) * <span class="hljs-number">100.0</span> / <span class="hljs-number">4095.0</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">checkThresholds</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Bool</span> {
        <span class="hljs-comment">// Swift logic for threshold checking</span>
        <span class="hljs-keyword">let</span> tempThreshold: <span class="hljs-type">Float</span> = <span class="hljs-number">25.0</span>
        <span class="hljs-keyword">let</span> humidityThreshold: <span class="hljs-type">Float</span> = <span class="hljs-number">60.0</span>

        <span class="hljs-keyword">return</span> lastTemperature &gt; tempThreshold || lastHumidity &gt; humidityThreshold
    }
}

<span class="hljs-comment">// Main application loop</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> -&gt; <span class="hljs-type">Never</span> {
    <span class="hljs-keyword">let</span> sensorController = <span class="hljs-type">SensorController</span>()

    <span class="hljs-keyword">while</span> <span class="hljs-literal">true</span> {
        <span class="hljs-comment">// Read sensors using Swift controller with C driver</span>
        <span class="hljs-keyword">let</span> readings = sensorController.readSensors()

        <span class="hljs-comment">// Process data with Swift's type safety and expressiveness</span>
        <span class="hljs-keyword">if</span> sensorController.checkThresholds() {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Warning: Temperature: \(readings.temperature)°C, Humidity: \(readings.humidity)%"</span>)
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">print</span>(<span class="hljs-string">"Normal: Temperature: \(readings.temperature)°C, Humidity: \(readings.humidity)%"</span>)
        }

        <span class="hljs-comment">// Delay using C driver function</span>
        sensor_delay_ms(<span class="hljs-number">1000</span>) <span class="hljs-comment">// 1 second delay</span>
    }
}
</code></pre>
<p>The <code>func main()</code> is the main event loop standard for embedded systems. It creates the sensor controller, reads sensor data in a loop, checks thresholds, and prints results accordingly. The loop includes a delay (via the C driver) to avoid hammering the sensor continuously.</p>
<p>In an actual embedded context, instead of using <code>print()</code>, you might blink an LED, send UART messages, or log data to memory.</p>
<p>With Embedded Swift and C now working together, let’s explore what lies ahead. The next section outlines ongoing improvements, emerging use cases, and research directions that are shaping the future of Embedded Swift.</p>
<h2 id="heading-future-work">Future Work</h2>
<p>Embedded Swift is still a young but rapidly evolving technology. Its modern language features, type safety, and performance make it an attractive option for embedded development, and ongoing work is expanding its capabilities, reach, and ecosystem.</p>
<h3 id="heading-ongoing-improvements">Ongoing Improvements</h3>
<p><strong>Compiler Optimizations</strong>: The Swift compiler team is actively improving code generation for embedded targets, including:</p>
<ul>
<li><p>Reducing binary size</p>
</li>
<li><p>Minimizing ARC overhead</p>
</li>
<li><p>Improving static dispatch performance</p>
</li>
</ul>
<p><strong>Hardware Support</strong>: Embedded Swift can target a wide variety of ARM and RISC-V microcontrollers, which are popular for building industrial applications. Support for additional architectures is being developed.</p>
<p><strong>Tooling Enhancements</strong>: Tooling support for Embedded Swift is still evolving, but several community-driven and open-source efforts are making development more accessible:</p>
<ul>
<li><p><strong>Build Systems</strong>: The Swift Embedded Working Group provides example projects that adapt Swift Package Manager (SwiftPM) for cross-compilation. Custom linker scripts and build helpers are available for platforms like STM32 and nRF52.</p>
</li>
<li><p><strong>Debugging Support</strong>: Developers can debug Embedded Swift programs using existing tools like GDB or OpenOCD, provided the build includes appropriate debug symbols. While not yet officially streamlined, this approach enables step-through debugging on real hardware.</p>
</li>
<li><p><strong>IDE Integration</strong>: There is no official IDE support yet, but some developers use VSCode with Swift syntax highlighting and external build tasks. These setups are still manual but serve as early prototypes for embedded workflows.</p>
</li>
</ul>
<h3 id="heading-emerging-use-cases">Emerging Use Cases</h3>
<p>There are a number of emerging use cases for embedded Swift. For example, Swift’s memory safety, type guarantees, and protocol-oriented design make it ideal for secure and scalable IoT devices, especially where firmware bugs could affect user safety or privacy.</p>
<p>The automotive sector is also exploring Swift for infotainment systems, driver assistance features, and safety-critical logic (where deterministic execution and safety matter).</p>
<p>Swift’s expressive syntax and compile-time safety make it suitable for industrial automation – think real-time control loops, sensor fusion systems, and edge devices in smart manufacturing.</p>
<p>It’s also useful for medical devices, as it aligns well with strict medical regulations around memory safety, type guarantees, and predictable resource usage.</p>
<h3 id="heading-community-and-ecosystem">Community and Ecosystem</h3>
<h4 id="heading-open-source-projects">Open Source Projects</h4>
<p>The Swift Embedded working group maintains <a target="_blank" href="https://github.com/swiftlang/swift-embedded-examples">example repositories</a> showcasing how to use Embedded Swift on microcontrollers such as STM32, nRF52, and ESP32. Early-stage libraries for UART, GPIO, and basic peripherals are emerging, though the ecosystem is still young compared to C or Rust.</p>
<h4 id="heading-learning-resources">Learning Resources</h4>
<p>While <a target="_blank" href="https://docs.swift.org/embedded/documentation/embedded">Embedded Swift</a> is not yet widely taught in formal curricula, community tutorials and exploratory projects (for example, Swift for Arduino) are lowering the barrier for hobbyists and independent learners. As tooling matures, educational adoption is likely to follow.</p>
<h4 id="heading-industry-interest">Industry Interest</h4>
<p>Embedded Swift is beginning to draw attention from developers and companies looking for safer, more maintainable alternatives to C. Although large-scale adoption remains limited, use cases like rapid prototyping, IoT development, and internal experimentation are gaining traction.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Embedded Swift represents a major step forward in embedded programming. By combining the power and safety of Swift with the low-level control needed for microcontrollers, it offers an exciting alternative to traditional C and C++ development.</p>
<p>While C will remain essential for hardware-level programming and performance-critical paths, Swift brings compelling advantages to many embedded scenarios:</p>
<ul>
<li><p><strong>Memory safety</strong>: Swift eliminates entire categories of bugs such as buffer overflows, use-after-free, and null pointer dereferencing.</p>
</li>
<li><p><strong>Type safety</strong>: Many logic errors are caught at compile time, long before they can cause runtime failures.</p>
</li>
<li><p><strong>Modern language features</strong>: Developers can use functional paradigms, generics, and protocol-oriented design even in embedded code.</p>
</li>
<li><p><strong>C interoperability</strong>: Swift works seamlessly with existing C libraries, allowing gradual adoption without rewriting low-level drivers.</p>
</li>
<li><p><strong>Developer productivity</strong>: Clear syntax, automatic memory management, and strong tooling lead to faster development and easier maintenance.</p>
</li>
</ul>
<p>Government and regulatory bodies are increasingly encouraging or mandating the use of memory-safe programming languages to reduce vulnerabilities in critical software systems. For example:</p>
<ul>
<li><p>In 2022, the <a target="_blank" href="https://media.defense.gov/2025/Jun/23/2003742198/-1/-1/0/CSI_MEMORY_SAFE_LANGUAGES_REDUCING_VULNERABILITIES_IN_MODERN_SOFTWARE_DEVELOPMENT.PDF"><strong>U.S. National Security Agency (NSA)</strong></a> recommended moving away from unsafe languages like C/C++ for new software projects, promoting memory-safe alternatives.</p>
</li>
<li><p>In June 2025, the NSA and CISA released a joint Cybersecurity Information Sheet titled “<a target="_blank" href="https://www.nsa.gov/Press-Room/Press-Releases-Statements/Press-Release-View/Article/4223298/nsa-and-cisa-release-csi-highlighting-importance-of-memory-safe-languages-in-so/">Memory Safe Languages: Reducing Vulnerabilities in Modern Software Development</a>”, which emphasized that memory safety flaws remain a persistent risk, and organizations should develop strategies to adopt memory-safe programming languages in new systems.</p>
</li>
<li><p>The <a target="_blank" href="https://www.trust-in-soft.com/resources/blogs/memory-safety-is-key-the-shift-in-u.s.-cyber-standards"><strong>U.S. Cybersecurity and Infrastructure Security Agency (CISA)</strong></a> and <a target="_blank" href="https://nvlpubs.nist.gov/nistpubs/specialpublications/nist.sp.800-218.pdf"><strong>NIST</strong></a> have echoed similar guidance in the context of national cybersecurity.</p>
</li>
</ul>
<p>While these documents do not mention Swift explicitly, Swift's strong type system, ARC-based memory model, and compile-time safety guarantees align closely with the goals outlined in these recommendations. As such, it offers a practical, developer-friendly path toward safer embedded development.</p>
<p>Swift may not be the right fit for every embedded system. In applications where every byte of memory or instruction cycle is critical, real-time guarantees are hard requirements, or toolchain maturity is essential (for example, RTOS integration, static analyzers), C or Rust may still be preferred.</p>
<p>But in many modern embedded applications, especially those involving rapid prototyping, fast product iteration, safety-critical or maintainable firmware, and interoperability with existing C codebases, Swift offers a highly productive and safe development experience.</p>
<p>Embedded Swift is still maturing, but its momentum is undeniable. With ongoing compiler work, community-driven examples, and growing interest from developers, it’s poised to play a major role in the future of embedded systems.</p>
<p>Whether you're building an IoT device, a piece of industrial equipment, or a proof-of-concept wearable, Swift can help you write safer, more expressive firmware, without giving up performance or control.</p>
<p>Swift can be especially powerful during the prototyping phase, when the primary goal is to validate functionality quickly and safely. And with its increasing support for multiple hardware platforms, it offers a strong foundation for bringing modern software development practices to the embedded world.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Beginner Developer's Guide to Scrum ]]>
                </title>
                <description>
                    <![CDATA[ Let me guess: you’re learning to code…alone. You’ve been grinding through tutorials. You've built a portfolio site, maybe deployed a few projects on GitHub. And now you're trying to land a job or join a team. Then the interviews start. Suddenly, peop... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-beginner-developers-guide-to-scrum/</link>
                <guid isPermaLink="false">68813c7579e092b166d373b6</guid>
                
                    <category>
                        <![CDATA[ Scrum ]]>
                    </category>
                
                    <category>
                        <![CDATA[ agile development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ project management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer ]]>
                    </category>
                
                    <category>
                        <![CDATA[ interview ]]>
                    </category>
                
                    <category>
                        <![CDATA[ guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ education ]]>
                    </category>
                
                    <category>
                        <![CDATA[ learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Product Management ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Career ]]>
                    </category>
                
                    <category>
                        <![CDATA[ workflow ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aditya Vikram Kashyap ]]>
                </dc:creator>
                <pubDate>Wed, 23 Jul 2025 19:48:05 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753300058064/7046dd6c-1d9e-4f06-9ca1-65b3bb7eec83.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Let me guess: you’re learning to code…alone.</p>
<p>You’ve been grinding through tutorials. You've built a portfolio site, maybe deployed a few projects on GitHub. And now you're trying to land a job or join a team.</p>
<p>Then the interviews start.</p>
<p>Suddenly, people ask:</p>
<ul>
<li><p>"Are you familiar with Agile?"</p>
</li>
<li><p>"Have you worked in a Scrum environment?"</p>
</li>
<li><p>"What’s your experience with sprints?"</p>
</li>
</ul>
<p>Cue the imposter syndrome. Because no one teaches this stuff in JavaScript 101.</p>
<p>This guide is for you.</p>
<p>I’ll help make the Scrum process – a very common way developers work together – <em>make actual sense</em>. I’ll walk you through the basics, but also tell you what developers actually <em>do</em>, how standups feel when you're new, and what’s expected of you when you’re no longer coding in a vacuum.</p>
<p>Let’s break it down.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-even-is-scrum">What Even Is Scrum?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-three-roles-in-scrum-and-who-does-what">The Three Roles in Scrum (and Who Does What)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-scrum-rhythm-what-a-sprint-actually-looks-like">The Scrum Rhythm: What a Sprint Actually Looks Like</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-who-attends-the-ceremonies">Who attends the Ceremonies:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-standups-where-you-talk-like-a-human-not-a-robot">Standups: Where You Talk Like a Human, Not a Robot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sprint-planning">Sprint Planning</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-whats-a-user-story-and-why-does-it-sound-like-a-childrens-book">What’s a User Story and Why Does It Sound Like a Children’s Book?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-counts-as-done-definition-of-done-and-why-its-important">What Counts as “Done”? Definition of Done and Why It’s Important</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-demos-retros-and-saying-the-hard-things">Demos, Retros, and Saying the Hard Things</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-tools-you-might-encounter">Tools You Might Encounter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-if-youre-preparing-for-a-job-heres-what-you-can-do">If You’re Preparing for a Job, Here’s What You Can Do</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-even-is-scrum"><strong>What Even Is Scrum?</strong></h2>
<p>Scrum is not a tool. It’s not a software. It’s not some elite thing only PMs care about.</p>
<p>It’s a lightweight framework that helps software teams build things incrementally, together, in short focused cycles called sprints.</p>
<p>Scrum is used by everyone from FAANG teams to indie dev shops because it helps:</p>
<ul>
<li><p>Keep teams aligned</p>
</li>
<li><p>Deliver working software fast</p>
</li>
<li><p>Course-correct often</p>
</li>
<li><p>Spot problems early (before they go nuclear)</p>
</li>
</ul>
<p>It’s the opposite of the old-school “build for a year and pray it works” model.</p>
<h2 id="heading-the-three-roles-in-scrum-and-who-does-what"><strong>The Three Roles in Scrum (and Who Does What)</strong></h2>
<p>Scrum officially defines three roles. Here's what that means in practice:</p>
<h3 id="heading-1-product-owner-po"><strong>1. Product Owner (PO)</strong></h3>
<p>Think: Vision-holder. They decide <em>what</em> the team builds and <em>why</em>. A product owner:</p>
<ul>
<li><p>Writes user stories (think of these as feature requests written from a user’s point of view)</p>
</li>
<li><p>Prioritizes the work</p>
</li>
<li><p>Clarifies what success looks like</p>
</li>
<li><p>Says “yes” or “not yet” to features</p>
</li>
</ul>
<h3 id="heading-2-scrum-master-sm"><strong>2. Scrum Master (SM)</strong></h3>
<p>Think: Air-traffic controller meets therapist. They make sure the process works. The are master Facilitators, like between Dev and PO’s. A Scrum Master:</p>
<ul>
<li><p>Facilitates meetings</p>
</li>
<li><p>Removes blockers (“Your AWS access is stuck? I’ll escalate it.”)</p>
</li>
<li><p>Coaches the team on Scrum practices</p>
</li>
<li><p>Doesn’t manage people – manages <em>flow</em></p>
</li>
</ul>
<h3 id="heading-3-developers-you"><strong>3. Developers (YOU!)</strong></h3>
<p>Think: Builders. You write code, test it, ship it, fix it, and improve it. You also:</p>
<ul>
<li><p>Break down stories into tasks</p>
</li>
<li><p>Pick up work from the team board (like Jira or Trello)</p>
</li>
<li><p>Communicate progress</p>
</li>
<li><p>Demo what you’ve built at the end of the sprint</p>
</li>
</ul>
<p>You might also work with designers, testers, or DevOps folks – but within Scrum, you’re all “developers” building a product together.</p>
<h2 id="heading-the-scrum-rhythm-what-a-sprint-actually-looks-like"><strong>The Scrum Rhythm: What a Sprint Actually Looks Like</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752809790048/253fd92b-1ebe-4f3e-bfbc-48719676dc82.png" alt="253fd92b-1ebe-4f3e-bfbc-48719676dc82" class="image--center mx-auto" width="900" height="530" loading="lazy"></p>
<p>Image Source: <a target="_blank" href="https://www.invensislearning.com/blog/what-are-scrum-ceremonies/">https://www.invensislearning.com/blog/what-are-scrum-ceremonies/</a></p>
<h3 id="heading-understanding-the-scrum-cycle"><strong>Understanding the Scrum Cycle</strong></h3>
<p>So, what does it <em>actually</em> look like when a team uses Scrum to build software?</p>
<p>Let’s walk through a full sprint – not just the buzzwords, but what really happens when a group of humans tries to plan, build, review, and improve together. Think of this as your backstage pass to the rhythm of modern teamwork.</p>
<h3 id="heading-step-1-build-and-refine-the-product-backlog">📦 Step 1: Build and Refine the Product Backlog</h3>
<p>Before any coding starts, the team needs to agree on <em>what</em> they might build – not just this week, but in the near future too.</p>
<p>That’s where the <strong>Product Backlog</strong> comes in. This is a big, running list of everything the product might need – features, bug fixes, improvements, ideas, and maybe a few wild dreams. It’s like the wishlist for the product, but more organized (ideally).</p>
<p>The Product Owner is responsible for maintaining and prioritizing this list. They decide what’s most important to work on based on customer needs, business goals, and feedback.</p>
<p>But the PO doesn’t do this in isolation. Enter the <strong>Backlog Refinement meeting</strong>.</p>
<p>In these sessions, the Scrum Team – that’s the PO, the Scrum Master (SM), and the Developers – come together to:</p>
<ul>
<li><p><strong>Review</strong> the most important upcoming items</p>
</li>
<li><p><strong>Clarify</strong> any vague or confusing parts of each task</p>
</li>
<li><p><strong>Break big items</strong> down into smaller, buildable pieces called <strong>user stories</strong></p>
</li>
<li><p><strong>Estimate effort</strong> (how much time or complexity is involved for each story)</p>
</li>
</ul>
<p>This meeting makes sure the team isn’t caught off guard in the sprint – that they understand the work ahead and can actually start sprinting when the time comes.</p>
<h3 id="heading-step-2-sprint-planning-what-are-we-building-this-time">🧭 Step 2: Sprint Planning – What Are We Building This Time?</h3>
<p>Now that we’ve got a solid backlog, it’s time to pick what to build <em>right now</em>.</p>
<p>At the start of each sprint (which typically lasts 1 to 4 weeks), the team holds a <strong>Sprint Planning meeting</strong>. This meeting sets the stage for the entire sprint – it’s like the huddle before the big game.</p>
<p>In Sprint Planning, the team:</p>
<ul>
<li><p>Reviews the top items from the backlog</p>
</li>
<li><p>Discusses what can realistically be completed based on their availability and capacity</p>
</li>
<li><p>Chooses a handful of these stories to commit to</p>
</li>
<li><p><strong>Defines a Sprint Goal</strong> – a simple statement that captures the purpose of this sprint</p>
</li>
</ul>
<p>For example, the Sprint Goal might be:<br>🎯 <em>“Allow users to reset their passwords.”</em></p>
<p>Every user story chosen should contribute to that goal. The collection of these stories becomes the <strong>Sprint Backlog</strong> – basically, the to-do list for the sprint.</p>
<p>So when we say:</p>
<p>“The team selects an ordered list of user stories to comprise the Sprint Backlog for the next sprint, which will be achievable to satisfy the Sprint Goal...”</p>
<p>We’re really just saying:<br>👉 <em>“Pick a realistic number of important tasks that, if completed, will help us hit our target for the sprint.”</em></p>
<p>Not too vague. Not too ambitious. Just achievable and focused.</p>
<h3 id="heading-step-3-daily-standups-stay-in-sync">☀️ Step 3: Daily Standups – Stay in Sync</h3>
<p>Now the sprint is underway! But how does everyone stay aligned and avoid working in silos?</p>
<p>That’s where the <strong>Daily Standup</strong> comes in. Every day – usually in the morning – the team has a quick check-in (about 15 minutes) where each person answers three questions:</p>
<ol>
<li><p><strong>What did I do yesterday?</strong></p>
</li>
<li><p><strong>What am I working on today?</strong></p>
</li>
<li><p><strong>Is anything blocking me?</strong> (that is, am I stuck?)</p>
</li>
</ol>
<p>Example:</p>
<p>“Yesterday I set up the login API integration. Today I’ll work on the UI validation. I’m blocked on getting access to the staging database — may need help.”</p>
<p>These standups keep the team in sync and surface blockers early so they can be addressed quickly. They’re not about micromanaging or showing off. They’re about visibility and support.</p>
<h3 id="heading-whats-a-sprint-burndown-chart">📉 What’s a Sprint Burndown Chart?</h3>
<p>You might hear your team mention a “burndown chart.” No, this isn’t about things going down in flames (hopefully).</p>
<p>A <strong>Sprint Burndown Chart</strong> is a graph that shows how much work is left in the sprint – day by day.</p>
<ul>
<li><p>The <strong>y-axis</strong> is the amount of work remaining (often measured in story points or tasks)</p>
</li>
<li><p>The <strong>x-axis</strong> is the number of days left in the sprint</p>
</li>
</ul>
<p>The line should ideally trend downward as work gets completed – hence “burning down.” If it flattens out or slopes up, that’s a red flag that the team might be stuck, behind schedule, or not updating the board.</p>
<p>Think of it as a visual heartbeat of the sprint. You can learn more via a practical example <a target="_blank" href="https://youtu.be/2K84aZn9AY8?si=tS8oMGxVD0CYtnlw">in this video</a>.</p>
<h3 id="heading-step-4-sprint-review-show-what-youve-built">🖥️ Step 4: Sprint Review – Show What You’ve Built</h3>
<p>At the end of the sprint, the team holds a <strong>Sprint Review</strong> (also called a “demo”). This is where you show what was actually built during the sprint.</p>
<ul>
<li><p>The <strong>Developers</strong> demo working features – live, not just screenshots</p>
</li>
<li><p>The <strong>Product Owner</strong> reviews whether the Sprint Goal was achieved</p>
</li>
<li><p>Stakeholders may ask questions, give feedback, or suggest tweaks</p>
</li>
</ul>
<p>This meeting isn’t just for show – it’s a feedback loop. It helps the team validate that what they built is useful, usable, and meets expectations. If changes are needed, those get added to the backlog for future sprints.</p>
<h3 id="heading-step-5-sprint-retrospective-look-back-to-move-forward">🔍 Step 5: Sprint Retrospective – Look Back to Move Forward</h3>
<p>Once the review is done, the team shifts focus from <em>what</em> they built to <em>how</em> they worked together.</p>
<p>Enter the <strong>Sprint Retrospective</strong> – a meeting to reflect on the process, not the product.</p>
<p>The team discusses:</p>
<ul>
<li><p>✅ What went well</p>
</li>
<li><p>❌ What didn’t go so well</p>
</li>
<li><p>🔁 What could be improved next time</p>
</li>
</ul>
<p>This isn’t about pointing fingers. It’s about learning, adapting, and continuously improving how the team collaborates.</p>
<p>The <strong>Scrum Master</strong> often facilitates this meeting and helps turn feedback into action items for the next sprint. For example:</p>
<p>“We underestimated testing time. Next sprint, let’s budget for QA earlier.”</p>
<p>The best teams take retros seriously. Why? Because even if your code is perfect, your <em>process</em> needs tuning too – and small process changes often lead to big gains.</p>
<h3 id="heading-scrum-is-a-loop">♻️ Scrum Is a Loop</h3>
<p>Here’s the rhythm:</p>
<ol>
<li><p>Plan the sprint</p>
</li>
<li><p>Check in daily</p>
</li>
<li><p>Build and demo the product</p>
</li>
<li><p>Reflect and improve</p>
</li>
</ol>
<p>Then do it all over again – with slightly better coordination and slightly more trust each time.</p>
<p>It’s not about being fast. It’s about being intentional, consistent, and collaborative.</p>
<h3 id="heading-example-sprint">Example Sprint</h3>
<p>Let’s say, for example, that your team does 4-week sprints. (Keep in mind that Sprints can differ by team, nature of product, release cycles, and so on.)</p>
<p>Here’s the rough beat:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Week</strong></td><td><strong>What Happens (Sprint Ceremonies)</strong></td><td><strong>Your Role</strong></td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td><strong>Sprint Planning</strong></td><td>Help estimate effort, pick what to build</td></tr>
<tr>
<td>1-4</td><td><strong>Daily Stand ups</strong> (15 mins)</td><td>Share what you’re doing &amp; any blockers</td></tr>
<tr>
<td>1-3</td><td><strong>Development Time</strong></td><td>Code, test, commit, fix, push, repeat</td></tr>
<tr>
<td>3.5-4</td><td><strong>Sprint Review</strong></td><td>Demo what you built</td></tr>
<tr>
<td>4</td><td><strong>Sprint Retrospective</strong></td><td>Reflect on how the sprint went as a team</td></tr>
</tbody>
</table>
</div><p>Scrum works in <strong>loops</strong>. Every 2-4 weeks (depending on your cadence and sprint cycle), your team should have working, demo-able software to show for it – even if it’s small.</p>
<p>And no, it’s not about “speed.” It’s about consistency, communication, and collaboration.</p>
<h2 id="heading-who-attends-the-ceremonies"><strong>Who attends the Ceremonies:</strong></h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Ceremony</strong></td><td><strong>Who Attends</strong></td><td><strong>Why They’re There</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Sprint Planning</strong></td><td>Product Owner (PO), Scrum Master (SM), Development Team</td><td>To define what will be delivered and how the work will be accomplished</td></tr>
<tr>
<td><strong>Daily Standup</strong></td><td>Development Team, Scrum Master (optional), PO (optional)</td><td>To sync on progress, share blockers, and coordinate efforts</td></tr>
<tr>
<td><strong>Sprint Review</strong></td><td>Development Team, Scrum Master, Product Owner, Stakeholders</td><td>To demo the work, get feedback, and assess if goals were met</td></tr>
<tr>
<td><strong>Sprint Retrospective</strong></td><td>Development Team, Scrum Master, Product Owner (optional)</td><td>To reflect on the process, identify what worked/what didn’t, and improve the next sprint</td></tr>
<tr>
<td><strong>Backlog Refinement</strong></td><td>Product Owner, Development Team, Scrum Master (optional)</td><td>To clarify upcoming stories, estimate work, and prepare for future sprint planning</td></tr>
</tbody>
</table>
</div><p>Now lets dive deeper and understand practically how each of these ceremonies work:</p>
<h2 id="heading-standups-where-you-talk-like-a-human-not-a-robot"><strong>Standups: Where You Talk Like a Human, Not a Robot</strong></h2>
<p>So how does the team actually stay connected day to day? That’s where standups come in.</p>
<p>Every morning, your team meets briefly – usually on Zoom or in a circle – and you answer 3 questions:</p>
<ol>
<li><p>What did I work on yesterday?</p>
</li>
<li><p>What will I work on today?</p>
</li>
<li><p>What’s blocking me? Any impediments?</p>
</li>
</ol>
<p>Example:</p>
<p>"Yesterday I cleaned up the signup validation logic. Today I’m working on the email verification flow. I’m stuck on SendGrid config – might need help setting up credentials."</p>
<p>It’s not about impressing anyone. It’s about keeping everyone in sync. Some days you’ll say, “I spent the whole day debugging a CSS bug that turned out to be a semicolon.” That’s okay.</p>
<p>How does it work?</p>
<p>The Scrum Master gathers everyone in a huddle room, the PO and Dev Team included, and opens the the Standup. They are the facilitator of the ceremony. Everyone gets a chance to answer the 3 questions above (usually about 2-5 minutes each). It’s not a full report – it’s quick. When one person is done, they pass it on to someone else.</p>
<p>This ensures there is team cohesion and transparency.</p>
<p><a target="_blank" href="https://youtu.be/q_R9wQY4G5I?si=W1AcvcLXB-mnUM1f">Here is a video example of a standup</a>.</p>
<h2 id="heading-sprint-planning"><strong>Sprint Planning</strong></h2>
<p>The goal of the planning meeting is to answer the questions “What are we going to work on, and how are we going to do it?” It is critical for the team to have a shared goal and a shared commitment to this goal before beginning this ceremony.</p>
<p>Participants should:</p>
<ul>
<li><p>Measure growth</p>
</li>
<li><p>Sync with the Scrum Master</p>
</li>
<li><p>Sync with the Product Owner</p>
</li>
</ul>
<p>Sprint planning happens just before the sprint starts, and usually lasts for an hour or two. In this meeting, the team goes over a collection of <strong>user stories</strong> and discuss, plan, measure, and prioritize. This is where they decide what is going to be in scope for their upcoming sprint cycle.</p>
<p>The Product Owner will have a prioritized view of things in the backlog. They work with the team on each object or customer experience. Together, as a group they go through and make calculations, deciding to what they can commit.</p>
<h2 id="heading-whats-a-user-story-and-why-does-it-sound-like-a-childrens-book"><strong>What’s a User Story and Why Does It Sound Like a Children’s Book?</strong></h2>
<p>So you might be wondering: how do you know what to work on? What to build? So much work, so little time? Thats where <strong>user stories</strong> come in.</p>
<p>In Scrum, teams don’t just write vague tasks like “code the login.” Instead, they write user stories – short, human-centered feature descriptions that describe what the user needs, why they need it, and what success looks like.</p>
<p>Here’s an example:</p>
<p><em>As a user, I want to be able to reset my password, so I can access my account if I forget it.</em></p>
<p>User stories are the scaffolding of teamwork. They’re written with empathy, not just efficiency. And each one comes with <strong>acceptance criteria</strong> – a checklist that clarifies what “done” actually means:</p>
<ul>
<li><p>A “Forgot Password” link is visible</p>
</li>
<li><p>Clicking it shows a form</p>
</li>
<li><p>An email gets sent with a reset link</p>
</li>
</ul>
<p>Once a story is agreed upon, developers break it down into tasks, like “build form,” “hook into backend,” or “handle email validation.” It’s collaborative, not prescriptive. And user stories have priority so you know what’s the most important and what’s the least.</p>
<p>A helpful rule of thumb many teams use is the <a target="_blank" href="https://medium.com/@nic/writing-user-stories-with-gherkin-dda63461b1d2"><strong>Gherkin</strong>-style "Given–When–Then"</a> format:</p>
<ul>
<li><p><strong>Given</strong> some initial context</p>
</li>
<li><p><strong>When</strong> an event occurs</p>
</li>
<li><p><strong>Then</strong> a specific outcome should happen</p>
</li>
</ul>
<p>This ensures that everyone – devs, testers, and product owners – shares the same understanding of behavior and expectations.</p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=7hoGqhb6qAs">Here is a great video example</a> thats outlines how to draft effective and powerful user stories.</p>
<h2 id="heading-what-counts-as-done-definition-of-done-and-why-its-important"><strong>What Counts as “Done”? Definition of Done and Why It’s Important</strong></h2>
<p>Now you might be wondering – how do I know when a task is done and can be closed out?</p>
<p>The <strong>Definition of Done</strong> is a type of documentation in the form of a <strong>team agreement</strong>. The Definition of Done identifies the conditions that need to be achieved in order for the product to be considered done (as in <strong>potentially shippable</strong>).</p>
<p>This is how we know that we "did the thing right". Meaning, we built the correct level of quality into the product. The Definition of Done is not the same as the acceptance criteria, which are written by the product owner to help us know we did the "right thing".</p>
<p>Every team has a Definition of Done – it’s not just “I pushed code.” It could mean:</p>
<ul>
<li><p>Code is written</p>
</li>
<li><p>Reviewed by a peer</p>
</li>
<li><p>Merged into main</p>
</li>
<li><p>Tested on staging</p>
</li>
<li><p>Possibly deployed</p>
</li>
</ul>
<p>This clarity keeps teams honest and accountable. No “it works on my machine” energy here. The DoD sets a quality bar. It prevents ambiguity, rework, and “it works on my machine” moments. When every card on the board passes the same finish line, teams move faster – and trust each other more.</p>
<p>Everyone should know what done is in a team. Either its Done as per DoD standards or its not.</p>
<p><a target="_blank" href="https://youtu.be/pYOJyQoBT3U?si=nVygkQQx79NaAOo4">Here is a beautiful video</a> highlighting the impotence of DoD.</p>
<h2 id="heading-demos-retros-and-saying-the-hard-things"><strong>Demos, Retros, and Saying the Hard Things</strong></h2>
<p>Once you’ve built the product, then comes demos (showcasing your work) and retros (analysis as a team on what when well and what areas to improve on).</p>
<p>In the retro, everyone’s encouraged to speak up:</p>
<ul>
<li><p>What went well?</p>
</li>
<li><p>What didn’t?</p>
</li>
<li><p>What should we try next time?</p>
</li>
</ul>
<p>Example:</p>
<p>“We missed a lot of stories because we didn’t account for testing time. Maybe we buffer next sprint with fewer tasks.”</p>
<p>The goal is not to blame – it’s to <em>improve</em>. Over time, this feedback loop becomes gold. The Scrum Master usually facilitates, collects feedback (via tools like Parabol, Miro, or sticky notes), and helps turn insights into actionable experiments for the next sprint.</p>
<p>Over time, retros become the heartbeat of team evolution.</p>
<p><a target="_blank" href="https://youtu.be/5eu1HotNmWs?si=1DZaSmztB6rHyawj">Here is a video</a> highlighting the importance of a Retro and Sprint Review.</p>
<h3 id="heading-why-retrospection-matters-more-than-you-think">🧠 Why Retrospection Matters More Than You Think</h3>
<p>The Sprint Retrospective is more than just another meeting. It’s a mirror for your team – a safe, structured space to pause, reflect, and improve together.</p>
<p>You discuss:</p>
<p>✅ what went well</p>
<p>❌ what did not go well</p>
<p>🔁 what could we do better next time</p>
<p>Great teams don't just deliver great software, they continually deliver better ways of working.</p>
<p>This is why many experienced Scrum practitioners consider the retro to be the most important event in Scrum. Code is deployed once, but process improvements grow exponentially, sprint after sprint.</p>
<h2 id="heading-tools-you-might-encounter"><strong>Tools You Might Encounter</strong></h2>
<p>Scrum doesn’t require software, but real-world teams use a variety of tools:</p>
<ul>
<li><p><strong>Jira</strong> – Tracks sprints, issues, velocity</p>
</li>
<li><p><strong>Trello</strong> – Simple board, good for small teams</p>
</li>
<li><p><strong>Slack</strong> – Where standups often happen if async</p>
</li>
<li><p><strong>Notion / Confluence</strong> – Docs, retros, notes</p>
</li>
<li><p><strong>GitHub Projects</strong> – Lightweight planning for devs</p>
</li>
</ul>
<p>Don’t worry if you’re not fluent in these yet. They’re tools – you’ll learn them on the job.</p>
<h2 id="heading-if-youre-preparing-for-a-job-heres-what-you-can-do"><strong>If You’re Preparing for a Job, Here’s What You Can Do</strong></h2>
<ul>
<li><p>✍️ Practice writing user stories from your side projects</p>
</li>
<li><p>🧪 Run a mini-sprint: Plan your weekend project, set goals, and “review” it at the end</p>
</li>
<li><p>🤝 Contribute to an open-source project that uses Scrum or Agile workflows</p>
</li>
<li><p>🧾 Write about what you learned – maybe as a tutorial (<em>hint hint</em>)</p>
</li>
</ul>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>So to recap, Scrum is a simple yet powerful way for teams to work together, stay organized, and deliver results quickly. It runs in short cycles called <strong>sprints</strong>, where the team plans what to do, checks in daily, shows their progress at the end, and reflects on how to improve.</p>
<p>The four key ceremonies – <strong>Sprint Planning</strong>, <strong>Daily Scrum</strong>, <strong>Sprint Review</strong>, and <strong>Sprint Retrospective</strong> – help keep everyone aligned and focused. With clear roles and regular feedback, Scrum makes it easier to handle changes, solve problems early, and continuously get better as a team.</p>
<p>But scrum isn’t a magic spell. It’s just a way for humans to build complex things – together – without falling apart.</p>
<p>You don’t need to be a Scrum Master. You don’t need a certification. But if you understand how sprints work, what’s expected of you, and how to show up to meetings with clarity and candor, you’re 10 steps ahead of most.</p>
<p>Scrum helps teams talk, plan, build, and learn. And now? You can too.</p>
<p>If you liked this, please do share. You never know who it might help out.</p>
<p>Until then…keep learning, unlearning, and relearning!!!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Production-Ready Full Stack Apps with the MERN Stack ]]>
                </title>
                <description>
                    <![CDATA[ As developers, we’re always looking for more efficient tools. The MERN stack (MongoDB, Express.js, React, and Node.js) stands out for its JavaScript-centric nature, offering a unified language across the entire application. In this guide, you'll buil... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-production-ready-full-stack-apps-with-the-mern-stack/</link>
                <guid isPermaLink="false">686bd0446349e98b57ebc099</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clean code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mohit Menghnani ]]>
                </dc:creator>
                <pubDate>Mon, 07 Jul 2025 13:48:52 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751502709499/b43b3607-f01b-45c0-9797-75eef92497c6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As developers, we’re always looking for more efficient tools. The MERN stack (MongoDB, Express.js, React, and Node.js) stands out for its JavaScript-centric nature, offering a unified language across the entire application.</p>
<p>In this guide, you'll build a complete Task Manager app with user authentication, protected routes, and full CRUD functionality, built with React on the frontend and Express/MongoDB on the backend.</p>
<p>This article will serve as your hands-on, code-first guide to building, securing, and deploying a MERN application, drawing from my own practical experience. Every section has code you can run, and I’ll give concise explanations along the way.</p>
<p>It doesn’t matter if you're just getting started with MERN or looking to level up your architecture and production deployment knowledge – this article is designed to get you from zero to production with confidence.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-tools-amp-tech-stack">Tools &amp; Tech Stack</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-skills-amp-setup">Skills &amp; Setup</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup-laying-the-groundwork">Project Setup: Laying the Groundwork</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-structure">Project Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-quality-linting-and-formatting">Code Quality: Linting and Formatting</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-ensuring-robustness">Testing: Ensuring Robustness</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-backend-testing-nodejsexpressjs">Backend Testing (Node.js/Express.js)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-testing-react-testing-library-cypress">Frontend Testing (React Testing Library + Cypress)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-task-manager">How to Build the Task Manager</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-backend-implementation-nodejsexpressjs">Backend Implementation (Node.js/Express.js)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-implementation-react">Frontend Implementation (React)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-deployment-from-localhost-to-live">Deployment: From</a> <a target="_blank" href="http://Localhost">Localhost</a> <a class="post-section-overview" href="#heading-deployment-from-localhost-to-live">to Live</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-backend-deployment-nodejsexpressjs">Backend Deployment (Node.js/Express.js)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-deployment-react">Frontend Deployment (React)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-database-deployment-mongodb-atlas">Database Deployment (MongoDB Atlas)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-1-env-configuration-example">1. .env Configuration Example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-connect-to-mongodb-in-appjs">2. Connect to MongoDB in app.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-deployment-options">Other Deployment Options</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-security-best-practices-fortifying-your-application">Security Best Practices: Fortifying Your Application</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-setup-input-validation-and-sanitization">Setup Input Validation and Sanitization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-add-authentication-and-authorization">Add Authentication and Authorization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-implement-rate-limiting">Implement Rate Limiting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setup-cors-configuration-cross-origin-resource-sharing">Setup CORS Configuration (Cross-Origin Resource Sharing)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-environment-variables">Use Environment Variables</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-monitoring-and-logging-with-winston-and-morgan">Monitoring and Logging with Winston and Morgan</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-frontend-error-monitoring-sentry">Frontend Error Monitoring (Sentry)</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before jumping in the project, here’s what you’ll need to get the most out of this tutorial:</p>
<h3 id="heading-tools-amp-tech-stack">Tools &amp; Tech Stack</h3>
<p>You’ll be using the following technologies throughout the project:</p>
<ul>
<li><p><strong>Node.js &amp; npm</strong> – Backend runtime and package manager</p>
</li>
<li><p><strong>Express.js</strong> – Web framework for Node</p>
</li>
<li><p><strong>MongoDB Atlas</strong> – Cloud-hosted NoSQL database</p>
</li>
<li><p><strong>Mongoose</strong> – ODM for MongoDB</p>
</li>
<li><p><strong>React</strong> – Frontend UI library</p>
</li>
<li><p><strong>React Router</strong> – For client-side routing</p>
</li>
<li><p><strong>Axios</strong> – For making API requests</p>
</li>
<li><p><strong>Jest &amp; Supertest</strong> – For backend tests</p>
</li>
<li><p><strong>React Testing Library &amp; Cypress</strong> – For Frontend unit and E2E tests</p>
</li>
<li><p><strong>ESLint + Prettier</strong> – For code formatting, linting</p>
</li>
<li><p><strong>Husky</strong> – To setup pre-commit hooks</p>
</li>
<li><p><strong>Helmet, Joi, express-rate-limit, cors</strong> – For security, validation, and best practices</p>
</li>
<li><p><strong>PM2 &amp; NGINX</strong> – For backend deployment</p>
</li>
<li><p><strong>Sentry</strong> – For error monitoring</p>
</li>
</ul>
<h3 id="heading-skills-amp-setup">Skills &amp; Setup</h3>
<ul>
<li><p>Basic knowledge of JavaScript, React, and Node.js</p>
</li>
<li><p>Familiarity with REST APIs and HTTP request/response flows</p>
</li>
<li><p>Git and a GitHub account for version control</p>
</li>
<li><p>A free MongoDB Atlas account</p>
</li>
<li><p>Node.js and npm installed locally (Node 18+ recommended)</p>
</li>
</ul>
<h2 id="heading-project-setup-laying-the-groundwork"><strong>Project Setup: Laying the Groundwork</strong></h2>
<p>A well-structured project is crucial for maintainability. We'll adopt a clear separation between the front end and the back end here.</p>
<h3 id="heading-project-structure"><strong>Project Structure</strong></h3>
<p>This structure clearly separates the React front end (client/) from the Node.js/Express.js back end (server/), promoting modularity and easier management.</p>
<pre><code class="lang-javascript">my-mern-app/                # Root folder
├── client/                 # React frontend
│   ├── public/
│   ├── src/
│   │   ├── components/
│   │   ├── pages/
│   │   ├── App.js
│   │   └── index.js
│   └── package.json
├── server/                 # Node.js/Express.js backend
│   ├── config/
│   ├── controllers/
│   ├── models/
│   ├── routes/
│   ├── services/
│   ├── app.js
│   └── package.json
</code></pre>
<h3 id="heading-code-quality-linting-and-formatting"><strong>Code Quality: Linting and Formatting</strong></h3>
<p>Consistency is key when you’re building a production-grad application like this one. We'll use ESLint with Airbnb style and Prettier for automated code quality and formatting.</p>
<p>To install these tools, run this in your terminal:</p>
<pre><code class="lang-bash">npm install --save-dev eslint prettier eslint-config-airbnb-base eslint-plugin-prettier
</code></pre>
<p>And here are some setups with their recommended configurations:</p>
<p>This configuration sets up ESLint for a Node.js project using the Airbnb and Prettier style guides, with custom rules to relax strict linting constraints like allowing <code>console.log</code> and disabling mandatory function names.</p>
<h4 id="heading-eslintrcjs-server-side-example"><strong>.eslintrc.js (server-side example)</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {

  <span class="hljs-attr">env</span>: {

    <span class="hljs-attr">node</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">commonjs</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">es2021</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">extends</span>: [<span class="hljs-string">"airbnb-base"</span>, <span class="hljs-string">"prettier"</span>],

  <span class="hljs-attr">plugins</span>: [<span class="hljs-string">"prettier"</span>],

  <span class="hljs-attr">parserOptions</span>: {

    <span class="hljs-attr">ecmaVersion</span>: <span class="hljs-number">12</span>,

  },

  <span class="hljs-attr">rules</span>: {

    <span class="hljs-string">"prettier/prettier"</span>: <span class="hljs-string">"error"</span>,

    <span class="hljs-string">"no-console"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"func-names"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"no-process-exit"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"class-methods-use-this"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"import/no-extraneous-dependencies"</span>: <span class="hljs-string">"off"</span>,

  },

};
</code></pre>
<h4 id="heading-prettierrc"><strong>.prettierrc</strong></h4>
<p>This config enforces consistent formatting: add semicolons, use trailing commas where valid, and prefer single quotes for strings.</p>
<pre><code class="lang-json">{

  <span class="hljs-attr">"semi"</span>: <span class="hljs-literal">true</span>,

  <span class="hljs-attr">"trailingComma"</span>: <span class="hljs-string">"all"</span>,

  <span class="hljs-attr">"singleQuote"</span>: <span class="hljs-literal">true</span>

}
</code></pre>
<h3 id="heading-version-control-git-essentials"><strong>Version Control: Git Essentials</strong></h3>
<p>Git is indispensable. You can use feature branches and pull requests for collaborative development, making it easier to work on large projects with coworkers. Consider using Husky for pre-commit hooks to enforce linting and testing.</p>
<h4 id="heading-install-husky">Install Husky:</h4>
<p>Install Husky to easily manage Git hooks, allowing you to automate tasks like linting and testing before commits.</p>
<pre><code class="lang-bash">npm install husky --save-dev
</code></pre>
<h4 id="heading-packagejson-add-script">package.json (add script)</h4>
<p>This <code>package.json</code> file sets up a Node.js project named <code>my-mern-app</code>, and configures a <code>prepare</code> script to install Git hooks using Husky (v7). It's ready for adding pre-commit automation, such as linting or testing.</p>
<pre><code class="lang-json">{

  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"my-mern-app"</span>,

  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,

  <span class="hljs-attr">"description"</span>: <span class="hljs-string">""</span>,

  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,

  <span class="hljs-attr">"scripts"</span>: {

    <span class="hljs-attr">"prepare"</span>: <span class="hljs-string">"husky install"</span>

  },

  <span class="hljs-attr">"keywords"</span>: [],

  <span class="hljs-attr">"author"</span>: <span class="hljs-string">""</span>,

  <span class="hljs-attr">"license"</span>: <span class="hljs-string">"ISC"</span>,

  <span class="hljs-attr">"devDependencies"</span>: {

    <span class="hljs-attr">"husky"</span>: <span class="hljs-string">"^7.0.0"</span>

  }

}
</code></pre>
<h4 id="heading-create-a-pre-commit-hook">Create a pre-commit hook</h4>
<p>The below command sets up a pre-commit hook that automatically runs your tests and linter before each commit, ensuring code quality and preventing errors from entering your codebase.</p>
<pre><code class="lang-json">npx husky add .husky/pre-commit <span class="hljs-string">"npm test &amp;&amp; npm run lint"</span>
</code></pre>
<h2 id="heading-testing-ensuring-robustness"><strong>Testing: Ensuring Robustness</strong></h2>
<p>Automated testing is vital. We'll cover unit, integration, and end-to-end testing in this guide.</p>
<h3 id="heading-backend-testing-nodejsexpressjs">Backend Testing (Node.js/Express.js)</h3>
<p>You’ll use Jest for unit testing and Supertest for API integration tests.</p>
<h5 id="heading-install-them-like-this">Install them like this:</h5>
<pre><code class="lang-bash">npm install --save-dev jest supertest
</code></pre>
<p>You’ll use Jest to write unit tests for your JavaScript code and Supertest to test HTTP requests against your Express.js API.</p>
<h4 id="heading-example-test-servertestsauthtestjs">Example Test (server/tests/auth.test.js):</h4>
<p>This test suite uses Supertest to simulate API calls for user registration and login, asserting that the responses have the expected status codes and properties.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'supertest'</span>);

<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../app'</span>); <span class="hljs-comment">// Your Express app instance</span>

describe(<span class="hljs-string">'Auth API'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should register a new user'</span>, <span class="hljs-keyword">async</span> () =&gt; {

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app)

      .post(<span class="hljs-string">'/api/auth/register'</span>)

      .send({

        <span class="hljs-attr">username</span>: <span class="hljs-string">'testuser'</span>,

        <span class="hljs-attr">email</span>: <span class="hljs-string">'test@example.com'</span>,

        <span class="hljs-attr">password</span>: <span class="hljs-string">'password123'</span>,

      });

    expect(res.statusCode).toEqual(<span class="hljs-number">201</span>);

    expect(res.body).toHaveProperty(<span class="hljs-string">'_id'</span>);

  });


  it(<span class="hljs-string">'should login an existing user'</span>, <span class="hljs-keyword">async</span> () =&gt; {

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app)

      .post(<span class="hljs-string">'/api/auth/login'</span>)

      .send({

        <span class="hljs-attr">email</span>: <span class="hljs-string">'test@example.com'</span>,

        <span class="hljs-attr">password</span>: <span class="hljs-string">'password123'</span>,

      });

    expect(res.statusCode).toEqual(<span class="hljs-number">200</span>);

    expect(res.headers[<span class="hljs-string">'set-cookie'</span>]).toBeDefined();

  });

});
</code></pre>
<h3 id="heading-frontend-testing-react-testing-library-cypress">Frontend Testing (React Testing Library + Cypress)</h3>
<p>You’ll use Jest and the React Testing Library for unit/integration tests, and Cypress for E2E tests.</p>
<h5 id="heading-you-can-install-these-like-this">You can install these like this:</h5>
<pre><code class="lang-bash">npm install --save-dev @testing-library/react @testing-library/jest-dom jest cypress
</code></pre>
<p>React Testing Library will help you test your React components, and Cypress will provide comprehensive end-to-end testing of your frontend application.</p>
<h4 id="heading-example-component-test-clientsrccomponentsbuttontestjs">Example Component Test (client/src/components/Button.test.js):</h4>
<p>This unit test uses the React Testing Library to render a Button component and verifies that the specified text content is present in the rendered output.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;

<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">'./Button'</span>;


test(<span class="hljs-string">'renders button with text'</span>, <span class="hljs-function">() =&gt;</span> {

  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span>&gt;</span>Click Me<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span></span>);

  <span class="hljs-keyword">const</span> buttonElement = screen.getByText(<span class="hljs-regexp">/Click Me/i</span>);

  expect(buttonElement).toBeInTheDocument();

});
</code></pre>
<p>The following Cypress test simulates a complete user authentication flow, from registration to login and logout, asserting expected URL changes and page content.</p>
<pre><code class="lang-javascript">Example E2E Test (cypress/e2e/auth.cy.js)

describe(<span class="hljs-string">'Authentication Flow'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should allow a user to register and login'</span>, <span class="hljs-function">() =&gt;</span> {

    cy.visit(<span class="hljs-string">'/register'</span>);

    cy.get(<span class="hljs-string">'input[name="username"]'</span>).type(<span class="hljs-string">'e2euser'</span>);

    cy.get(<span class="hljs-string">'input[name="email"]'</span>).type(<span class="hljs-string">'e2e@example.com'</span>);

    cy.get(<span class="hljs-string">'input[name="password"]'</span>).type(<span class="hljs-string">'password123'</span>);

    cy.get(<span class="hljs-string">'button[type="submit"]'</span>).click();

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/dashboard'</span>);

    cy.contains(<span class="hljs-string">'Welcome, e2euser'</span>);

    cy.get(<span class="hljs-string">'button'</span>).contains(<span class="hljs-string">'Logout'</span>).click();

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/login'</span>);

    cy.get(<span class="hljs-string">'input[name="email"]'</span>).type(<span class="hljs-string">'e2e@example.com'</span>);

    cy.get(<span class="hljs-string">'input[name="password"]'</span>).type(<span class="hljs-string">'password123'</span>);

    cy.get(<span class="hljs-string">'button[type="submit"]'</span>).click();

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/dashboard'</span>);

  });

});
</code></pre>
<h2 id="heading-how-to-build-the-task-manager"><strong>How to Build the Task Manager</strong></h2>
<p>We'll build a simple Task Manager with user authentication and CRUD operations for tasks so you can see how the whole thing comes together.</p>
<h3 id="heading-backend-implementation-nodejsexpressjs">Backend Implementation (Node.js/Express.js)</h3>
<h4 id="heading-dependencies">Dependencies</h4>
<p>Start by installing our core backend libraries: Express for routing, Mongoose for MongoDB interactions, dotenv for environment variables, bcrypt/jsonwebtoken/cookie-parser for secure authentication, and helmet for setting secure HTTP headers:</p>
<pre><code class="lang-bash">npm install express mongoose dotenv bcryptjs jsonwebtoken cookie-parser
</code></pre>
<h4 id="heading-serverappjs-entry-point">server/app.js (Entry Point)</h4>
<p>Next, we’ll set up the first or the main entry point for the backend. This is the main Express.js application file, which configures middleware, establishes a MongoDB connection, and sets up API routes for authentication and task management.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>);

<span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cookie-parser'</span>);

<span class="hljs-keyword">const</span> helmet = <span class="hljs-built_in">require</span>(<span class="hljs-string">'helmet'</span>);

<span class="hljs-keyword">const</span> authRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/authRoutes'</span>);

<span class="hljs-keyword">const</span> taskRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/taskRoutes'</span>);

<span class="hljs-keyword">const</span> { notFound, errorHandler } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./middleware/errorMiddleware'</span>);

dotenv.config();


<span class="hljs-keyword">const</span> app = express();

app.use(helmet());

app.use(express.json());

app.use(cookieParser());


mongoose.connect(process.env.MONGO_URI)

  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'MongoDB connected!'</span>))

  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'MongoDB connection error:'</span>, err));


app.use(<span class="hljs-string">'/api/auth'</span>, authRoutes);

app.use(<span class="hljs-string">'/api/tasks'</span>, taskRoutes);


app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {

  res.send(<span class="hljs-string">'MERN Task Manager API is running!'</span>);

});


app.use(notFound);

app.use(errorHandler);


<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port <span class="hljs-subst">${PORT}</span>`</span>);

});
</code></pre>
<h4 id="heading-serverenv">server/.env</h4>
<p>To avoid hardcoding secrets, we’ll add a <code>.env</code> file where we can securely store environment variables, such as our database URI and JWT secret. This file stores sensitive environment variables such as your MongoDB connection string, server port, and JWT secret, keeping them secure and separate from your codebase.</p>
<pre><code class="lang-bash">MONGO_URI=your_mongodb_connection_string_here

PORT=5000

JWT_SECRET=supersecretjwtkey
</code></pre>
<h4 id="heading-servermodelsuserjs">server/models/User.js</h4>
<p>Now, let’s define our User model using MongoDB. This schema includes fields for username, email, and password, with pre-save hooks for password hashing and a method for password comparison.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

<span class="hljs-keyword">const</span> bcrypt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'bcryptjs'</span>);


<span class="hljs-keyword">const</span> UserSchema = <span class="hljs-keyword">new</span> mongoose.Schema({

  <span class="hljs-attr">username</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">email</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">password</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

  },

});

UserSchema.pre(<span class="hljs-string">'save'</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">next</span>) </span>{

  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isModified(<span class="hljs-string">'password'</span>)) {

    next();

  }

  <span class="hljs-keyword">const</span> salt = <span class="hljs-keyword">await</span> bcrypt.genSalt(<span class="hljs-number">10</span>);

  <span class="hljs-built_in">this</span>.password = <span class="hljs-keyword">await</span> bcrypt.hash(<span class="hljs-built_in">this</span>.password, salt);

});


UserSchema.methods.matchPassword = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">enteredPassword</span>) </span>{

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bcrypt.compare(enteredPassword, <span class="hljs-built_in">this</span>.password);

};


<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'User'</span>, UserSchema);
</code></pre>
<h4 id="heading-servermodelstaskjs">server/models/Task.js</h4>
<p>Next, we’ll create the Task model. This schema defines the Task model, which links each task to a user and includes fields for title, description, completion status, and creation timestamp.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);


<span class="hljs-keyword">const</span> TaskSchema = <span class="hljs-keyword">new</span> mongoose.Schema({

  <span class="hljs-attr">user</span>: {

    <span class="hljs-attr">type</span>: mongoose.Schema.Types.ObjectId,

    <span class="hljs-attr">ref</span>: <span class="hljs-string">'User'</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">title</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">description</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">completed</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">Boolean</span>,

    <span class="hljs-attr">default</span>: <span class="hljs-literal">false</span>,

  },

  <span class="hljs-attr">createdAt</span>: {

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>,

    <span class="hljs-attr">default</span>: <span class="hljs-built_in">Date</span>.now,

  },

});


<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'Task'</span>, TaskSchema);
</code></pre>
<h4 id="heading-servercontrollersauthcontrollerjs">server/controllers/authController.js</h4>
<p>Let’s build out the authentication controller. This controller handles user authentication flows, including registration, login, logout, and fetching user profiles, using JWTs and secure HTTP-only cookies.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/User'</span>);

<span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);

<span class="hljs-keyword">const</span> generateToken = <span class="hljs-function">(<span class="hljs-params">id</span>) =&gt;</span> {

  <span class="hljs-keyword">return</span> jwt.sign({ id }, process.env.JWT_SECRET, {

    <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'1h'</span>,

  });

};

<span class="hljs-built_in">exports</span>.registerUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { username, email, password } = req.body;

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> userExists = <span class="hljs-keyword">await</span> User.findOne({ email });

    <span class="hljs-keyword">if</span> (userExists) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User already exists'</span> });

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.create({ username, email, password });

    <span class="hljs-keyword">if</span> (user) {

      <span class="hljs-keyword">const</span> token = generateToken(user._id);

      res.cookie(<span class="hljs-string">'token'</span>, token, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">'production'</span>, <span class="hljs-attr">maxAge</span>: <span class="hljs-number">3600000</span> });

      res.status(<span class="hljs-number">201</span>).json({ <span class="hljs-attr">id</span>: user.id, <span class="hljs-attr">username</span>: user.username, <span class="hljs-attr">email</span>: user.email });

    } <span class="hljs-keyword">else</span> {

      res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid user data'</span> });

    }

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.loginUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { email, password } = req.body;

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });

    <span class="hljs-keyword">if</span> (user &amp;&amp; (<span class="hljs-keyword">await</span> user.matchPassword(password))) {

      <span class="hljs-keyword">const</span> token = generateToken(user._id);

      res.cookie(<span class="hljs-string">'token'</span>, token, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">'production'</span>, <span class="hljs-attr">maxAge</span>: <span class="hljs-number">3600000</span> });

      res.json({ <span class="hljs-attr">id</span>: user.id, <span class="hljs-attr">username</span>: user.username, <span class="hljs-attr">email</span>: user.email });

    } <span class="hljs-keyword">else</span> {

      res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid email or password'</span> });

    }

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.logoutUser = <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {

  res.cookie(<span class="hljs-string">'token'</span>, <span class="hljs-string">''</span>, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">0</span>) });

  res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Logged out successfully'</span> });

};


<span class="hljs-built_in">exports</span>.getUserProfile = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(req.user._id).select(<span class="hljs-string">'-password'</span>);

    <span class="hljs-keyword">if</span> (user) {

      res.json(user);

    } <span class="hljs-keyword">else</span> {

      res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User not found'</span> });

    }

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};
</code></pre>
<h4 id="heading-servercontrollerstaskcontrollerjs">server/controllers/taskController.js</h4>
<p>Now it’s time to implement the task controller. This controller provides the logic for fetching, creating, updating, and deleting tasks, ensuring that users can only interact with their tasks.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Task = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/Task'</span>);


<span class="hljs-built_in">exports</span>.getTasks = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> tasks = <span class="hljs-keyword">await</span> Task.find({ <span class="hljs-attr">user</span>: req.user._id });

    res.status(<span class="hljs-number">200</span>).json(tasks);

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.createTask = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { title, description } = req.body;

  <span class="hljs-keyword">if</span> (!title) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Please add a title'</span> });

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> Task.create({ title, description, <span class="hljs-attr">user</span>: req.user._id });

    res.status(<span class="hljs-number">201</span>).json(task);

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.updateTask = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> Task.findById(req.params.id);

    <span class="hljs-keyword">if</span> (!task) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Task not found'</span> });

    <span class="hljs-keyword">if</span> (task.user.toString() !== req.user._id.toString()) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized'</span> });


    <span class="hljs-keyword">const</span> updatedTask = <span class="hljs-keyword">await</span> Task.findByIdAndUpdate(req.params.id, req.body, { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">runValidators</span>: <span class="hljs-literal">true</span> });

    res.status(<span class="hljs-number">200</span>).json(updatedTask);

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.deleteTask = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">try</span> {

    <span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> Task.findById(req.params.id);

    <span class="hljs-keyword">if</span> (!task) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Task not found'</span> });

    <span class="hljs-keyword">if</span> (task.user.toString() !== req.user._id.toString()) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized'</span> });


    <span class="hljs-keyword">await</span> Task.deleteOne({ <span class="hljs-attr">_id</span>: req.params.id });

    res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Task removed'</span> });

  } <span class="hljs-keyword">catch</span> (error) {

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};
</code></pre>
<h4 id="heading-servermiddlewareauthmiddlewarejs">server/middleware/authMiddleware.js</h4>
<p>To protect private routes<strong>,</strong> we will create a middleware that verifies the JWT from the request's cookies, ensuring that only authenticated users can access specific endpoints.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);

<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/User'</span>);

<span class="hljs-built_in">exports</span>.protect = <span class="hljs-keyword">async</span> (req, res, next) =&gt; {

  <span class="hljs-keyword">let</span> token;

  <span class="hljs-keyword">if</span> (req.cookies.token) {

    <span class="hljs-keyword">try</span> {

      token = req.cookies.token;

      <span class="hljs-keyword">const</span> decoded = jwt.verify(token, process.env.JWT_SECRET);

      req.user = <span class="hljs-keyword">await</span> User.findById(decoded.id).select(<span class="hljs-string">'-password'</span>);

      next();

    } <span class="hljs-keyword">catch</span> (error) {

      res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized, token failed'</span> });

    }

  } <span class="hljs-keyword">else</span> {

    res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized, no token'</span> });

  }

};
</code></pre>
<h4 id="heading-servermiddlewareerrormiddlewarejs">server/middleware/errorMiddleware.js</h4>
<p>To handle errors cleanly across our backend, we’ll add global error-handling middleware that can handle 404 Not Found errors and provide a centralized error-handling mechanism for consistent API error responses.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">exports</span>.notFound = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> error = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Not Found - <span class="hljs-subst">${req.originalUrl}</span>`</span>);

  res.status(<span class="hljs-number">404</span>);

  next(error);

};


<span class="hljs-built_in">exports</span>.errorHandler = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> statusCode = res.statusCode === <span class="hljs-number">200</span> ? <span class="hljs-number">500</span> : res.statusCode;

  res.status(statusCode);

  res.json({

    <span class="hljs-attr">message</span>: err.message,

    <span class="hljs-attr">stack</span>: process.env.NODE_ENV === <span class="hljs-string">'production'</span> ? <span class="hljs-literal">null</span> : err.stack,

  });

};
</code></pre>
<h4 id="heading-serverroutesauthroutesjs">server/routes/authRoutes.js</h4>
<p>Next, let’s define our authentication routes. These endpoints enable user authentication and map HTTP methods to their corresponding controller functions.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> { registerUser, loginUser, logoutUser, getUserProfile } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../controllers/authController'</span>);

<span class="hljs-keyword">const</span> { protect } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../middleware/authMiddleware'</span>);


<span class="hljs-keyword">const</span> router = express.Router();


router.post(<span class="hljs-string">'/register'</span>, registerUser);

router.post(<span class="hljs-string">'/login'</span>, loginUser);

router.get(<span class="hljs-string">'/logout'</span>, logoutUser);

router.get(<span class="hljs-string">'/profile'</span>, protect, getUserProfile);


<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<h4 id="heading-serverroutestaskroutesjs">server/routes/taskRoutes.js</h4>
<p>Now we’ll add the routes for task operations. This file defines the API routes for task management, applying the protect middleware to secure all task-related operations.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> { getTasks, createTask, updateTask, deleteTask } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../controllers/taskController'</span>);

<span class="hljs-keyword">const</span> { protect } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../middleware/authMiddleware'</span>);

<span class="hljs-keyword">const</span> router = express.Router();

router.route(<span class="hljs-string">'/'</span>).get(protect, getTasks).post(protect, createTask);

router.route(<span class="hljs-string">'/:id'</span>).put(protect, updateTask).delete(protect, deleteTask);

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<h3 id="heading-frontend-implementation-react">Frontend Implementation (React)</h3>
<h4 id="heading-dependencies-1">Dependencies</h4>
<p>Now, you’ll need to initialize a new React project and install your essential libraries: Axios for HTTP requests, React Router for navigation, and React Toastify for displaying notifications.</p>
<pre><code class="lang-bash">npm install axios react-router-dom react-toastify
</code></pre>
<h4 id="heading-clientsrcindexjs">client/src/index.js</h4>
<p>Let’s start the frontend by setting up the entry point. Here we are rendering the main App component and wrapping it with AuthProvider to provide authentication context globally.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'./index.css'</span>;

<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;

<span class="hljs-keyword">import</span> { AuthProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./context/AuthContext'</span>;


<span class="hljs-keyword">const</span> root = ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>));

root.render(

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">AuthProvider</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthProvider</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>

);
</code></pre>
<h4 id="heading-clientsrcappjs">client/src/App.js</h4>
<p>Next, we’ll define our main App component. This sets up the client-side routing for the application, and defines public and private routes, and includes a navigation bar and toast notification system.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> { ToastContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'react-toastify/dist/ReactToastify.css'</span>;


<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/Navbar'</span>;

<span class="hljs-keyword">import</span> Register <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Register'</span>;

<span class="hljs-keyword">import</span> Login <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Login'</span>;

<span class="hljs-keyword">import</span> Dashboard <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Dashboard'</span>;

<span class="hljs-keyword">import</span> PrivateRoute <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/PrivateRoute'</span>;


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">ToastContainer</span> /&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/register"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Register</span> /&gt;</span>} /&gt;

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/login"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Login</span> /&gt;</span>} /&gt;

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/dashboard"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">PrivateRoute</span> /&gt;</span>}&gt;

            <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">index</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Dashboard</span> /&gt;</span>} /&gt;

          <span class="hljs-tag">&lt;/<span class="hljs-name">Route</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">h1</span>&gt;</span>Welcome to Task Manager!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>} /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</span>&gt;</span>

      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">Router</span>&gt;</span></span>

  );

}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h4 id="heading-clientsrccontextauthcontextjs">client/src/context/AuthContext.js</h4>
<p>We’ll create an authentication context that manages the global authentication state. It provides functions for user login, registration, and logout, and automatically loads user data on component mount.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext, useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> AuthContext = createContext();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AuthProvider = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);


  useEffect(<span class="hljs-function">() =&gt;</span> {

    <span class="hljs-keyword">const</span> loadUser = <span class="hljs-keyword">async</span> () =&gt; {

      <span class="hljs-keyword">try</span> {

        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'/api/auth/profile'</span>);

        setUser(res.data);

      } <span class="hljs-keyword">catch</span> (err) {

        setUser(<span class="hljs-literal">null</span>);

      } <span class="hljs-keyword">finally</span> {

        setLoading(<span class="hljs-literal">false</span>);

      }

    };

    loadUser();

  }, []);


  <span class="hljs-keyword">const</span> login = <span class="hljs-keyword">async</span> (email, password) =&gt; {

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'/api/auth/login'</span>, { email, password });

      setUser(res.data);

      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;

    } <span class="hljs-keyword">catch</span> (err) {

      <span class="hljs-built_in">console</span>.error(err.response.data.message);

      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    }

  };


  <span class="hljs-keyword">const</span> register = <span class="hljs-keyword">async</span> (username, email, password) =&gt; {

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'/api/auth/register'</span>, { username, email, password });

      setUser(res.data);

      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;

    } <span class="hljs-keyword">catch</span> (err) {

      <span class="hljs-built_in">console</span>.error(err.response.data.message);

      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    }

  };


  <span class="hljs-keyword">const</span> logout = <span class="hljs-keyword">async</span> () =&gt; {

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'/api/auth/logout'</span>);

      setUser(<span class="hljs-literal">null</span>);

    } <span class="hljs-keyword">catch</span> (err) {

      <span class="hljs-built_in">console</span>.error(err);

    }

  };


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AuthContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">user</span>, <span class="hljs-attr">loading</span>, <span class="hljs-attr">login</span>, <span class="hljs-attr">register</span>, <span class="hljs-attr">logout</span> }}&gt;</span>

      {children}

    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContext.Provider</span>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AuthContext;
</code></pre>
<h4 id="heading-clientsrccomponentsnavbarjs">client/src/components/Navbar.js</h4>
<p>Here’s a dynamic navigation bar component that dynamically displays links based on the user's authentication status, showing either login/register options or a welcome message and logout button.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> { user, logout } = useContext(AuthContext);


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Task Manager<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

        {user ? (

          <span class="hljs-tag">&lt;&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>Welcome, {user.username}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{logout}</span>&gt;</span>Logout<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

          <span class="hljs-tag">&lt;/&gt;</span>

        ) : (

          <span class="hljs-tag">&lt;&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/login"</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/register"</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

          <span class="hljs-tag">&lt;/&gt;</span></span>

        )}

      &lt;/div&gt;

    &lt;/nav&gt;

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<h4 id="heading-clientsrccomponentsprivateroutejs">client/src/components/PrivateRoute.js</h4>
<p>To protect certain pages, we can create a Private Route component. This will be a guard for private routes, ensuring that only authenticated users can access them and redirecting unauthenticated users to the login page.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { Navigate, Outlet } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> PrivateRoute = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> { user, loading } = useContext(AuthContext);


  <span class="hljs-keyword">if</span> (loading) {

    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>; <span class="hljs-comment">// Or a spinner</span>

  }


  <span class="hljs-keyword">return</span> user ? <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Outlet</span> /&gt;</span></span> : <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Navigate</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/login"</span> <span class="hljs-attr">replace</span> /&gt;</span></span>;

};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> PrivateRoute;
</code></pre>
<h4 id="heading-clientsrcpagesregisterjs">client/src/pages/Register.js</h4>
<p>Now, let’s create the Register component, which provides a user registration form, handles input state and form submission, and displays success or error messages using toast notifications.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Register = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> [username, setUsername] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> { register } = useContext(AuthContext);

  <span class="hljs-keyword">const</span> navigate = useNavigate();


  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e) =&gt; {

    e.preventDefault();

    <span class="hljs-keyword">const</span> success = <span class="hljs-keyword">await</span> register(username, email, password);

    <span class="hljs-keyword">if</span> (success) {

      toast.success(<span class="hljs-string">'Registration successful!'</span>);

      navigate(<span class="hljs-string">'/dashboard'</span>);

    } <span class="hljs-keyword">else</span> {

      toast.error(<span class="hljs-string">'Registration failed. Please try again.'</span>);

    }

  };


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Username:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{username}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setUsername(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Password:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Register;
</code></pre>
<h4 id="heading-clientsrcpagesloginjs">client/src/pages/Login.js</h4>
<p>Now, for the login form, it works similarly to the register page but logs users into the system instead. This page manages input fields, handles form submissions, and provides feedback via toast notifications.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Login = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> { login } = useContext(AuthContext);

  <span class="hljs-keyword">const</span> navigate = useNavigate();


  <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-keyword">async</span> (e) =&gt; {

    e.preventDefault();

    <span class="hljs-keyword">const</span> success = <span class="hljs-keyword">await</span> login(email, password);

    <span class="hljs-keyword">if</span> (success) {

      toast.success(<span class="hljs-string">'Login successful!'</span>);

      navigate(<span class="hljs-string">'/dashboard'</span>);

    } <span class="hljs-keyword">else</span> {

      toast.error(<span class="hljs-string">'Login failed. Invalid credentials.'</span>);

    }

  };


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Password:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Login;
</code></pre>
<h4 id="heading-clientsrcpagesdashboardjs">client/src/pages/Dashboard.js</h4>
<p>Finally, we’ll build the Dashboard page. This dashboard component displays a user's tasks, allowing them to create new tasks, mark tasks as complete or incomplete, and delete tasks, with real-time updates.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useEffect, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Dashboard = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> { user } = useContext(AuthContext);

  <span class="hljs-keyword">const</span> [tasks, setTasks] = useState([]);

  <span class="hljs-keyword">const</span> [newTaskTitle, setNewTaskTitle] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [newTaskDescription, setNewTaskDescription] = useState(<span class="hljs-string">''</span>);


  useEffect(<span class="hljs-function">() =&gt;</span> {

    <span class="hljs-keyword">if</span> (user) {

      fetchTasks();

    }

  }, [user]);


  <span class="hljs-keyword">const</span> fetchTasks = <span class="hljs-keyword">async</span> () =&gt; {

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'/api/tasks'</span>);

      setTasks(res.data);

    } <span class="hljs-keyword">catch</span> (err) {

      toast.error(<span class="hljs-string">'Failed to fetch tasks.'</span>);

      <span class="hljs-built_in">console</span>.error(err);

    }

  };


  <span class="hljs-keyword">const</span> handleCreateTask = <span class="hljs-keyword">async</span> (e) =&gt; {

    e.preventDefault();

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'/api/tasks'</span>, { <span class="hljs-attr">title</span>: newTaskTitle, <span class="hljs-attr">description</span>: newTaskDescription });

      setNewTaskTitle(<span class="hljs-string">''</span>);

      setNewTaskDescription(<span class="hljs-string">''</span>);

      toast.success(<span class="hljs-string">'Task created successfully!'</span>);

      fetchTasks();

    } <span class="hljs-keyword">catch</span> (err) {

      toast.error(<span class="hljs-string">'Failed to create task.'</span>);

      <span class="hljs-built_in">console</span>.error(err);

    }

  };


  <span class="hljs-keyword">const</span> handleUpdateTask = <span class="hljs-keyword">async</span> (id, completed) =&gt; {

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">await</span> axios.put(<span class="hljs-string">`/api/tasks/<span class="hljs-subst">${id}</span>`</span>, { completed });

      toast.success(<span class="hljs-string">'Task updated successfully!'</span>);

      fetchTasks();

    } <span class="hljs-keyword">catch</span> (err) {

      toast.error(<span class="hljs-string">'Failed to update task.'</span>);

      <span class="hljs-built_in">console</span>.error(err);

    }

  };


  <span class="hljs-keyword">const</span> handleDeleteTask = <span class="hljs-keyword">async</span> (id) =&gt; {

    <span class="hljs-keyword">try</span> {

      <span class="hljs-keyword">await</span> axios.delete(<span class="hljs-string">`/api/tasks/<span class="hljs-subst">${id}</span>`</span>);

      toast.success(<span class="hljs-string">'Task deleted successfully!'</span>);

      fetchTasks();

    } <span class="hljs-keyword">catch</span> (err) {

      toast.error(<span class="hljs-string">'Failed to delete task.'</span>);

      <span class="hljs-built_in">console</span>.error(err);

    }

  };


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Welcome, {user ? user.username : 'Guest'}!<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Your Tasks<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleCreateTask}</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>

          <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>

          <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"New Task Title"</span>

          <span class="hljs-attr">value</span>=<span class="hljs-string">{newTaskTitle}</span>

          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setNewTaskTitle(e.target.value)}

          required

        /&gt;

        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>

          <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>

          <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Description (optional)"</span>

          <span class="hljs-attr">value</span>=<span class="hljs-string">{newTaskDescription}</span>

          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setNewTaskDescription(e.target.value)}

        /&gt;

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Add Task<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>

        {tasks.map((task) =&gt; (

          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{task._id}</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">textDecoration:</span> <span class="hljs-attr">task.completed</span> ? '<span class="hljs-attr">line-through</span>' <span class="hljs-attr">:</span> '<span class="hljs-attr">none</span>' }}&gt;</span>

              {task.title}: {task.description}

            <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> handleUpdateTask(task._id, !task.completed)}&gt;

              {task.completed ? 'Mark Incomplete' : 'Mark Complete'}

            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> handleDeleteTask(task._id)}&gt;Delete<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

        ))}

      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Dashboard;
</code></pre>
<h2 id="heading-deployment-from-localhost-to-live"><strong>Deployment: From Localhost to Live</strong></h2>
<p>Deploying a MERN stack application involves deploying the backend API and the frontend React application separately.</p>
<p>Let’s talk about why we do it separately. As you have seen from above, in a MERN stack app, the frontend and backend are separate by design. React handles the UI, while Express and Node handle server logic and API calls. Because they serve different roles, you'll need to deploy them separately.</p>
<p>The backend runs on a Node.js compatible server, which connects to a database such as MongoDB Atlas. The frontend, once it is built, becomes static files that can be hosted from anywhere, from NGINX to hosting platforms like Netlify or Vercel.</p>
<p>This separation provides you with flexibility and improved scalability. Let’s walk through how to deploy each part.</p>
<h3 id="heading-backend-deployment-nodejsexpressjs"><strong>Backend Deployment (Node.js/Express.js)</strong></h3>
<p>For backend deployment, platforms like Heroku, Render, or AWS EC2 are common choices. Here, I’ll outline a general approach for a cloud VM on AWS EC2</p>
<h4 id="heading-1-prepare-for-production">1. Prepare for Production</h4>
<p>To start, set the environment to <code>production</code> and install only the dependencies your app needs to run, optimizing your application's performance. Skipping devDependencies helps reduce its footprint.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> NODE_ENV=production

npm install --production
</code></pre>
<h4 id="heading-2-process-manager-pm2">2. Process Manager (PM2)</h4>
<p>Next, we’ll set up a process manager to keep our backend server running reliably. PM2 is a popular tool that handles automatic restarts if your Node.js application crashes, manages multiple app instances, and also helps ensure high availability in production environments.</p>
<pre><code class="lang-bash">npm install -g pm2

pm2 start server/app.js --name mern-api

pm2 save

pm2 startup
</code></pre>
<h4 id="heading-3-nginx-as-a-reverse-proxy">3. NGINX as a Reverse Proxy</h4>
<p>Now that our backend is running with PM2, we need a way to handle incoming web traffic. That’s where NGINX comes in. We'll install NGINX to serve as a high-performance reverse proxy directing incoming web traffic to your Node.js backend and serving static frontend files.</p>
<pre><code class="lang-bash">sudo apt update

sudo apt install nginx
</code></pre>
<p>Once NGINX is installed, it’s time to configure it (/etc/nginx/sites-available/default or a new config file). We’ll set it up to forward API requests to the backend and serve the React app, acting as the single entry point. You can update the default configuration file or create a new one:</p>
<pre><code class="lang-nginx"><span class="hljs-comment"># /etc/nginx/sites-available/default</span>
<span class="hljs-section">server</span> {

    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;

    <span class="hljs-attribute">server_name</span> your_domain_or_ip;


    <span class="hljs-attribute">location</span> /api/ {

        <span class="hljs-attribute">proxy_pass</span> http://localhost:5000;

        <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;

        <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>;

        <span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">'upgrade'</span>;

        <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;

        <span class="hljs-attribute">proxy_cache_bypass</span> <span class="hljs-variable">$http_upgrade</span>;

    }


    <span class="hljs-attribute">location</span> / {

        <span class="hljs-attribute">root</span> /var/www/my-mern-app/client/build; <span class="hljs-comment"># Path to your React build folder</span>

        <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> /index.html;

    }

}
</code></pre>
<p>With the NGINX configuration created, we’ll enable it and restart the service to apply the changes, making your application go live:</p>
<pre><code class="lang-bash">sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/

sudo systemctl restart nginx
</code></pre>
<h4 id="heading-4-https-with-certbot-lets-encrypt">4. HTTPS with Certbot (Let's Encrypt)</h4>
<p>To secure your app with HTTPS, we can install Certbot and use it to automatically obtain and configure a free SSL/TLS certificate from Let’s Encrypt, enabling secure HTTPS connections for your domain.</p>
<pre><code class="lang-bash">sudo snap install --classic certbot

sudo certbot --nginx -d your_domain_or_ip
</code></pre>
<h3 id="heading-frontend-deployment-react"><strong>Frontend Deployment (React)</strong></h3>
<p>With the backend deployed, let’s move to the frontend. For the React frontend, we’ll build the application and serve the static files via NGINX (as shown above) or a dedicated static site hosted on platforms like Netlify, Vercel, or AWS S3 + CloudFront.</p>
<h4 id="heading-build-the-react-app">Build the React App</h4>
<p>This command compiles and optimizes your React application into a build folder containing static assets, ready for efficient deployment to any web server or static hosting service.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> client

npm run build
</code></pre>
<h3 id="heading-database-deployment-mongodb-atlas"><strong>Database Deployment (MongoDB Atlas)</strong></h3>
<p>For production, we’ll use a managed MongoDB service like MongoDB Atlas. It handles replication, sharding, and backups, simplifying database management significantly.</p>
<h4 id="heading-create-a-cluster-on-mongodb-atlas">Create a Cluster on MongoDB Atlas</h4>
<ul>
<li><p>Sign up/Log in to MongoDB Atlas.</p>
</li>
<li><p>Create a new cluster (choose a cloud provider and region).</p>
</li>
<li><p>Set up a database user with appropriate permissions.</p>
</li>
<li><p>Configure network access (allow connections from your server's IP address).</p>
</li>
<li><p>Get your connection string and update MONGO_URI in your server/.env file.</p>
</li>
</ul>
<h4 id="heading-1-env-configuration-example">1. <code>.env</code> Configuration Example</h4>
<p>After creating the cluster and user in MongoDB Atlas, you’ll receive a connection string. You need to update your <code>.env</code> file with it</p>
<pre><code class="lang-ini"><span class="hljs-comment"># server/.env</span>
<span class="hljs-attr">MONGO_URI</span>=mongodb+srv://yourUser:yourPassword@cluster0.mongodb.net/yourDBName
<span class="hljs-attr">JWT_SECRET</span>=your_secret_jwt_key
<span class="hljs-attr">NODE_ENV</span>=production
</code></pre>
<h4 id="heading-2-connect-to-mongodb-in-appjs">2. Connect to MongoDB in <code>app.js</code></h4>
<p>Next, in the <code>server/app.js</code> file, make sure you're using the connection string from the environment variable:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);
<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>);
dotenv.config();

mongoose.connect(process.env.MONGO_URI)
  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'MongoDB connected!'</span>))
  .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Connection error:'</span>, err));
</code></pre>
<h3 id="heading-other-deployment-options"><strong>Other Deployment Options</strong></h3>
<p>While this article drives you through manual deployment with EC2 and NGINX, other platforms can simplify the process:</p>
<ul>
<li><p><strong>Render</strong>, <strong>Railway</strong>, and <strong>Heroku</strong> offer easy full-stack deployment with GitHub integration.</p>
</li>
<li><p><strong>Vercel</strong> and <strong>Netlify</strong> are ideal for hosting the React frontend.</p>
</li>
<li><p>You may consider using <strong>Docker</strong> to maintain consistent environments across development and production.</p>
</li>
<li><p>For CI/CD, Linting, Testing, &amp; Deployment can be automated on every push using tools like <strong>GitHub Actions</strong></p>
</li>
</ul>
<p>There is no right or wrong choice here. Select the setup that best suits your project’s scale, team experience, and desired level of control.</p>
<h2 id="heading-security-best-practices-fortifying-your-application"><strong>Security Best Practices: Fortifying Your Application</strong></h2>
<p>Security is paramount. You can implement these best practices to protect your MERN application.</p>
<h3 id="heading-setup-input-validation-and-sanitization"><strong>Setup Input Validation and Sanitization</strong></h3>
<p>Always validate and sanitize input on the server side. You can use libraries like Joi or Zod to make this process easier.</p>
<h4 id="heading-example-with-joi">Example with Joi:</h4>
<p>To validate and sanitize incoming data on the server, we will utilize Joi, a powerful library for defining schemas and enforcing input rules.</p>
<pre><code class="lang-bash">npm install joi
</code></pre>
<p>Now that we’ve installed Joi, we will use it to define strict validation rules for user registration and login inputs. This ensures data quality and prevents common injection attacks.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/validators/authValidator.js</span>

<span class="hljs-keyword">const</span> Joi = <span class="hljs-built_in">require</span>(<span class="hljs-string">'joi'</span>);


<span class="hljs-keyword">const</span> registerSchema = Joi.object({

  <span class="hljs-attr">username</span>: Joi.string().min(<span class="hljs-number">3</span>).max(<span class="hljs-number">30</span>).required(),

  <span class="hljs-attr">email</span>: Joi.string().email().required(),

  <span class="hljs-attr">password</span>: Joi.string().min(<span class="hljs-number">6</span>).required(),

});


<span class="hljs-keyword">const</span> loginSchema = Joi.object({

  <span class="hljs-attr">email</span>: Joi.string().email().required(),

  <span class="hljs-attr">password</span>: Joi.string().required(),

});


<span class="hljs-built_in">module</span>.exports = { registerSchema, loginSchema };
</code></pre>
<p>Next, we’ll integrate these schemas directly into our authentication controller to automatically validate incoming request bodies against predefined schemas.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/controllers/authController.js (snippet)</span>

<span class="hljs-keyword">const</span> { registerSchema, loginSchema } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../validators/authValidator'</span>);


<span class="hljs-built_in">exports</span>.registerUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { error } = registerSchema.validate(req.body);

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: error.details[<span class="hljs-number">0</span>].message });

  <span class="hljs-comment">// ... rest of the registration logic</span>

};


<span class="hljs-built_in">exports</span>.loginUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { error } = loginSchema.validate(req.body);

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: error.details[<span class="hljs-number">0</span>].message });

  <span class="hljs-comment">// ... rest of the login logic</span>

};
</code></pre>
<h3 id="heading-add-authentication-and-authorization"><strong>Add Authentication and Authorization</strong></h3>
<p>You can use JWTs for authentication and implement middleware for protected routes.</p>
<h4 id="heading-jwt-implementation-covered-in-authcontrollerjs-and-authmiddlewarejs-above">JWT Implementation (covered in authController.js and authMiddleware.js above)</h4>
<p>Key aspects:</p>
<ul>
<li><p>HttpOnly Cookies: Store JWTs in HttpOnly cookies to prevent client-side JavaScript access, mitigating XSS attacks.</p>
</li>
<li><p>Secure Flag: Use secure: true in production to ensure cookies are only sent over HTTPS.</p>
</li>
</ul>
<p>These practices ensure that authentication tokens are securely transmitted and stored, protecting against common web vulnerabilities like Cross-Site Scripting (XSS).</p>
<h3 id="heading-implement-rate-limiting"><strong>Implement Rate Limiting</strong></h3>
<p>To protect our API from abuse and malicious intent, we will implement basic rate limiting. This helps protect against brute-force login attempts and DDoS attacks.</p>
<h4 id="heading-installation">Installation</h4>
<p>We will install express-rate-limit package for it</p>
<pre><code class="lang-bash">npm install express-rate-limit
</code></pre>
<h4 id="heading-serverappjs-snippet">server/app.js (snippet)</h4>
<p>Once it is installed, let’s configure the rate limiter and apply it to all incoming requests. This ensures that no single IP can overwhelm your server with repeated calls. The following middleware limits each IP address to 200 requests within a 15-minute window.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> rateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-rate-limit'</span>);

<span class="hljs-keyword">const</span> limiter = rateLimit({

  <span class="hljs-attr">windowMs</span>: <span class="hljs-number">15</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// 15 minutes</span>

  <span class="hljs-attr">max</span>: <span class="hljs-number">200</span>, <span class="hljs-comment">// Limit each IP to 200 requests per windowMs</span>

  <span class="hljs-attr">message</span>: <span class="hljs-string">'Too many requests from this IP, please try again after 15 minutes'</span>,

});

app.use(limiter); <span class="hljs-comment">// Apply to all requests</span>
</code></pre>
<h3 id="heading-setup-cors-configuration-cross-origin-resource-sharing"><strong>Setup CORS Configuration (Cross-Origin Resource Sharing)</strong></h3>
<p>Next, we move our focus to enable secure communication between your frontend and backend. By default, all browsers block cross-origin requests, so we need to configure CORS (Cross-Origin Resource Sharing) to permit the React app to communicate with the Express API.</p>
<h4 id="heading-installation-1">Installation</h4>
<pre><code class="lang-bash">npm install cors
</code></pre>
<h4 id="heading-serverappjs-snippet-1">server/app.js (snippet)</h4>
<p>Once installed<strong>,</strong> we can configure CORS for our Express application, specifying allowed origins and enabling credential sharing for secure cross-origin requests. Remember to replace the origin with your actual production URL when deploying.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cors'</span>);

app.use(cors({

  <span class="hljs-attr">origin</span>: <span class="hljs-string">'http://localhost:3000'</span>, <span class="hljs-comment">// Replace with your frontend URL in production</span>

  <span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>,

}));
</code></pre>
<h3 id="heading-use-environment-variables"><strong>Use Environment Variables</strong></h3>
<p>To keep sensitive information secure and out of your codebase, we will use environment variables. This allows us to efficiently manage secrets, such as database connection strings and JWT keys, without hardcoding them or including them in the source code.</p>
<p>Create a <code>.env</code> file in your <code>server/</code> directory:</p>
<h4 id="heading-env-example">.env (example)</h4>
<p>This .env file stores sensitive configuration details like database connection strings and API keys</p>
<pre><code class="lang-ini"><span class="hljs-attr">MONGO_URI</span>=your_mongodb_connection_string

<span class="hljs-attr">JWT_SECRET</span>=your_super_secret_jwt_key

<span class="hljs-attr">NODE_ENV</span>=production
</code></pre>
<h2 id="heading-monitoring-and-logging-with-winston-and-morgan"><strong>Monitoring and Logging with Winston and Morgan</strong></h2>
<p>Once the application is live, it's critical to monitor the behavior and catch issues promptly. Monitoring and logging help you measure performance, find bugs, and keep a log of all server activity.</p>
<p>We’ll use Morgan for logging HTTP requests and Winston for more general-purpose application logging.</p>
<h3 id="heading-installation-2">Installation</h3>
<p>We will install Morgan for logging HTTP requests and Winston for comprehensive and customizable application logging.</p>
<pre><code class="lang-bash">npm install morgan winston
</code></pre>
<h3 id="heading-serverconfigloggerjs">server/config/logger.js</h3>
<p>Next, let’s configure Winston to handle our application logs. This will output logs to the console by default, with options to enable file-based logging for errors and general information.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">'winston'</span>);

<span class="hljs-keyword">const</span> logger = winston.createLogger({

  <span class="hljs-attr">level</span>: <span class="hljs-string">'info'</span>,

  <span class="hljs-attr">format</span>: winston.format.combine(

    winston.format.timestamp(),

    winston.format.json()

  ),

  <span class="hljs-attr">transports</span>: [

    <span class="hljs-keyword">new</span> winston.transports.Console(),

    <span class="hljs-comment">// new winston.transports.File({ filename: 'error.log', level: 'error' }),</span>

    <span class="hljs-comment">// new winston.transports.File({ filename: 'combined.log', level: 'info' }),</span>

  ],

});

<span class="hljs-built_in">module</span>.exports = logger;
</code></pre>
<h3 id="heading-serverappjs-snippet-2">server/app.js (snippet)</h3>
<p>With Winston and Morgan set up, now let’s integrate them into our <code>app.js</code> file. We’ll use Morgan for request logging during development and replace standard <code>console.log</code> calls with Winston logs for structured and configurable application logging.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> morgan = <span class="hljs-built_in">require</span>(<span class="hljs-string">'morgan'</span>);

<span class="hljs-keyword">const</span> logger = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config/logger'</span>);

<span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'development'</span>) {

  app.use(morgan(<span class="hljs-string">'dev'</span>));

}

<span class="hljs-comment">// Replace console.log with logger.info for database connection</span>

mongoose.connect(process.env.MONGO_URI)

  .then(<span class="hljs-function">() =&gt;</span> logger.info(<span class="hljs-string">'MongoDB connected!'</span>))

  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> logger.error(<span class="hljs-string">'MongoDB connection error:'</span>, err));


<span class="hljs-comment">// Replace console.log in app.listen</span>

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {

  logger.info(<span class="hljs-string">`Server running on port <span class="hljs-subst">${PORT}</span>`</span>);

});
</code></pre>
<h3 id="heading-frontend-error-monitoring-sentry"><strong>Frontend Error Monitoring (Sentry)</strong></h3>
<p>To monitor errors in the frontend, we’ll integrate Sentry. It’s a fantastic tool for tracking exceptions and performance issues in real time. It helps us capture and report client-side errors.</p>
<h4 id="heading-installation-3">Installation</h4>
<pre><code class="lang-bash">npm install @sentry/react @sentry/tracing
</code></pre>
<h4 id="heading-clientsrcindexjs-snippet">client/src/index.js (snippet)</h4>
<p>After installation, let’s initialize Sentry in the React application so that it can automatically capture errors and performance data. We’ll add this to our <code>index.js</code> file.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Sentry <span class="hljs-keyword">from</span> <span class="hljs-string">'@sentry/react'</span>;

<span class="hljs-keyword">import</span> { BrowserTracing } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sentry/tracing'</span>;


Sentry.init({

  <span class="hljs-attr">dsn</span>: <span class="hljs-string">"YOUR_SENTRY_DSN"</span>, <span class="hljs-comment">// Replace with your Sentry DSN</span>

  <span class="hljs-attr">integrations</span>: [<span class="hljs-keyword">new</span> BrowserTracing()],

  <span class="hljs-attr">tracesSampleRate</span>: <span class="hljs-number">1.0</span>,

  <span class="hljs-attr">environment</span>: process.env.NODE_ENV,

});
</code></pre>
<p>And that’s it! Congratulations on building and deploying a full-stack MERN app.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>This article provided a code-first walkthrough of building, securing, and deploying a MERN stack application. By focusing on practical code examples and essential configurations, you now have a solid foundation for your MERN projects.</p>
<p>Remember, continuous learning and adaptation are key in the ever-evolving world of web development. Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Refactor Complex Codebases – A Practical Guide for Devs ]]>
                </title>
                <description>
                    <![CDATA[ Developers often see refactoring as a secondary concern that they can delay indefinitely because it doesn’t immediately contribute to revenue or feature development. And managers frequently view refactoring as "not a business need" until it boils ove... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-refactor-complex-codebases/</link>
                <guid isPermaLink="false">682df5a0f2057ab279952dbe</guid>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ code review ]]>
                    </category>
                
                    <category>
                        <![CDATA[ refactoring ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Wed, 21 May 2025 15:47:44 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1747835131515/f6ea465a-9b14-4918-8943-87ec225b19b3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Developers often see refactoring as a secondary concern that they can delay indefinitely because it doesn’t immediately contribute to revenue or feature development.</p>
<p>And managers frequently view refactoring as "not a business need" until it boils over and becomes the most significant business need possible.</p>
<blockquote>
<p><em>"Oh, our software somehow works. We can't implement any new changes. And oh, everyone is quitting because work is miserable."</em></p>
</blockquote>
<p>In this article, I’ll walk you through the steps I use to refactor a complex codebase. We’ll talk about setting goals, writing tests, breaking up monoliths into smaller modules, verifying changes, making sure existing features still work, and keeping tabs on performance. I’ll also show you how to speed up reviews using AI tools.</p>
<p>By following these steps, you can turn complex, fragile code into a clean, reliable codebase your team can own.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXccvZ3sehF8oGifjnapnY9AUcPde9aKy9t_YEUeL8M2s3dcwxFq_bJLCSp_S02fIvfbwzpZfkz7e-2JQpXpzcdqELqs80EjkLLRpz0Uat6q9_RcRM5VQbjLoUxA2GlaqyeolsKGeA?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="code-refactoring" width="600" height="400" loading="lazy"></p>
<h2 id="heading-the-issue-of-technical-debt">The Issue of Technical Debt</h2>
<p>As projects grow and evolve, <a target="_blank" href="https://en.wikipedia.org/wiki/Technical_debt">technical debt</a> increases. Code that was once functional and manageable turns into an unmaintainable mess, where even small changes become risky and time-consuming.</p>
<p>Despite the obvious need for cleanup, refactoring rarely gets prioritized because there's always something more urgent, new features, bug fixes, and client demands.</p>
<p>I’ve had conversations with engineers, many of whom are working on enterprise software and are fully aware of their codebase's code smells and inconsistencies. They dislike the situation but feel powerless to change it.</p>
<p>So how do we shift from a culture of writing for pure functionality to a culture that values maintainability, especially for complex codebases?</p>
<p>It’s usually a mistake to completely halt new feature development for a long refactoring period (except perhaps in emergencies). Business needs still exist, and putting everything on hold can create tension and lost opportunities. It’s better to find a balance so you’re still delivering value to users even as you clean under the hood.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeZx-XKCA2DC6kQQe2-4NU07wKEm0_VZ4kqEjbF6u2vy2paRigdNRUGjr-_AoE6ueNjCxNjnB-mI7uroXFhJ0nFfvWzwYq2VUMsdsPhXu4KvGYSZcUN0nFmKg8U8WzgGJQAgKtUaw?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Uncle-bob-take-on-refactoring" width="600" height="400" loading="lazy"></p>
<p>While there is no one-size-fits-all solution, a structured approach can help teams introduce sustainable refactoring practices, even in environments where management is resistant. Let’s explore how this works.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-refactoring">What is Refactoring?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preparing-for-refactoring">Preparing for Refactoring</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-secure-management-buy-in">Secure Management Buy-in</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-ensure-a-safety-net-with-automated-testing">Ensure a Safety Net with Automated Testing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-identify-high-risk-areas">Identify High-Risk Areas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-set-clear-refactoring-goals">Set Clear Refactoring Goals</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-techniques-for-refactoring-complex-codebases">Techniques for Refactoring Complex Codebases</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-identifying-and-isolating-problem-areas">1. Identifying and Isolating Problem Areas</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-incremental-vs-big-bang-refactoring">2. Incremental vs. Big Bang Refactoring</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-breaking-down-monolithic-code">3. Breaking Down Monolithic Code</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-ensuring-backward-compatibility">4. Ensuring Backward Compatibility</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-handling-dependencies-and-tight-coupling">5. Handling Dependencies and Tight Coupling</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-testing-strategiessafely-refactoring-with-confidence">6. Testing Strategies (Safely Refactoring with Confidence)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-7-refactoring-without-breaking-performance">7. Refactoring Without Breaking Performance</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-8-automate-code-reviews-with-ai-tools">8. Automate Code Reviews with AI Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-summary">Summary</a></p>
</li>
</ul>
</li>
</ul>
<h2 id="heading-what-is-refactoring"><strong>What is Refactoring?</strong></h2>
<p>Many people all too often use the word "refactor" when they mean a targeted rewrite.</p>
<p>As Martin Fowler famously said,</p>
<blockquote>
<p><em>“Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations... However, the cumulative effect... is quite significant.”</em>​</p>
</blockquote>
<p>In practice, this means continuously polishing code to reduce complexity and technical debt.</p>
<p>While traditional software development follows a linear approach of designing first and coding second, real-world projects often evolve in ways that lead to structural decay. Refactoring counteracts this by continuously refining the codebase, transforming disorganized or inefficient implementations into well-structured, maintainable solutions.</p>
<p>A targeted rewrite is a focused overhaul of a specific aspect of an application, often affecting multiple parts of the codebase. It carries more risk than refactoring but is still controlled and contained.</p>
<h2 id="heading-preparing-for-refactoring">Preparing for Refactoring</h2>
<p>Even the most skilled refactoring effort can stall without proper preparation. Before you start moving code around, laying a foundation that will keep your work organized and your team on the same page is crucial.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcr3hNpzC9XPUVnG6d7uHuC977aYrG2VVOH-8E4WhzM5Rfz3vzPDUPTwJChrK0l7WUK8BLTzYr5-295_27ARWQvcmjufXOk68Bg8szUjEq3IFVCDO0XfTSRFy1LaxqyjvjVDNddsw?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="martin-fowler-on-refactoring" width="600" height="400" loading="lazy"></p>
<p>Here are some steps you can take to ensure your refactoring efforts are successful.</p>
<h3 id="heading-secure-management-buy-in">Secure Management Buy-in</h3>
<p>As I’ve already discussed, getting time for refactoring can be difficult in feature-driven organizations. Often, management will accept refactoring investment if you can tie it to business outcomes, faster time to market, fewer outages (which translates to happier customers), and the ability to take on new initiatives.</p>
<p>Make those connections explicit. For example, you could say:</p>
<blockquote>
<p><em>“If we refactor our reporting engine now, it will make it feasible to add the analytics module next quarter, which unlocks a new revenue stream.”</em></p>
</blockquote>
<p>Or use data:</p>
<blockquote>
<p><em>“We spent 30% of our last sprint fixing bugs in module Y. After refactoring Y, we expect that to drop significantly, freeing time for new features.”</em></p>
</blockquote>
<p>Business-minded arguments help justify the balance.</p>
<h3 id="heading-ensure-a-safety-net-with-automated-testing">Ensure a Safety Net with Automated Testing</h3>
<p>As you refactor, tests are your safety net. Before modifying a component, write characterization tests around it if they don’t exist.</p>
<pre><code class="lang-python"><span class="hljs-comment"># example: characterization test for a legacy function</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">legacy_calculate_discount</span>(<span class="hljs-params">price, rate</span>):</span>
    <span class="hljs-comment"># ... complex logic you don't fully understand yet ...</span>
    <span class="hljs-keyword">return</span> price * (<span class="hljs-number">1</span> - rate/<span class="hljs-number">100</span>) <span class="hljs-keyword">if</span> rate &lt; <span class="hljs-number">100</span> <span class="hljs-keyword">else</span> <span class="hljs-number">0</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_legacy_calculate_discount</span>():</span>
    <span class="hljs-comment"># capture existing behavior</span>
    <span class="hljs-keyword">assert</span> legacy_calculate_discount(<span class="hljs-number">100</span>, <span class="hljs-number">10</span>) == <span class="hljs-number">90</span>
    <span class="hljs-keyword">assert</span> legacy_calculate_discount(<span class="hljs-number">50</span>, <span class="hljs-number">200</span>) == <span class="hljs-number">0</span>
</code></pre>
<p>These tests capture the current behavior, so you’ll know if you accidentally change it. Unit tests, integration tests, and e2e tests all validate that refactoring hasn’t broken anything.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfWfke-9FxoQIPFwRWVoIWrYN7L40mEmhpdAUkcBm34mwzXJ0R8jXKH8rZ0HjAghAtQ-v6dTUYYvK0T8_QBgyfeab-7R50pnB6BgdDm9L4PkFwvwGlUYTHNo21f37fxMZYt3xeY?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="automated-testing-is-imp-for-refactoring" width="600" height="400" loading="lazy"></p>
<p>It’s often worth investing time in setting up a continuous integration pipeline so that every change triggers automated tests. This gives rapid feedback and confidence that you’re not introducing regressions. Robust testing and CI/CD enable you to move faster and refactor with peace of mind.</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># .github/workflows/ci.yml</span>
name: CI
on: [<span class="hljs-type">push</span>, <span class="hljs-type">pull_request</span>]
jobs:
  test:
    runs<span class="hljs-literal">-on</span>: ubuntu<span class="hljs-literal">-latest</span>
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup<span class="hljs-literal">-python</span>@v4
        with: python<span class="hljs-literal">-version</span>: <span class="hljs-string">'3.10'</span>
      - run: pip install <span class="hljs-literal">-r</span> requirements.txt
      - run: pytest -<span class="hljs-literal">-maxfail</span>=<span class="hljs-number">1</span> -<span class="hljs-literal">-disable</span><span class="hljs-literal">-warnings</span> <span class="hljs-literal">-q</span>
</code></pre>
<h3 id="heading-identify-high-risk-areas">Identify High-Risk Areas</h3>
<p>The first step is to figure out what to refactor. High-risk areas are parts of the code likely to cause bugs or slow development. Common signs include long methods, large classes, duplicate code, and complex conditional logic​.</p>
<p>Such code “smells” often hint at deeper design problems. Tools like static analysis can automatically flag these issues.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfS4aFy2hyRSq3UmgB2gQ8NN_-yUksNXcSavTtpnL8KIiWpGGidCSCstLKANZGOjJLqEF69wp-xjMGH6jrjurSaFtUIMS09vUaDgJ6vGtyabP-4QC5ISmT_cMvaaw6c2KlyVa1CKQ?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="SonarQube-dashboard" width="600" height="400" loading="lazy"></p>
<p>For example, SonarQube will mark code smells (like high complexity or long methods) that increase technical debt​. Using SonarQube or similar tools, you can generate reports on code complexity (for example, cyclomatic complexity metrics​) and find hotspots in the codebase that need more attention.</p>
<h3 id="heading-set-clear-refactoring-goals">Set Clear Refactoring Goals</h3>
<p>Before refactoring code, define the goal.</p>
<p>Goals must be specific and measurable. For example, you might aim to reduce a class’s size or a function’s <a target="_blank" href="https://www.ibm.com/docs/en/raa/6.1.0?topic=metrics-cyclomatic-complexity">cyclomatic complexity</a> by a certain amount or to increase unit test coverage from 60% to 90%.</p>
<p>Each goal is tied to a measurable outcome: shorter methods, fewer if statements or classes with a single responsibility, faster execution for processing orders, higher test coverage, and no unused code. These targets will guide our refactoring plan and let us verify when we’ve succeeded.</p>
<p><strong>Tip:</strong> Write down your refactoring goals and share them with your team. This sets expectations that you’re not adding new features in this effort, just making the code cleaner and more robust. It also helps justify the time spent by showing the benefits (like more straightforward future additions and fewer bugs).</p>
<h2 id="heading-techniques-for-refactoring-complex-codebases">Techniques for Refactoring Complex Codebases</h2>
<h3 id="heading-1-identifying-and-isolating-problem-areas">1. Identifying and Isolating Problem Areas</h3>
<p>It can be overwhelming to decide where to start refactoring a large codebase. Not every part of the code needs refactoring – some areas are delicate or rarely touched.</p>
<p>The most impactful refactoring efforts typically target the “problem areas”: parts of the codebase that are overly complex, error-prone, or act as bottlenecks for development and performance. Identifying these areas is a crucial first step.</p>
<h3 id="heading-techniques-for-finding-hotspots">Techniques for Finding Hotspots</h3>
<h4 id="heading-team-knowledge-amp-developer-frustration">Team knowledge &amp; developer frustration</h4>
<p>Don’t underestimate the value of anecdotal information from the team. Which parts of the code do developers dread working in? Often, the team’s instincts point to areas that are hard to understand or modify (for example, “the accounting module is a black box, we hate touching it”). These could be areas to improve.</p>
<p>In my experience, simply asking, “If you had a magic wand, which part of the code would you rewrite?” yields very insightful answers.</p>
<h4 id="heading-code-complexity-metrics">Code complexity metrics</h4>
<p>Use static analysis tools to measure cyclomatic complexity, code duplication, large functions/classes, and so on. Files or modules with extremely high complexity numbers or thousands of lines are good candidates for scrutiny. But static complexity alone doesn’t tell the whole story – a file might be ugly but rarely touched.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXc07SWwlu4GxU6AwoXQEHyyEcQY-6YMOEPr7b7Quhk5UvLD7qx9XyZla2SzP32eGFoYY_Xy-SYZQ9mOMX7Mxeq1YCnFXQxudsMNbvak9CLZfSOeRIvdll_pLW56sAmvRcPZMk36Rg?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="SonarQube" width="600" height="400" loading="lazy"></p>
<h4 id="heading-change-frequency-churn">Change frequency (Churn)</h4>
<p>Look at version control history to see which files are often changed, especially those associated with bug fixes or incidents.</p>
<h4 id="heading-hotspot-analysis">Hotspot analysis</h4>
<p>A robust approach combines complexity and change frequency to find “hotspots.” For example, a tool or technique plotting modules by their complexity and how often they change can highlight the problematic areas. CodeScene (a code analysis tool) popularized this: <em>hotspots</em> are parts of the code that are highly complex and frequently modified, indicating areas where “paying down debt has a real impact”​.</p>
<p>If a module is a mess and developers are in it every week, improving that module will likely yield outsized benefits (fewer bugs, faster adds).</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdJkGfbDK6UFDN9hqzeyCMBWmajADhAMJwzSouyMNz_63o9SRNfOly9AP_XiY2jqfi02fHSIFkMBCfstkjJfkxVB-NaHCSit0xssTYfztZ2BRQZmqYr_lTc3R750-1-lrJi7eeViQ?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="code-health-dashboard" width="600" height="400" loading="lazy"></p>
<h4 id="heading-performance-bottlenecks-and-crashes">Performance bottlenecks and crashes</h4>
<p>Some parts of the codebase become targets for refactoring because they cause frequent performance problems or outages. For instance, if a specific service or job crashes often or can’t keep up with the load, you might need to refactor it for stability.</p>
<h3 id="heading-how-to-isolate-problem-areas">How to Isolate Problem Areas</h3>
<p>Once you’ve identified a hotspot or problem area, the next challenge is isolating it so you can refactor safely. In a complex system, nothing lives in complete isolation. That problematic module likely interacts with many others.</p>
<p>Here are strategies to isolate and tackle it:</p>
<h4 id="heading-break-dependencies-create-seams">Break dependencies (Create seams)</h4>
<p>Michael Feathers (in <em>Working Effectively with Legacy Code</em>) introduced the concept of “seams” – places where you can cut into a codebase to isolate a part for testing or refactoring. This might mean introducing an interface or abstraction between components so you can work on one side independently.  </p>
<p>For example, suppose PaymentService is tightly coupled to StripeGateway, with direct calls scattered throughout the code.</p>
<pre><code class="lang-python"><span class="hljs-comment"># payment_service.py</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge_customer</span>(<span class="hljs-params">order_id, amount</span>):</span>
    <span class="hljs-comment"># Hardcoded dependency to Stripe</span>
    stripe = StripeGateway()
    stripe.charge(order_id, amount)
</code></pre>
<p>To isolate and refactor the payment logic safely, you can introduce a <code>PaymentProcessor</code> interface and have <code>PaymentService</code> depend on that interface instead. Then, create an adapter like StripeAdapter that implements PaymentProcessor and delegates to the existing Stripe logic.</p>
<p>This way, you can safely refactor or even replace the Stripe integration behind the StripeAdapter without impacting <code>PaymentService</code> or any other module that uses it. As long as the <code>PaymentProcessor</code> interface is honored, the rest of the system remains unaffected.</p>
<pre><code class="lang-python"><span class="hljs-comment"># interfaces.py</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentProcessor</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge</span>(<span class="hljs-params">self, order_id, amount</span>):</span>
        <span class="hljs-keyword">raise</span> NotImplementedError


<span class="hljs-comment"># stripe_adapter.py</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StripeAdapter</span>(<span class="hljs-params">PaymentProcessor</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge</span>(<span class="hljs-params">self, order_id, amount</span>):</span>
        <span class="hljs-comment"># Internally still uses Stripe</span>
        stripe = StripeGateway()
        stripe.charge(order_id, amount)


<span class="hljs-comment"># payment_service.py (Refactored)</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentService</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, processor: PaymentProcessor</span>):</span>
        self.processor = processor

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge_customer</span>(<span class="hljs-params">self, order_id, amount</span>):</span>
        self.processor.charge(order_id, amount)
</code></pre>
<h4 id="heading-branch-by-abstraction">“Branch-by-abstraction”</h4>
<p>This technique is related to the above and is often used in continuous delivery. The idea is to add a layer of abstraction (like an interface or proxy) in front of the old code, have both old and new code implementations behind it, and then gradually shift usage from the old to the new implementation. For a while, you might have a temporary state where both versions exist (perhaps toggled by a config or feature flag).</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcaFoXSHVYTBz_1DOsucPkvwGQwfo9qrvhPYvvjYOXQsLIh2MCTfseB1g9SOfijpdKMwcwmK4lfPWcyhn4vf5gaFwdliKUZUGDOcQVJ0qupRLjvnhFrSm5LZfe8OoqZtZkHkj9IXw?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Branch-by-abstraction" width="600" height="400" loading="lazy"></p>
<p>This is similar to how the strangler fig pattern works at an architectural level. It’s a bit of extra work (since you maintain two paths for a while), but it allows you to migrate functionality and fall back if needed incrementally.</p>
<p>Aim to identify the 20% of the code causing 80% of the problems. Focus your refactoring energy there for maximum impact. When you do, create a plan to isolate that area via abstractions, interfaces, modules, or other means so that you can work on it with minimal risk of side effects. The more you can contain the blast radius of a refactoring, the more confidently you can move forward.</p>
<h3 id="heading-2-incremental-vs-big-bang-refactoring">2. Incremental vs. Big Bang Refactoring</h3>
<p>One of the first strategic decisions is approaching the refactor <strong>incrementally</strong> or going for a <strong>“big bang”</strong> overhaul. In most cases, an incremental approach is preferable, but there are scenarios where more significant coordinated refactoring steps are considered.</p>
<p><strong>Let’s break down what these mean:</strong></p>
<pre><code class="lang-python"><span class="hljs-comment"># before: one large function with multiple responsibilities</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_order</span>(<span class="hljs-params">order</span>):</span>
    validate(order)
    apply_discount(order)
    save_to_db(order)
    send_confirmation(order)
    log_metrics(order)
    update_loyalty_points(order)
    <span class="hljs-comment"># potentially more steps </span>

<span class="hljs-comment"># after: refactored incrementally into clearer, smaller units</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process_order</span>(<span class="hljs-params">order</span>):</span>
    validate(order)
    apply_discount(order)
    persist_and_notify(order)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">persist_and_notify</span>(<span class="hljs-params">order</span>):</span>
    save_to_db(order)
    send_confirmation(order)
    log_metrics(order)
    update_loyalty_points(order)
</code></pre>
<h4 id="heading-incremental-refactoring">Incremental refactoring</h4>
<p>This means making small, manageable changes over time rather than attempting a massive overhaul in one shot. The system should remain functional at each step (even internally in transition). The advantage is risk mitigation: each small change is less likely to go wrong, and it’s easier to pinpoint and fix if it does.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdaSmnIWRE9FKNmmABBzc6Tk6KFwsj29FQ2YwyQ_kWqryheb0yUdpec51lQHg5XahoxKgCm4vv9twD849H3Yo5dn0678tuGih9Z-HfBBCfhBngs4YhpH6x2pjzqnAeDVYGohXHvDQ?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Incremental refactoring" width="600" height="400" loading="lazy"></p>
<p>Incremental delivery lets you confirm changes in production and makes diagnosing issues easier since you’re only changing one small thing at a time​. It also means the system keeps running during the refactor, so there’s less pressure to rush to “get the system back to working condition”​. If priorities shift, you can pause after some increments and still have a working product.</p>
<h4 id="heading-big-bang-refactoring-rewrite">Big bang refactoring (Rewrite)</h4>
<p>This is the “tear it down and rebuild” approach. You stop adding new features, possibly freeze the code for a period, and devote a considerable effort to redesigning or rewriting a significant portion (or the entirety) of the system. The idea is to emerge on the other side with a <em>brand new, clean</em> system.</p>
<p>So when (if ever) is a big bang justified? Perhaps when the existing system is truly untenable – for example, an outdated technology that <strong>must</strong> be replaced (such as a platform that can’t meet new performance or security requirements or code written in a language no longer supported). Even then, wise teams often simulate a big bang by breaking it into stages or developing the new system in parallel.</p>
<p>Whenever possible, favor an incremental refactoring strategy. Teams successfully pull off massive transformations by treating the big refactor as a series of mini-refactors under a shared vision.</p>
<h3 id="heading-3-breaking-down-monolithic-code">3. Breaking Down Monolithic Code</h3>
<p>Many complex codebases start life as a single monolithic application, one deployable, a single code project, or a tightly coupled set of modules all maintained and released together.</p>
<p>Over time, monoliths can become unwieldy, builds take forever, a change in one area can unintentionally affect another, and teams can be complex to scale because everyone is stepping on each other’s toes in the same code. A common refactoring challenge for senior engineers is modularising or splitting a monolith into more manageable pieces.</p>
<pre><code class="lang-python"><span class="hljs-comment"># define the interface</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PaymentProcessor</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge</span>(<span class="hljs-params">self, amount</span>):</span> ...

<span class="hljs-comment"># old implementation</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LegacyProcessor</span>(<span class="hljs-params">PaymentProcessor</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge</span>(<span class="hljs-params">self, amount</span>):</span>
        <span class="hljs-comment"># original code</span>

<span class="hljs-comment"># new implementation behind a feature flag</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">NewProcessor</span>(<span class="hljs-params">PaymentProcessor</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">charge</span>(<span class="hljs-params">self, amount</span>):</span>
        <span class="hljs-comment"># cleaner code</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_processor</span>():</span>
    <span class="hljs-keyword">if</span> config.feature_new_payment:
        <span class="hljs-keyword">return</span> NewProcessor()
    <span class="hljs-keyword">return</span> LegacyProcessor()

<span class="hljs-comment"># usage remains the same</span>
processor = get_processor()
processor.charge(<span class="hljs-number">100</span>)
</code></pre>
<h4 id="heading-strategies-for-modularization">Strategies for modularization.</h4>
<ul>
<li><p><strong>Layer separation:</strong> Start by enforcing logical layer boundaries. For example, separate the user interface code from business logic and separate business logic from data access. In a messy monolith, these concerns often get mixed together. By organizing the code into layers (even within the same repository), you can limit the ripple effect of changes.</p>
</li>
<li><p><strong>Domain-based modularization:</strong> If your system spans multiple business domains or functional areas, consider splitting along those lines. For example, an e-commerce monolith might be separated into modules like Accounts, Orders, Products, Shipping, and so on.<br>  Each could become a subsystem or a package. The goal is to minimize the information these modules need to know about each other’s internals (high cohesion within modules and clear APIs between them).</p>
</li>
<li><p><strong>Microservices or services extraction:</strong> In recent years, the trend has been to break monoliths into microservices, independent services that communicate over APIs. This form of architectural refactoring can significantly improve independent deployability and scalability. But it’s a significant undertaking with complexities (distributed systems, network calls, and so on). If you decide to go this route, do it gradually.<br>  A proven method is the <strong>strangler fig pattern</strong> mentioned earlier: you pick one piece of functionality and rewrite or extract it as a separate service, redirect traffic or calls to the new service. At the same time, the rest of the monolith remains intact and iteratively does this for other pieces​.</p>
</li>
<li><p><strong>Modular monolith:</strong> Not every system needs to go full microservices. There’s an approach called a modular monolith, essentially structuring your single application into well-defined modules that communicate via explicit interfaces (almost like internal microservices but without the overhead of separate deployments).</p>
</li>
</ul>
<p>This can give you many microservices' advantages (clear boundaries, separate development responsibility) while avoiding operational complexity.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfLiNAEDyOsR4G_q1oQS3jpSenci3XDJRm10Gy3picTpaO9uHwme2H3YkbJF-Jrvqq3Q-QMxGjJJwy04mqUf1a7D8IRsCDER5pHBT6GTMPRkao5EXXIFGtj4Iki15mOHmRKRLTiWw?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="microservices' advantages" width="600" height="400" loading="lazy"></p>
<ul>
<li><strong>Identify shared utilities vs. truly independent components:</strong> In breaking down a monolith, some code is widely shared (like utility functions or cross-cutting concerns such as authentication). It might make sense to factor those into libraries or services <em>first</em>, as they will be needed by whatever other pieces you split out.</li>
</ul>
<p>While breaking down a monolith, maintaining functionality during the transition is essential. Techniques like backward compatibility (discussed next) and thorough testing will be your safety net.</p>
<p>Finally, be prepared for the team workflow to change. If you move to microservices, teams might take ownership of different services, requiring more DevOps and communication across teams. If you keep a modular monolith, enforce code ownership or review rules to keep the modules from tangling up again (for example, you might restrict direct database access from one module to another’s tables, and so on).</p>
<h3 id="heading-4-ensuring-backward-compatibility">4. Ensuring Backward Compatibility</h3>
<p>A critical concern during large refactoring is: <em>Will our changes break existing contracts</em>?</p>
<p>In other words, can other systems, modules, or clients that rely on our code work as expected after we refactor? Backward compatibility is especially important if your codebase provides public APIs (to external customers or other teams), data persisted in a certain format, configuration files that users have written, etc.</p>
<p>Here are some strategies and considerations to maintain backward compatibility:</p>
<p>Suppose you have a widely-used function like <code>send_email(to, subject, body)</code>. You want to refactor the internal logic to support additional features like HTML formatting, but you don’t want to break existing callers.</p>
<p>Instead of changing the function signature, you keep the public API unchanged and delegate to a new internal function:</p>
<pre><code class="lang-python"><span class="hljs-comment"># original API</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_email</span>(<span class="hljs-params">to, subject, body</span>):</span>
    <span class="hljs-comment"># send mail...</span>

<span class="hljs-comment"># refactored internals, keep signature</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_email</span>(<span class="hljs-params">to, subject, body</span>):</span>
    sendv2(to=to, subject=subject, body=body)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">sendv2</span>(<span class="hljs-params">to, subject, body, html=True</span>):</span>
    <span class="hljs-comment"># new implementation with HTML support</span>
</code></pre>
<p>The internal <code>send_email_v2()</code> function adds new capabilities like HTML formatting, but older code using <code>send_email()</code> still works without any modifications.</p>
<p>If you're introducing a new, improved version like <code>send_email_v2(to, subject, body, html=True)</code>, it's good practice to:</p>
<ul>
<li><p>Mark the old version (send_email) as deprecated in documentation.</p>
</li>
<li><p>Ensure the old version internally calls the new one.</p>
</li>
<li><p>Give other teams time to migrate at their own pace.</p>
</li>
</ul>
<h4 id="heading-use-versioning-for-external-apis">Use versioning for external APIs</h4>
<p>If your system provides an HTTP API or similar to external clients, the safest route for major changes is to version the API. Introduce a v2 API endpoint for the refactored logic, keep v1 running (maybe internally calling v2 or using a translation layer). Clients can move to v2 at their own pace.</p>
<p>It’s extra work to maintain two APIs temporarily, but it prevents a breaking change from angering users or causing outages. Always communicate changes clearly and provide migration guides if applicable.</p>
<h4 id="heading-have-a-clear-deprecation-policy">Have a clear deprecation policy</h4>
<p>Make sure there’s a policy (and communication) around how long deprecated features will be supported. For internal APIs, maybe it’s one release cycle. For external ones, maybe multiple cycles or never removal without a major version bump. A good practice is to announce deprecation early.</p>
<p>If you’re exposing an HTTP API, consider introducing a new versioned endpoint (for example, <strong>/api/v2/send_email</strong>) and maintain the older <strong>/api/v1/send_email temporarily</strong>. Internally, v1 might call v2 with default parameters, ensuring behavior stays consistent for existing clients.</p>
<p>In summary, maintain backward compatibility whenever possible, and implement a clear deprecation policy for anything you do change​.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe3xM4som_GQrtHXI3NNR0G-4KJ-1D2YO-JbNdT75IxZ5_upcBRDnOVp7krEESiqwwtXg18pDypLq3VxDr44Hof76cs8HajOZy2w0FZ50kWmPk6Y7EwNByNLNrqAokmhmmL5sP3AA?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Clear deprecation policy" width="600" height="400" loading="lazy"></p>
<h4 id="heading-write-adapter-or-compatibility-layers">Write adapter or compatibility layers</h4>
<p>In some cases, you can write an adapter to bridge old and new systems. For instance, suppose you refactor the underlying data model of your application, but you still have old configuration files in the old format. Rather than forcing all those files to be rewritten immediately, you could write a small adapter that translates the old format to the new one at runtime (or during startup). This way, old data continues to work. </p>
<h4 id="heading-test-for-compatibility">Test for compatibility</h4>
<p>Include tests that specifically ensure backward compatibility. For instance, if you have a public API, keep a suite of tests using the old API contracts and run them against the refactored code, they should still pass. </p>
<p>In summary, ensure that as you refactor, the external behavior and contracts remain consistent. This careful approach protects your users and downstream systems, allowing you to reap the internal benefits of refactoring without causing external chaos.</p>
<h3 id="heading-5-handling-dependencies-and-tight-coupling">5. Handling dependencies and tight coupling</h3>
<p>One of the hairiest aspects of refactoring a large codebase is dealing with deeply interdependent code. Complex systems often suffer from tight coupling. Module A assumes details about Module B and vice versa, global variables or singletons are used all over, or a change in one place ripples through half the codebase.</p>
<p>Reducing coupling is a significant aim of refactoring because it makes the code more modular, meaning each piece can be understood, tested, and changed independently. So, how do we gradually loosen the coupling in a legacy system?</p>
<p>Let’s go over some strategies to reduce coupling.</p>
<h4 id="heading-introduce-interfaces-or-abstraction-layers">Introduce interfaces or abstraction layers</h4>
<p>A very effective way to decouple is to put an interface between components. For example, if you have a class that directly queries a database, introduce an interface and have the class use that instead. The underlying database code implements the interface.</p>
<pre><code class="lang-python"><span class="hljs-comment"># before: direct instantiation</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderService</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        self.repo = OrderRepository()

<span class="hljs-comment"># after: inject dependency</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderService</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, repo</span>):</span>
        self.repo = repo

<span class="hljs-comment"># wiring up in application startup</span>
repo = OrderRepository(db_conn)
service = OrderService(repo)
</code></pre>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfuMNvzC4x3X0EOgoRXzflfOv4C-Dxzc2Tm16KA0NdZcOH0nK300LUwcNzXCL6iqu0rhknHiVhnQN4csDCYUupQLc4Kt6Q4c7d1Pi47NfrXKoF9rhXCUMAhtozsDpFMVT2lo2OX5Q?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Introduce interfaces or abstraction layers" width="600" height="400" loading="lazy"></p>
<p>Now, that class no longer depends on how the data is fetched. Applying the dependency inversion principle depends on abstractions, not concretions.</p>
<h4 id="heading-use-dependency-injection">Use dependency injection</h4>
<p>Once you have interfaces, use dependency injection to supply concrete implementations. Many frameworks support DI containers, or you can do it manually (passing in dependencies via constructors). Dependency injection means code A doesn’t instantiate code B itself – instead, B is passed into A.  </p>
<p>This approach also makes unit testing easier (you can inject mock dependencies).</p>
<h4 id="heading-facades-or-wrapper-services">Facades or wrapper services</h4>
<p>If a particular subsystem is heavily entangled with others, consider creating a Facade, an object that provides a simplified interface to a larger body of code. Other parts of the system are then called the Facade, not the many internal methods of the subsystem. Internally, the subsystem can be refactored (even split into smaller pieces) as long as the Facade’s outward interface remains consistent.</p>
<p>This is similar to how microservices work (other services don’t care how one service is implemented internally – they just call its API), but you can do it in-process, too.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe_X2G_VNTR-I2EIp86SgPD3Zlks70Q4iG3BsqIs94PMgh-_qNfRk7ogT4mqONP7qXzg8PpN92k342-2nH6ertfy32Ga6SFH3PdSLwxP4US9PPjMi6Rqc9hy-gHbSKVzvTvYmTzOQ?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Facades or wrapper services" width="600" height="400" loading="lazy"></p>
<h4 id="heading-gradual-replacement-parallel-run">Gradual replacement (Parallel Run)</h4>
<p>If a specific component is to be replaced with a new implementation, it can help to run them in parallel for a while. For instance, if you have a spaghetti module that you want to redo correctly, you could leave the spaghetti code in place for legacy calls but start routing new calls to the new module.</p>
<p>The result is a codebase where changes in one area (hopefully) won’t unpredictably break another, a key property of a maintainable system.</p>
<h3 id="heading-6-testing-strategies-safely-refactoring-with-confidence">6. Testing Strategies (Safely Refactoring with Confidence)</h3>
<p>A robust testing strategy will give you the confidence to make sweeping changes because you’ll know quickly if something important breaks. Here’s how to approach testing in the context of a large refactoring:</p>
<h4 id="heading-establish-a-baseline-with-regression-tests">Establish a baseline with regression tests</h4>
<p>Before you even begin refactoring a particular component, make sure you have tests that cover its current behavior. You're lucky if the codebase already has a good test suite, but many legacy systems have inadequate tests.</p>
<p>One of the first tasks in those cases is often writing <strong>characterization tests</strong>. A characterization test is a test that documents what the system <em>currently does</em>, not what we think it should do​.</p>
<p>As Feathers says, “a characterization test is a test that characterizes the actual behavior of a piece of code.” This allows you to take a snapshot of what it does and ensure that it doesn’t change​.</p>
<p>This gives you a safety net so you can refactor with confidence that you’re not introducing regressions​. Use automated test suites to help things run smoothly (unit, integration, end-to-end).</p>
<h4 id="heading-continuous-integration-ci">Continuous integration (CI)</h4>
<p>It is highly recommended that testing be integrated into a CI pipeline that runs on every commit or merge. This way, you catch a bug during refactoring as soon as you introduce it, tightening the feedback loop.</p>
<h4 id="heading-canary-releases-and-feature-flags">Canary releases and feature flags</h4>
<p>Beyond pre-release testing, consider strategies for safely deploying refactored code. A canary release involves rolling out the change to a small subset of users or servers first, observing it, and then gradually expanding​.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfAif0ftiqEhiRPDygrmhtzSsfrctq6ZPfJnMg04GwKmxKk-NFiP9GjEGE9rfz7U_WKhRcBYSBYlirjKwzr-PvfZz2FJpEWS6U0UqNh-WayiVM5BGIyz3sabSX-zdKKA0j_ojvhIA?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="Canary releases and feature flags" width="600" height="400" loading="lazy"></p>
<p>This is great for catching issues that tests might miss (for example, performance issues or edge cases in production data). If the canary looks good (no errors, metrics are healthy), you proceed to full rollout. If not, you rollback quickly—with only a small impact scope.</p>
<h4 id="heading-performance-and-load-testing">Performance and load testing</h4>
<p>If performance is a concern, incorporate performance tests into your strategy. This can be done in a staging environment. You might reconsider your approach or optimize the new code if you see a significant regression.</p>
<h4 id="heading-testing-legacy-code-lacking-tests">Testing legacy code lacking tests</h4>
<p>If you’re dealing with a part of the system with zero tests (not uncommon in older code), prioritize getting at least some coverage there. There are also techniques like <strong>approval testing</strong> (where you generate output and have a human approve it as correct, then use that as a baseline for future tests). The key is not to refactor entirely in the dark; give yourself at least a flashlight in the form of tests!</p>
<p>In sum, a strong testing strategy is non-negotiable for refactoring complex systems. It’s your safety net, early warning system, and guide to know that your “cleanup” hasn’t broken anything vital.</p>
<h3 id="heading-7-refactoring-without-breaking-performance">7. Refactoring Without Breaking Performance</h3>
<p>A common concern when refactoring is whether these cleaner code changes will make my system slower or more resource-hungry. Ideally, refactoring is about the internal structure and shouldn’t change external behavior, and performance is part of the behavior.</p>
<p>In theory, performance should remain the same if you don’t change algorithms or data structures in a way that affects complexity.</p>
<p>In practice, though, performance can be inadvertently affected by refactoring. The new code may be more readable but uses more memory, or perhaps a critical caching mechanism was removed in the spirit of simplicity.</p>
<p><strong>Senior engineers need to be mindful of performance-sensitive parts of the system when refactoring and take steps to avoid regressions (or even improve performance where possible).</strong></p>
<p>Here’s how to refactor with performance in mind:</p>
<h4 id="heading-identify-performance-critical-code-paths">Identify performance-critical code paths</h4>
<p>Not all codes are equal regarding performance impact. If you refactor them, treat it almost like a functional change: you must re-measure performance afterwards. You have more leeway for parts of the code that run rarely or are not bottlenecks.</p>
<h4 id="heading-use-profiling-before-and-after">Use profiling before and after</h4>
<p>A profiler is a tool that measures where time is spent in your code or how memory is allocated. It’s beneficial to run a profiler on the code before refactoring a module to see how it behaves, and then run it after to compare. If you see, for example, that after refactoring, a function now shows up as taking 30% of execution time (when it was negligible before), that’s a red flag. Maybe the new code calls it more times than before.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> cProfile, pstats
<span class="hljs-keyword">from</span> mymodule <span class="hljs-keyword">import</span> slow_function

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">profile</span>(<span class="hljs-params">fn</span>):</span>
    profiler = cProfile.Profile()
    profiler.enable()
    fn()
    profiler.disable()
    stats = pstats.Stats(profiler).strip_dirs().sort_stats(<span class="hljs-string">'cumtime'</span>)
    stats.print_stats(<span class="hljs-number">10</span>)

<span class="hljs-comment"># run before refactor</span>
profile(<span class="hljs-keyword">lambda</span>: slow_function())

<span class="hljs-comment"># after you refactor slow_function(), re-run and compare stats</span>
</code></pre>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXd1xNcjypguN9JbN7JtBhAtBkfDrtCV6IwOORRUVT5rOAha_I2GQx3vgKRAjlxpeeUIGLTETRR6J3EnS2y95DY6ypiH95DQJT0vRfcyxv2KIz99hPXa0O8JjTzxpi5eSsk3spN6EQ?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="profiler-dashboard" width="600" height="400" loading="lazy"></p>
<h4 id="heading-when-possible-improve-performance-through-refactoring">When possible, improve performance through refactoring</h4>
<p>On the flip side, refactoring can help performance.</p>
<p>For example, by refactoring duplicated code into one place, you can use better caching in that one place. So, watch for performance improvement opportunities that arise naturally as you refactor.</p>
<p>Performance should be treated as part of the “external behavior” that needs to be preserved in a good mindset. Refactoring should ideally not make things slower for users. To ensure that, incorporate performance checks into your plan, especially for critical sections. Measure, don’t guess. The end goal is a codebase that is both clean <strong>and</strong> fast enough.</p>
<h3 id="heading-8-automate-code-reviews-with-ai-tools">8. Automate Code Reviews with AI tools</h3>
<p>Refactoring code is an ongoing process, not a one-time event – AI code review tools help enforce clean-code standards, catch smells early, and reduce the repetitive tasks that can bog down human reviewers. This frees your engineers to focus on deeper architectural or domain-specific issues.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfWs-ZM80TK_JcjwyPEnywdJl6Tf4G6gYFa1cN_J2ugTlniaGr4a397JuUj721m7kUw0EKMnzYHykpHJdG_aW7w3_B2J91bLL1UoaabdNsmH1uckMJHcFVpAhqZM2r855AsVYwDJg?key=nBTgfzmVkL2-N7DBMJ6e6gyk" alt="CodeRabbit-ai-code-reviewer-tool" width="600" height="400" loading="lazy"></p>
<p>One powerful option is <a target="_blank" href="https://www.coderabbit.ai/">CodeRabbit</a>, an AI-driven review platform designed to cut review time and bugs in half.</p>
<p>Here’s how it works and why it can boost your refactoring workflow:</p>
<h4 id="heading-ai-powered-contextual-feedback">AI-powered contextual feedback</h4>
<p>CodeRabbit analyzes pull requests line by line, applying both advanced language models and static analysis under the hood. It flags potential bugs, best-practice deviations, and style issues before a human opens the PR.</p>
<p>Some other features include:</p>
<ul>
<li><p><strong>Auto-generated summaries and 1-click fixes</strong> – Summarize large PRs and apply straightforward fixes instantly.</p>
</li>
<li><p><strong>Real-time collaboration and AI chat</strong> – Chat with the AI for clarifications, alternate code snippets, and instant feedback.</p>
</li>
<li><p><strong>Integrates with popular dev platforms</strong> – Supports GitHub, GitLab, and Azure DevOps for seamless PR scanning.</p>
</li>
</ul>
<p>CodeRabbit even has a free AI code reviews in VS Code and with this <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=CodeRabbit.coderabbit-vscode">VS Code extension</a>, you can get the most advanced AI code reviews directly in your code editor, saving review time, catching more bugs, and helping you in refactoring.</p>
<h2 id="heading-summary">Summary</h2>
<p>Refactoring a complex enterprise codebase is like renovating a large building while people still live in it without collapsing the structure.</p>
<p>Refactoring should be an ongoing process. You prevent the codebase from decaying by incorporating these practices into your regular development (perhaps allocating some time each sprint for refactoring or doing it opportunistically when touching your code). Each minor refactoring should not be too complex, and the cumulative effect is significant.</p>
<p>As <a target="_blank" href="https://martinfowler.com/">Martin Fowler</a> puts it, a series of small changes can lead to a significant improvement in design.</p>
<p>That's it for this blog. I hope you learned something new today.</p>
<p>If you want to read more interesting articles about developer tools, React, Next.js, AI and more, then I'll encourage you to checkout my <a target="_blank" href="https://www.devtoolsacademy.com/">blog</a>.</p>
<p>Some of the new and interesting articles I've written in the last 24 months.</p>
<ul>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/cursor-vs-windsurf/">Cursor vs Windsurf</a></p>
</li>
<li><p><a target="_blank" href="https://clerk.com/blog/nextjs-role-based-access-control">How to Implement Role-Based Access Control in Next.js</a></p>
</li>
<li><p><a target="_blank" href="https://www.devtoolsacademy.com/blog/ai-code-reviewers-vs-human-code-reviewers/">AI Code Reviewers vs Human Code Reviewers</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-i-built-a-custom-video-conferencing-app-with-stream-and-nextjs/">How to Build a Custom Video Conferencing App with Stream and Next.js</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/how-to-perform-code-reviews-in-tech-the-painless-way/">How to Perform Code Reviews in Tech – The Painless Way</a></p>
</li>
</ul>
<p>You can get in touch if you have any questions or corrections. I’m expecting them.</p>
<p>And if you found this blog useful, please share it with your friends and colleagues who might benefit from it as well. Your support enables me to continue producing useful content for the tech community.</p>
<p>Now it’s time to take the next step by subscribing to my <a target="_blank" href="https://bytesizedbets.com/"><strong>newsletter</strong></a> and following me on <a target="_blank" href="https://twitter.com/theankurtyagi"><strong>Twitter</strong></a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
