<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Andre Faria</title>
    <description>The latest articles on Forem by Andre Faria (@andremmfaria).</description>
    <link>https://forem.com/andremmfaria</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F501623%2F0c30a57d-7f5b-4490-8e33-bfbf9bad3252.jpeg</url>
      <title>Forem: Andre Faria</title>
      <link>https://forem.com/andremmfaria</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/andremmfaria"/>
    <language>en</language>
    <item>
      <title>Building a Python Display Framework for Raspberry Pi OLED Screens</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Sun, 12 Apr 2026 23:51:02 +0000</pubDate>
      <link>https://forem.com/andremmfaria/building-a-python-display-framework-for-raspberry-pi-oled-screens-1log</link>
      <guid>https://forem.com/andremmfaria/building-a-python-display-framework-for-raspberry-pi-oled-screens-1log</guid>
      <description>&lt;h2&gt;
  
  
  1. The Original Inspiration
&lt;/h2&gt;

&lt;p&gt;This project started with an article by &lt;strong&gt;Michael Klements&lt;/strong&gt; on The DIY Life: &lt;a href="https://the-diy-life.com/add-an-oled-stats-display-to-raspberry-pi-os-bookworm/" rel="noopener noreferrer"&gt;Add an OLED Stats Display to Raspberry Pi OS Bookworm&lt;/a&gt;. The article walks through connecting a small SSD1306 OLED display to a Raspberry Pi and writing a Python script that shows live system statistics (CPU usage, memory, disk, temperature, and IP address).&lt;/p&gt;

&lt;p&gt;The original script, available at &lt;a href="https://github.com/mklements/OLED_Stats" rel="noopener noreferrer"&gt;github.com/mklements/OLED_Stats&lt;/a&gt;, is a clear and working piece of code. It does exactly what it says on the tin. For a single-purpose stats screen, it is perfectly fine.&lt;/p&gt;

&lt;p&gt;But the more I looked at the script, the more I noticed a pattern I have seen in many embedded display projects: the same boilerplate repeated everywhere. Every example in the repo wires up &lt;code&gt;busio.I2C&lt;/code&gt;, initializes &lt;code&gt;adafruit_ssd1306.SSD1306_I2C&lt;/code&gt;, creates a &lt;code&gt;PIL.Image&lt;/code&gt;, sets up &lt;code&gt;ImageDraw&lt;/code&gt;, and then tears it all down at the end. If you want to show something different on the screen like a clock, a network status, an animation you will need to write almost the same scaffolding again from scratch.&lt;/p&gt;

&lt;p&gt;That made me want to build something better.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What I Built Instead
&lt;/h2&gt;

&lt;p&gt;The result is &lt;code&gt;rpi-display-core&lt;/code&gt;: a small Python framework for SSD1306 and SH1106 OLED displays on the Raspberry Pi. The goal was to eliminate all the display boilerplate and replace it with a clean, composable API.&lt;/p&gt;

&lt;p&gt;Instead of wiring up I2C every time you want to show something, you write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.displays.ssd1306&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SSD1306Display&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.widgets.clock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ClockWidget&lt;/span&gt;

&lt;span class="nc"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SSD1306Display&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;ClockWidget&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. Four lines. A running clock on the OLED display.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FBuilding%2520a%2520Python%2520Display%2520Framework%2520for%2520Raspberry%2520Pi%2520OLED%2520Screens%2FPXL_20260412_232715216.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FBuilding%2520a%2520Python%2520Display%2520Framework%2520for%2520Raspberry%2520Pi%2520OLED%2520Screens%2FPXL_20260412_232715216.jpg" alt="Terminal showing the script and code side by side in a vim split" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The framework provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specialized display classes for SSD1306 and SH1106 backends&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;canvas&lt;/code&gt; context manager that gives you a &lt;code&gt;PIL.ImageDraw&lt;/code&gt; surface and automatically flushes it to the display on exit&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Widget&lt;/code&gt; base class with a consistent &lt;code&gt;render(draw, x, y)&lt;/code&gt; interface&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Runner&lt;/code&gt; class that drives a render loop at a fixed FPS&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;MockDisplay&lt;/code&gt; for testing without hardware&lt;/li&gt;
&lt;li&gt;Multiple built-in widgets covering the most common display use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framework is available on PyPI, has a full pytest suite, and includes examples such as a &lt;code&gt;systemd&lt;/code&gt; service so you can run your display as a persistent background service.&lt;/p&gt;

&lt;p&gt;The repository is at: &lt;a href="https://github.com/andremmfaria/rpi-display-core" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/rpi-display-core&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Hardware You'll Need
&lt;/h2&gt;

&lt;p&gt;The hardware side of this project is minimal. You need a Raspberry Pi, a small OLED display, and four jumper wires.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://amzn.to/3OrRngj" rel="noopener noreferrer"&gt;Raspberry Pi&lt;/a&gt;&lt;/strong&gt; — any Pi with I2C support will work. (The Pi 5 is the current recommended board, but i used an Rpi 4b for this)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://amzn.to/40WQ9RS" rel="noopener noreferrer"&gt;Raspberry Pi Power Supply&lt;/a&gt;&lt;/strong&gt; — the official USB-C power supply for the Pi&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://amzn.to/40Q1Dq8" rel="noopener noreferrer"&gt;32GB MicroSD Card&lt;/a&gt;&lt;/strong&gt; — any class-10 card works; 32GB is more than enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://amzn.to/4i3Nvjd" rel="noopener noreferrer"&gt;I2C OLED Display 128×64&lt;/a&gt;&lt;/strong&gt; — the 0.96-inch SSD1306 module or 1.3-inch SH1106 module, four pins (GND, VCC, SCL, SDA)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://amzn.to/4fr4Dh7" rel="noopener noreferrer"&gt;4-Wire Female-to-Female Jumper Cables&lt;/a&gt;&lt;/strong&gt; — for connecting the display to the GPIO header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The framework supports both SSD1306 and SH1106 I2C displays. SPI variants are out of scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Wiring It Up
&lt;/h2&gt;

&lt;p&gt;The OLED module connects directly to the Raspberry Pi GPIO header using four wires. No breadboard required.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OLED Pin&lt;/th&gt;
&lt;th&gt;Pi Header Pin&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;Pin 9&lt;/td&gt;
&lt;td&gt;Ground&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VCC&lt;/td&gt;
&lt;td&gt;Pin 1&lt;/td&gt;
&lt;td&gt;3.3V power&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCL&lt;/td&gt;
&lt;td&gt;Pin 5&lt;/td&gt;
&lt;td&gt;I2C clock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDA&lt;/td&gt;
&lt;td&gt;Pin 3&lt;/td&gt;
&lt;td&gt;I2C data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FBuilding%2520a%2520Python%2520Display%2520Framework%2520for%2520Raspberry%2520Pi%2520OLED%2520Screens%2Fssd1306_wiring.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FBuilding%2520a%2520Python%2520Display%2520Framework%2520for%2520Raspberry%2520Pi%2520OLED%2520Screens%2Fssd1306_wiring.png" alt="SSD1306 OLED display wired to a Raspberry Pi GPIO header" width="710" height="642"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before running anything, I2C must be enabled on the Pi. Use &lt;code&gt;raspi-config&lt;/code&gt; → Interface Options → I2C, or add &lt;code&gt;dtparam=i2c_arm=on&lt;/code&gt; to &lt;code&gt;/boot/firmware/config.txt&lt;/code&gt;. After enabling I2C, verify the display is detected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;i2cdetect &lt;span class="nt"&gt;-y&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;3c&lt;/code&gt; appear in the output grid, which is the default I2C address for SSD1306 and SH1106 displays.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Installing the Framework
&lt;/h2&gt;

&lt;p&gt;The framework is available on PyPI and can be installed using &lt;code&gt;uv&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add rpi-display-core
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the project on PyPI at: &lt;a href="https://pypi.org/project/rpi-display-core" rel="noopener noreferrer"&gt;https://pypi.org/project/rpi-display-core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;adafruit-blinka&lt;/code&gt;, &lt;code&gt;adafruit-circuitpython-ssd1306&lt;/code&gt;, and &lt;code&gt;adafruit-circuitpython-sh1106&lt;/code&gt; packages provide the I2C and display drivers. &lt;code&gt;pillow&lt;/code&gt; handles image composition. The &lt;code&gt;rpi-display-core&lt;/code&gt; package itself manages these dependencies for you.&lt;/p&gt;

&lt;p&gt;To verify the installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"from rpi_display.displays.ssd1306 import SSD1306Display; from rpi_display import canvas, Widget, Runner; print('ok')"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Core Concepts
&lt;/h2&gt;

&lt;p&gt;The framework has four building blocks: &lt;code&gt;Display&lt;/code&gt; backends, &lt;code&gt;canvas&lt;/code&gt;, &lt;code&gt;Widget&lt;/code&gt;, and &lt;code&gt;Runner&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Display Backends
&lt;/h3&gt;

&lt;p&gt;The framework provides specialized classes for different display controllers. Hardware imports are deferred inside &lt;code&gt;__init__&lt;/code&gt;, so the module can be imported on any machine without crashing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.displays.ssd1306&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SSD1306Display&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.displays.sh1106&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SH1106Display&lt;/span&gt;

&lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SSD1306Display&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="c1"&gt;# default: address=0x3C, 128×64
# OR
# display = SH1106Display()
&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;              &lt;span class="c1"&gt;# fill with black and flush
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  canvas
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;canvas&lt;/code&gt; is a context manager that creates a fresh &lt;code&gt;PIL.Image&lt;/code&gt; and &lt;code&gt;ImageDraw&lt;/code&gt;, yields the draw surface, and then flushes the image to the display on exit, even if an exception is raised inside the block.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.displays.ssd1306&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SSD1306Display&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;canvas&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;

&lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SSD1306Display&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_default&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern comes directly from &lt;a href="https://luma-oled.readthedocs.io/" rel="noopener noreferrer"&gt;luma.oled&lt;/a&gt;, which uses the same &lt;code&gt;with canvas(device) as draw&lt;/code&gt; idiom.&lt;/p&gt;

&lt;h3&gt;
  
  
  Widget
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Widget&lt;/code&gt; is the base class for all display components. Every widget implements a single method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;draw&lt;/code&gt; argument is a &lt;code&gt;PIL.ImageDraw.ImageDraw&lt;/code&gt;. The &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; offsets let you position widgets anywhere on the 128×64 canvas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runner
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Runner&lt;/code&gt; drives the render loop. It accepts either a &lt;code&gt;Widget&lt;/code&gt; instance or a plain callable, and calls it at a fixed FPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.displays.ssd1306&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SSD1306Display&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.widgets.system&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SystemStatsWidget&lt;/span&gt;

&lt;span class="nc"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SSD1306Display&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SystemStatsWidget&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop runs until interrupted. A &lt;code&gt;try/finally&lt;/code&gt; block ensures &lt;code&gt;display.clear()&lt;/code&gt; is always called on exit, leaving the screen blank rather than frozen on the last frame.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Built-in Widgets
&lt;/h2&gt;

&lt;p&gt;The framework ships several widgets out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Text&lt;/code&gt; renders a single line of text at a given position and font size. &lt;code&gt;MultiLineText&lt;/code&gt; renders a list of strings as stacked lines with configurable spacing. &lt;code&gt;ScrollingText&lt;/code&gt; scrolls a string horizontally across the screen, advancing by a configurable number of pixels per render call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.widgets.text&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MultiLineText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ScrollingText&lt;/span&gt;

&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;MultiLineText&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Line 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Line 2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Line 3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;ScrollingText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a long message that scrolls...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three widgets load DejaVu Sans from &lt;code&gt;/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf&lt;/code&gt; when available, and fall back to the PIL bitmap font otherwise.&lt;/p&gt;

&lt;h3&gt;
  
  
  ProgressBar
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ProgressBar&lt;/code&gt; draws a filled rectangle representing a value between 0.0 and 1.0. Values outside that range are clamped at construction time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.widgets.shapes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProgressBar&lt;/span&gt;

&lt;span class="nc"&gt;ProgressBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ClockWidget
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ClockWidget&lt;/code&gt; shows the current time in large type, a horizontal divider, the date in smaller type below, and an optional seconds progress bar at the bottom of the screen. It reuses &lt;code&gt;ProgressBar&lt;/code&gt; internally for the seconds indicator.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FBuilding%2520a%2520Python%2520Display%2520Framework%2520for%2520Raspberry%2520Pi%2520OLED%2520Screens%2FPXL_20260412_232611652.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FBuilding%2520a%2520Python%2520Display%2520Framework%2520for%2520Raspberry%2520Pi%2520OLED%2520Screens%2FPXL_20260412_232611652.jpg" alt="ClockWidget running live on an SSD1306 OLED display connected to a Raspberry Pi" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  SystemStatsWidget
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;SystemStatsWidget&lt;/code&gt; is a composite widget that stacks five individual sub-widgets in a column:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IpWidget&lt;/code&gt; — local IP address via &lt;code&gt;hostname -I&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CpuWidget&lt;/code&gt; — CPU usage via &lt;code&gt;/proc/stat&lt;/code&gt; two-snapshot method&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RamWidget&lt;/code&gt; — memory usage via &lt;code&gt;free -m&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DiskWidget&lt;/code&gt; — disk usage via &lt;code&gt;df -h&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TempWidget&lt;/code&gt; — CPU temperature via &lt;code&gt;/sys/class/thermal/thermal_zone0/temp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each sub-widget fetches its data fresh on every &lt;code&gt;render()&lt;/code&gt; call and returns &lt;code&gt;"N/A"&lt;/code&gt; if the data source is unavailable, rather than raising an exception.&lt;/p&gt;

&lt;p&gt;The CPU widget deliberately avoids &lt;code&gt;top -bn1&lt;/code&gt; because it is slow and creates its own CPU load. Reading &lt;code&gt;/proc/stat&lt;/code&gt; twice with a 0.1-second gap gives an accurate idle-time delta at a fraction of the cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  NetworkWidget
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;NetworkWidget&lt;/code&gt; shows the hostname, local IP, and internet reachability (a 1-second ping to 8.8.8.8). All three lookups are wrapped in exception handlers and return graceful fallback values on failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spinner
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Spinner&lt;/code&gt; cycles through &lt;code&gt;|&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;\&lt;/code&gt; characters, advancing one frame per &lt;code&gt;render()&lt;/code&gt; call. The caller controls speed by adjusting the Runner's FPS.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. A Complete Example
&lt;/h2&gt;

&lt;p&gt;Here is a full script using &lt;code&gt;SystemStatsWidget&lt;/code&gt; with &lt;code&gt;Runner&lt;/code&gt;. This is also what the systemd service example uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.displays.ssd1306&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SSD1306Display&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.widgets.system&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SystemStatsWidget&lt;/span&gt;

&lt;span class="nc"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SSD1306Display&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;SystemStatsWidget&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the script runs, it updates the display once per second with the current IP, CPU, RAM, disk, and temperature. Press &lt;code&gt;Ctrl+C&lt;/code&gt; to stop. The display clears cleanly on exit.&lt;/p&gt;

&lt;p&gt;For testing without a physical display, swap in &lt;code&gt;MockDisplay&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MockDisplay&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rpi_display.widgets.system&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SystemStatsWidget&lt;/span&gt;

&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MockDisplay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nc"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SystemStatsWidget&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;fps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# d.last_image holds the most recent PIL Image after each render
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Running as a systemd Service
&lt;/h2&gt;

&lt;p&gt;For a persistent display that survives reboots, create a systemd unit file that runs &lt;code&gt;examples/09_systemd_service.py&lt;/code&gt;. That script runs &lt;code&gt;SystemStatsWidget&lt;/code&gt; at 1 FPS and is designed to be the entry point for a service.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;/etc/systemd/system/rpi-display.service&lt;/code&gt; with the following contents, replacing &lt;code&gt;YOUR_USERNAME&lt;/code&gt; and the path to match your installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;rpi-display-core stats display&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;YOUR_USERNAME&lt;/span&gt;
&lt;span class="py"&gt;WorkingDirectory&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/YOUR_USERNAME/rpi-display-core&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;uv run python examples/09_systemd_service.py&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then enable and start it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;rpi-display
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start rpi-display
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Restart=on-failure&lt;/code&gt; means a clean exit (e.g. &lt;code&gt;Ctrl+C&lt;/code&gt; in a terminal) will not trigger a restart. Only unexpected crashes will.&lt;/p&gt;

&lt;p&gt;To check logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; rpi-display &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the service restarts repeatedly, the most common cause is the display not being detected. Run &lt;code&gt;i2cdetect -y 1&lt;/code&gt; to confirm &lt;code&gt;3c&lt;/code&gt; appears.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Development Workflow
&lt;/h2&gt;

&lt;p&gt;If you want to contribute to the project, I use &lt;code&gt;uv&lt;/code&gt; for development. The following commands are used for linting, formatting, and testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run ruff check &lt;span class="nb"&gt;.&lt;/span&gt;
uv run ruff format &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
uv run black &lt;span class="nt"&gt;--check&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
uv run isort &lt;span class="nt"&gt;--check-only&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
uv run mypy
uv run pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. Future Improvements
&lt;/h2&gt;

&lt;p&gt;The framework covers the common cases for I2C OLED displays, but there are a number of directions it could grow.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Support for additional display controllers&lt;/strong&gt;: Potential future display backends include SPI displays and e-ink panels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Additional widgets&lt;/strong&gt;: I am considering adding &lt;code&gt;BitmapWidget&lt;/code&gt; for rendering 1-bit PNG or BMP files and a &lt;code&gt;QRCodeWidget&lt;/code&gt; for generating codes on the fly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced scrolling&lt;/strong&gt;: The &lt;code&gt;ScrollingText&lt;/code&gt; widget currently wraps at the end of the text. Supporting bidirectional bounce scrolling is a planned improvement.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Starting from Michael Klements' original stats display script, this project built a composable Python framework that replaces display boilerplate with clean abstractions. The specialized display classes, &lt;code&gt;canvas&lt;/code&gt;, &lt;code&gt;Widget&lt;/code&gt;, and &lt;code&gt;Runner&lt;/code&gt; primitives cover the full rendering lifecycle, and the built-in widgets handle the most common display use cases.&lt;/p&gt;

&lt;p&gt;The framework is available on PyPI at &lt;a href="https://pypi.org/project/rpi-display-core" rel="noopener noreferrer"&gt;https://pypi.org/project/rpi-display-core&lt;/a&gt;, is fully tested, and is designed for production use on the Raspberry Pi.&lt;/p&gt;

&lt;p&gt;The repository is available at: &lt;a href="https://github.com/andremmfaria/rpi-display-core" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/rpi-display-core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Original article: &lt;a href="https://the-diy-life.com/add-an-oled-stats-display-to-raspberry-pi-os-bookworm/" rel="noopener noreferrer"&gt;Add an OLED Stats Display to Raspberry Pi OS Bookworm&lt;/a&gt; by Michael Klements&lt;/li&gt;
&lt;li&gt;Original repo: &lt;a href="https://github.com/mklements/OLED_Stats" rel="noopener noreferrer"&gt;github.com/mklements/OLED_Stats&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>raspberrypi</category>
      <category>iot</category>
    </item>
    <item>
      <title>I just wanted a desk clock I accidentally built a Home Assistant dashboard</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Sun, 22 Mar 2026 03:46:30 +0000</pubDate>
      <link>https://forem.com/andremmfaria/i-just-wanted-a-desk-clock-i-accidentally-built-a-home-assistant-dashboard-o96</link>
      <guid>https://forem.com/andremmfaria/i-just-wanted-a-desk-clock-i-accidentally-built-a-home-assistant-dashboard-o96</guid>
      <description>&lt;h2&gt;
  
  
  1. The Unexpected Device
&lt;/h2&gt;

&lt;p&gt;I wasn’t trying to build anything.&lt;/p&gt;

&lt;p&gt;I just wanted a desk clock. Something small, clean, and with Wi-Fi so it would always have the correct time. No tinkering, no integrations, no dashboards. Just something I could plug in, place on my desk, and forget about.&lt;/p&gt;

&lt;p&gt;What I ended up buying was the &lt;a href="https://geekmagic.com/products/geekmagic-ultra-4" rel="noopener noreferrer"&gt;GeekMagic Ultra&lt;/a&gt; on Amazon. The ad marketed it as a generic “smart weather clock,” which sounded close enough to what I needed. The design is nice, the screen is sharp, and on paper it looks like a slightly more capable version of a normal digital clock.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FI%2520just%2520wanted%2520a%2520desk%2520clock%2520I%2520accidentally%2520built%2520a%2520Home%2520Assistant%2520dashboard%2Fgeekmagic-Black.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FI%2520just%2520wanted%2520a%2520desk%2520clock%2520I%2520accidentally%2520built%2520a%2520Home%2520Assistant%2520dashboard%2Fgeekmagic-Black.jpeg" alt="Image" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Out of the box, that’s exactly what it feels like. You connect to it using its own Wi-Fi network, then it provides you with a web interface so you can configure it to connect to your Wi-Fi. It shows time, weather, and a few widgets, and generally behaves like a polished consumer device. But after a few minutes of using it, something feels off.&lt;/p&gt;

&lt;p&gt;The customization is limited. You can change what’s displayed, but not how it works. It’s flexible in appearance, but rigid in behavior. That’s usually a sign that the hardware underneath is either heavily locked down or far more capable than the software allows. In this case, it was the latter.&lt;/p&gt;

&lt;p&gt;Once you dig a bit deeper, you realize this isn’t really a “smart clock” at all. It’s an ESP8266 with a 240×240 display attached to it. That’s it. No magic, no proprietary silicon. Just a very familiar microcontroller in a nicely packaged form factor.&lt;/p&gt;

&lt;p&gt;That realization changes the entire perspective. Because if it’s an ESP8266:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it can be reflashed&lt;/li&gt;
&lt;li&gt;it can run ESPHome&lt;/li&gt;
&lt;li&gt;it can integrate directly with Home Assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, it stops being a product and starts being a platform.&lt;/p&gt;

&lt;p&gt;What I thought was a simple desk accessory turned out to be a small, hackable display node that fits perfectly into a home automation setup. Not by design, but by accident.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Peeling It Open: Hardware Reality
&lt;/h2&gt;

&lt;p&gt;Once you accept that the device is hackable, the next step is understanding what you’re actually working with. And in this case, that means ignoring the marketing entirely and looking at the hardware.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FI%2520just%2520wanted%2520a%2520desk%2520clock%2520I%2520accidentally%2520built%2520a%2520Home%2520Assistant%2520dashboard%2FPXL_20260321_152434138.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FI%2520just%2520wanted%2520a%2520desk%2520clock%2520I%2520accidentally%2520built%2520a%2520Home%2520Assistant%2520dashboard%2FPXL_20260321_152434138.jpg" alt="Image" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this device's case, the chip is soldered on the board with the other components and cannot be removed easily. Otherwise, the device is very simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an ESP8266&lt;/li&gt;
&lt;li&gt;a 240×240 ST7789 TFT display&lt;/li&gt;
&lt;li&gt;SPI wiring between them&lt;/li&gt;
&lt;li&gt;a PWM-controlled backlight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthesolaruniverse.files.wordpress.com%2F2019%2F12%2F056_fig_01_96.jpg%3Fcrop%3D1%26h%3D504%26w%3D722" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthesolaruniverse.files.wordpress.com%2F2019%2F12%2F056_fig_01_96.jpg%3Fcrop%3D1%26h%3D504%26w%3D722" alt="Image" width="722" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s no extra compute layer, no buffering chip, no hidden abstraction. Everything you draw goes straight through the ESP8266 to the display. That simplicity is both the reason this works and the reason it can fail so easily.&lt;/p&gt;

&lt;p&gt;The ESP8266 is a capable chip, but it is also extremely constrained. You are working with a small amount of usable RAM, no PSRAM, and a heap that can become unstable if pushed too far. On the other side, the display is not trivial. A 240×240 screen sounds small, but it still requires a meaningful amount of memory to render properly.&lt;/p&gt;

&lt;p&gt;That creates a constant tension:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the display wants memory, and the ESP8266 does not have much of it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why so many initial attempts fail. The natural instinct is to treat it like a modern embedded system, allocate buffers, use large fonts, redraw frequently. On this device, that approach leads straight to crashes, boot loops, or a screen that just flickers black.&lt;/p&gt;

&lt;p&gt;The wiring itself also comes with a few quirks. Through community reverse engineering, the common mapping looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GPIO14&lt;/code&gt; → SPI clock&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GPIO13&lt;/code&gt; → SPI MOSI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GPIO0&lt;/code&gt; / &lt;code&gt;GPIO2&lt;/code&gt; → display control (DC / RESET)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GPIO5&lt;/code&gt; → backlight (PWM)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mapping is also referenced by the GeekMagic owner in &lt;a href="https://github.com/GeekMagicClock/smalltv/issues/4" rel="noopener noreferrer"&gt;issue #4 of the smalltv repository&lt;/a&gt;, where they shared the same pin definitions for the device (&lt;code&gt;TFT_DC=0&lt;/code&gt;, &lt;code&gt;TFT_RST=2&lt;/code&gt;, &lt;code&gt;SCK=14&lt;/code&gt;, &lt;code&gt;MOSI=13&lt;/code&gt;, &lt;code&gt;TFT_BL=5&lt;/code&gt;, &lt;code&gt;TFT_CS=-1&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;One detail that catches people off guard is the lack of a proper chip select line. Because of that, the display only behaves correctly when the SPI bus is configured in a specific mode (&lt;code&gt;mode3&lt;/code&gt;). This is not documented anywhere official, it’s something the community figured out by trial and error.&lt;/p&gt;

&lt;p&gt;And that pattern repeats across the entire device.&lt;/p&gt;

&lt;p&gt;Nothing here is particularly complex, but almost nothing is documented either. Every working configuration is the result of small discoveries layered on top of each other.&lt;/p&gt;

&lt;p&gt;The important takeaway is that this is not a forgiving platform. You don’t have the headroom to brute-force your way through problems. Every decision, buffer size, font size, update interval, has a direct impact on stability.&lt;/p&gt;

&lt;p&gt;Once you understand those constraints, the device becomes predictable and surprisingly capable. Until then, it just looks like it’s broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Real Work: Community Reverse Engineering
&lt;/h2&gt;

&lt;p&gt;If you try to approach this device using only official documentation, you won’t get very far.&lt;/p&gt;

&lt;p&gt;There is no proper datasheet for the product as a whole (although there is a &lt;a href="https://github.com/GeekMagicClock/smalltv-ultra" rel="noopener noreferrer"&gt;GH repo&lt;/a&gt; with some manuals). There is no “supported ESPHome configuration.” There isn’t even a clear description of how the display is wired internally. What exists instead is a long trail of people experimenting, breaking things, and slowly converging on what works.&lt;/p&gt;

&lt;p&gt;The starting point for me was a &lt;a href="https://www.youtube.com/watch?v=S1Q9PZ95SDM" rel="noopener noreferrer"&gt;YouTube video from Maker HQ&lt;/a&gt;, which provides a basic working configuration. This video was really useful because it gave me a &lt;a href="https://www.dropbox.com/scl/fi/9t175rsb23n8anikfplcg/ultratv.yaml?rlkey=au79zg7flndf2dz2g2uq598v4&amp;amp;e=1&amp;amp;dl=0" rel="noopener noreferrer"&gt;working config file as a starting point&lt;/a&gt;. Without it, the proper way to set the display parameters becomes a guessing game. It gets the screen to light up and things to render, but it doesn’t explain why certain settings matter or what happens when you deviate from them.&lt;/p&gt;

&lt;p&gt;The real work happened in the &lt;a href="https://community.home-assistant.io/t/installing-esphome-on-geekmagic-smart-weather-clock-smalltv-pro/618029" rel="noopener noreferrer"&gt;forum thread on the Home Assistant Community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An important detail for context: the hardware shown in post #8 of that thread is exactly the same as my unit, which places mine in the clone/counterfeit variant discussed there rather than the official SmallTV Ultra hardware.&lt;/p&gt;

&lt;p&gt;That thread is long, messy, and full of partial solutions, but it’s also where most of the important details were uncovered. Not in a single place, but spread across dozens of posts. You don’t read it linearly, you piece it together.&lt;/p&gt;

&lt;p&gt;A few of the key findings that came out of that effort:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The display works reliably only with &lt;code&gt;spi_mode: mode3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The newer &lt;code&gt;mipi_spi&lt;/code&gt; driver behaves better than older alternatives&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;color_depth: 8&lt;/code&gt; is effectively mandatory on ESP8266&lt;/li&gt;
&lt;li&gt;Full buffering is not viable, partial buffers must be used&lt;/li&gt;
&lt;li&gt;Small mistakes in configuration lead to hard crashes, not soft failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are obvious if you just look at ESPHome documentation. They only become clear when you see multiple people hitting the same issues and gradually narrowing down the causes. Another important detail is that there isn’t a single “correct” configuration. There are working configurations, but they depend on trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stability vs visual quality&lt;/li&gt;
&lt;li&gt;buffer size vs responsiveness&lt;/li&gt;
&lt;li&gt;font size vs memory usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why copying a YAML file blindly often doesn’t work. Small differences, even something like a slightly larger font, can push the device over the edge.&lt;/p&gt;

&lt;p&gt;This is one of those cases where the community didn’t just provide examples. It effectively reverse engineered the behavior of the device through collective experimentation. Without that, this would have been a dead end.&lt;/p&gt;

&lt;p&gt;Huge thanks to &lt;a href="https://www.youtube.com/@Maker_HQ" rel="noopener noreferrer"&gt;MakerHQ&lt;/a&gt; for publishing the video walkthrough, and to everyone in the &lt;a href="https://community.home-assistant.io/t/installing-esphome-on-geekmagic-smart-weather-clock-smalltv-pro/618029" rel="noopener noreferrer"&gt;Home Assistant forum thread&lt;/a&gt; who shared tests, pin mappings, and working configs. That collective effort is what made this project practical.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Step by Step: Connect, Flash, and Configure
&lt;/h2&gt;

&lt;p&gt;If you have the same hardware revision I got, the process is easier than many guides suggest.&lt;/p&gt;

&lt;p&gt;I did not need to solder anything at all. Flashing worked by simply plugging the device into my computer over USB and using the ESPHome web flasher.&lt;/p&gt;

&lt;p&gt;Here is the exact flow that worked for me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connect the device to your computer with a USB cable.&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://web.esphome.io/" rel="noopener noreferrer"&gt;https://web.esphome.io/&lt;/a&gt; in a Chromium-based browser (Chrome, Edge, Brave, etc.).&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Connect&lt;/strong&gt;, then select the serial device that appears for the clock.&lt;/li&gt;
&lt;li&gt;Install ESPHome onto the device from the web installer.&lt;/li&gt;
&lt;li&gt;Wait for the first boot to complete, then join the temporary Wi-Fi AP created by the device if prompted.&lt;/li&gt;
&lt;li&gt;Join the ap through your phone or something, enter the webpage on the device and configure the WiFi connection to your network.&lt;/li&gt;
&lt;li&gt;Provide your Wi-Fi credentials so the device can join your network.&lt;/li&gt;
&lt;li&gt;Add it to Home Assistant and upload your YAML configuration.&lt;/li&gt;
&lt;li&gt;Reboot once after the first successful upload and confirm that the display renders correctly.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One important browser caveat: Firefox did not work for me because this flow depends on Web Serial support, which is available in Chromium-based browsers.&lt;/p&gt;

&lt;p&gt;If you prefer to follow a visual walkthrough, there is also a step-by-step in the &lt;a href="https://www.youtube.com/watch?v=S1Q9PZ95SDM" rel="noopener noreferrer"&gt;MakerQH video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After this initial flash, updates are much easier because you can usually do OTA uploads from ESPHome without reconnecting USB.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Making It Work: ESPHome + Home Assistant Integration
&lt;/h2&gt;

&lt;p&gt;Once the display is stable, the problem shifts from “how do I make this work” to “what do I actually want it to show.”&lt;/p&gt;

&lt;p&gt;In my case, the answer was straightforward: I wanted a simple network status panel that still functioned as a desk clock.&lt;/p&gt;

&lt;p&gt;The architecture ended up being very simple, given that I already had the UniFi integration in Home Assistant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UniFi Dream Machine → Home Assistant → ESPHome → Display
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key decision here was to let Home Assistant do all the heavy lifting.&lt;/p&gt;

&lt;p&gt;Instead of pushing data via MQTT or building custom logic on the ESP8266, I used the &lt;code&gt;homeassistant:&lt;/code&gt; platform in ESPHome to pull values directly. That means the device is not calculating anything complex, it’s just rendering whatever Home Assistant already knows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FI%2520just%2520wanted%2520a%2520desk%2520clock%2520I%2520accidentally%2520built%2520a%2520Home%2520Assistant%2520dashboard%2FPXL_20260322_025913818.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FI%2520just%2520wanted%2520a%2520desk%2520clock%2520I%2520accidentally%2520built%2520a%2520Home%2520Assistant%2520dashboard%2FPXL_20260322_025913818.jpg" alt="Image" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The data flowing into the display includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WAN status (up/down)&lt;/li&gt;
&lt;li&gt;External IP address&lt;/li&gt;
&lt;li&gt;Total data received and sent&lt;/li&gt;
&lt;li&gt;Current download and upload speeds&lt;/li&gt;
&lt;li&gt;Uptime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of these come from existing Home Assistant entities. The ESP simply reads them and turns them into text on the screen. That approach keeps the system simple and, more importantly, stable.&lt;/p&gt;

&lt;p&gt;Take a look at the result on this gist: &lt;a href="https://gist.github.com/andremmfaria/7d060df2771cc90815e220d1a5440b85" rel="noopener noreferrer"&gt;https://gist.github.com/andremmfaria/7d060df2771cc90815e220d1a5440b85&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are still a few transformations that need to happen locally, but they are lightweight:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uptime arrives as raw seconds → converted into days/hours/minutes&lt;/li&gt;
&lt;li&gt;Byte counters → converted into KB/MB/GB for readability&lt;/li&gt;
&lt;li&gt;Speed values → relabeled to match expected units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing here is computationally heavy. It’s mostly formatting. This is important, because the ESP8266 doesn’t have much headroom. The more logic you move out of it, the more reliable the system becomes.&lt;/p&gt;

&lt;p&gt;Rendering is done using a display lambda, updated every 15 seconds. That interval is deliberate. Faster updates are possible, but they start to introduce timing warnings and unnecessary load. Slower updates keep things smooth and predictable.&lt;/p&gt;

&lt;p&gt;Another small but important choice was avoiding unnecessary state. The device does not cache values, track deltas, or maintain history. It simply redraws the current state each cycle. That makes it effectively stateless:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if Home Assistant updates, the display reflects it&lt;/li&gt;
&lt;li&gt;if the ESP reboots, it just reconnects and resumes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No synchronization problems, no drift, no edge cases. In the end, the ESP8266 isn’t acting like a smart device. It’s acting like a very small, very focused display terminal for Home Assistant. And that’s exactly what makes it work.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. The UI: Constraints Drive Design
&lt;/h2&gt;

&lt;p&gt;Once everything is wired and talking properly, the next question is simple: what should this actually look like?&lt;/p&gt;

&lt;p&gt;That’s where the constraints start shaping everything.&lt;/p&gt;

&lt;p&gt;A 240×240 screen sounds like enough space, but it fills up quickly. Add to that the ESP8266 limitations, limited RAM, slow redraws, and occasional watchdog warnings, and you’re not designing freely anymore. You’re designing within a tight box.&lt;/p&gt;

&lt;p&gt;Early on, it becomes clear that you can’t treat this like a modern UI. There’s no room for heavy layouts, large assets, or frequent updates. Even small changes, like increasing font sizes or adding extra text, can have a noticeable impact on performance.&lt;/p&gt;

&lt;p&gt;So the layout has to be intentional.&lt;/p&gt;

&lt;p&gt;The final structure ended up being simple and functional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ TIME            DATE ]
[ WAN STATUS      IP   ]
-----------------------
[ Down / Up            ]
[ RX / TX              ]
-----------------------
[ Uptime               ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The time is the primary element, so it gets the largest font and the most visual weight. The date sits opposite it, using the same horizontal space to balance the layout without competing for attention.&lt;/p&gt;

&lt;p&gt;Below that, the WAN status and IP address are split across the screen. This was a deliberate choice. Keeping them on the same line but on opposite sides avoids clutter while still grouping related information together.&lt;/p&gt;

&lt;p&gt;The middle section is purely data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;download and upload speeds&lt;/li&gt;
&lt;li&gt;total received and transmitted data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are aligned in a predictable way, so your eyes don’t need to search. Labels on the left, values on the right. No surprises.&lt;/p&gt;

&lt;p&gt;At the bottom, uptime sits on its own, separated by a line. It’s useful, but not something you need to glance at constantly, so it gets the least visual emphasis.&lt;/p&gt;

&lt;p&gt;The biggest trade-offs showed up in small details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large fonts improve readability, but reduce available space&lt;/li&gt;
&lt;li&gt;Right-aligned text looks better, but is slightly more expensive to render&lt;/li&gt;
&lt;li&gt;Frequent updates feel “live,” but increase CPU load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even color choices matter. Bright colors for data, white for labels, muted tones for separators. Not for aesthetics alone, but to keep the information readable at a glance.&lt;/p&gt;

&lt;p&gt;There’s also no use of images or complex graphics. Everything is drawn using basic primitives, text, lines, and simple shapes. Not because it looks better, but because it’s cheaper to render and more stable over time.&lt;/p&gt;

&lt;p&gt;The end result isn’t flashy, but it doesn’t need to be. It’s fast enough, stable enough, and clear enough to do its job.&lt;/p&gt;

&lt;p&gt;And on a device like this, that’s the real definition of a good UI.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. What This Became (and Why It’s Better Than a Clock)
&lt;/h2&gt;

&lt;p&gt;At some point, this stopped being about fixing a device and started becoming something else entirely.&lt;/p&gt;

&lt;p&gt;I set out to get a clock. What I ended up with is a small, always-on display that reflects the state of my network in real time.&lt;/p&gt;

&lt;p&gt;The difference is subtle, but important.&lt;/p&gt;

&lt;p&gt;A clock is passive. It shows time, maybe the weather, and that’s it. This device, once integrated with Home Assistant, becomes part of the system. It reacts to changes, reflects status, and gives you information you didn’t realize you wanted in that form.&lt;/p&gt;

&lt;p&gt;Right now, it shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;time and date&lt;/li&gt;
&lt;li&gt;WAN status&lt;/li&gt;
&lt;li&gt;external IP&lt;/li&gt;
&lt;li&gt;live bandwidth usage&lt;/li&gt;
&lt;li&gt;total traffic&lt;/li&gt;
&lt;li&gt;uptime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But that’s just a starting point.&lt;/p&gt;

&lt;p&gt;Because it’s running ESPHome, it can be extended in any direction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flash the screen when WAN goes down&lt;/li&gt;
&lt;li&gt;display alerts or notifications&lt;/li&gt;
&lt;li&gt;switch between different pages of data&lt;/li&gt;
&lt;li&gt;integrate other sensors from Home Assistant&lt;/li&gt;
&lt;li&gt;react to events instead of just polling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that requires changing the hardware. It’s all software.&lt;/p&gt;

&lt;p&gt;What makes this particularly interesting is how accidental it is. The device wasn’t designed to be used this way. It just happens to expose enough of its internals to make it possible.&lt;/p&gt;

&lt;p&gt;That’s a recurring pattern with these kinds of products. They sit in a space between consumer electronics and development boards. Most people use them as intended. A few people look inside and realize they can do much more. This ended up being one of those cases.&lt;/p&gt;

&lt;p&gt;It’s still sitting on my desk, still acting as a clock. But now it’s also a live view into my network, something I can glance at without opening a dashboard or checking an app. And that’s the part that makes it better.&lt;/p&gt;

&lt;p&gt;Not because it’s more complex, but because it’s more useful.&lt;/p&gt;

</description>
      <category>homeassistant</category>
      <category>hacking</category>
      <category>iot</category>
    </item>
    <item>
      <title>Improving the ESP32 Wiimote Library - From Prototype to Production-Ready Arduino Library</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Tue, 10 Mar 2026 19:58:21 +0000</pubDate>
      <link>https://forem.com/andremmfaria/improving-the-esp32-wiimote-library-from-prototype-to-production-ready-arduino-library-448e</link>
      <guid>https://forem.com/andremmfaria/improving-the-esp32-wiimote-library-from-prototype-to-production-ready-arduino-library-448e</guid>
      <description>&lt;h2&gt;
  
  
  1. Why I Needed a Better ESP32 Wiimote Library
&lt;/h2&gt;

&lt;p&gt;Nintendo’s Wii controllers are still surprisingly capable input devices. They are inexpensive, widely available, and include multiple sensors: digital buttons, a three-axis accelerometer, and support for extension controllers such as the Nunchuk. Because they communicate over Bluetooth, they can also be integrated into modern embedded systems without additional hardware.&lt;/p&gt;

&lt;p&gt;For ESP32 projects, one of the few existing implementations is the &lt;strong&gt;&lt;a href="https://github.com/hrgraf/ESP32Wiimote" rel="noopener noreferrer"&gt;ESP32Wiimote&lt;/a&gt;&lt;/strong&gt;. The library provides a functional way to connect an ESP32 board to a Wiimote and exposes several core features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bluetooth pairing with Wii controllers&lt;/li&gt;
&lt;li&gt;Button input events&lt;/li&gt;
&lt;li&gt;Accelerometer data from the Wiimote&lt;/li&gt;
&lt;li&gt;Support for extension controllers like the Nunchuk&lt;/li&gt;
&lt;li&gt;A simple demonstration sketch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a starting point, the library works well. It demonstrates how the ESP32’s Bluetooth stack can communicate with the Wiimote and decode controller data. For experimentation or small prototypes, it provides everything needed to get input from the controller.&lt;/p&gt;

&lt;p&gt;However, once I began integrating the library into a larger project, some limitations became apparent. These are common challenges when a library evolves from a proof-of-concept into something used in real systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited runtime feedback&lt;/strong&gt; – applications had little visibility into connection state or controller status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal documentation&lt;/strong&gt; – most usage details were embedded only in the example sketch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of automated testing&lt;/strong&gt; – making refactors risky and harder to validate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic project structure&lt;/strong&gt; – the repository layout did not fully follow modern Arduino library conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limited observability&lt;/strong&gt; – debugging Bluetooth behavior required manual serial prints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these issues prevented the library from working, but they made it harder to integrate into a reliable system. In particular, when building systems that run continuously or interact with other services, features like connection monitoring, structured logging, and predictable APIs become much more important.&lt;/p&gt;

&lt;p&gt;Rather than starting from scratch, I decided to refactor and extend the original project while preserving its core functionality. The result is my fork of the library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/andremmfaria/ESP32Wiimote" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/ESP32Wiimote&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of the fork is not to replace the original work, but to evolve it into a more maintainable and production-ready Arduino library. The improvements focus on code organization, runtime features, testing infrastructure, and integration with the broader Arduino ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Real Project Behind This Work
&lt;/h2&gt;

&lt;p&gt;The motivation for improving the library came from a practical project: using a &lt;strong&gt;Wii controller as a wireless input device for Home Assistant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Home automation platforms often rely on smartphones or dedicated remotes for interaction. While these solutions work well, they do not always provide the flexibility of a programmable controller with physical buttons and motion sensors.&lt;/p&gt;

&lt;p&gt;A Wiimote offers several advantages in this context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple buttons for triggering actions&lt;/li&gt;
&lt;li&gt;accelerometer input for gesture control&lt;/li&gt;
&lt;li&gt;extension controllers such as the Nunchuk&lt;/li&gt;
&lt;li&gt;reliable wireless connectivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To integrate the controller with Home Assistant, I designed a small bridge architecture where an ESP32 acts as the Bluetooth interface to the Wiimote and forwards controller events to another system.&lt;/p&gt;

&lt;p&gt;The high-level architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Wiimote
   ↓ Bluetooth
ESP32
   ↓ Serial
Serial → MQTT bridge
   ↓ MQTT
Home Assistant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;ESP32 connects to the Wiimote over Bluetooth&lt;/strong&gt; and decodes controller input.&lt;/li&gt;
&lt;li&gt;Controller events are sent through the ESP32’s &lt;strong&gt;serial interface&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A small bridge service converts those events into &lt;strong&gt;MQTT messages&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Home Assistant consumes the MQTT events and triggers automations.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This design keeps the ESP32 firmware relatively simple while allowing the rest of the system to run on a more capable host.&lt;/p&gt;

&lt;p&gt;For example, a button press could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;toggle lights&lt;/li&gt;
&lt;li&gt;activate a scene&lt;/li&gt;
&lt;li&gt;control media playback&lt;/li&gt;
&lt;li&gt;navigate a dashboard&lt;/li&gt;
&lt;li&gt;trigger custom automations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before building the full integration, however, the underlying Wiimote library needed to be more robust. The ESP32 firmware had to be able to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detect when controllers disconnect&lt;/li&gt;
&lt;li&gt;expose battery status&lt;/li&gt;
&lt;li&gt;provide clear debugging output&lt;/li&gt;
&lt;li&gt;remain maintainable as new features are added&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Improving the Wiimote library therefore became the first step toward enabling this architecture.&lt;/p&gt;

&lt;p&gt;In a &lt;strong&gt;follow-up article&lt;/strong&gt;, I will go deeper into the Home Assistant side of the project and describe how the ESP32 firmware, serial bridge, and MQTT integration work together to turn a Wiimote into a home automation controller.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Applying Arduino Library Best Practices
&lt;/h2&gt;

&lt;p&gt;The original &lt;strong&gt;&lt;a href="https://github.com/hrgraf/ESP32Wiimote" rel="noopener noreferrer"&gt;ESP32Wiimote&lt;/a&gt;&lt;/strong&gt; already provides a solid implementation for connecting ESP32 boards to Wii controllers. The core Bluetooth functionality, input decoding, and extension support were all present and working well.&lt;/p&gt;

&lt;p&gt;The goal of this fork was therefore not to redesign the library, but to &lt;strong&gt;apply common Arduino ecosystem best practices&lt;/strong&gt; and make the project compliant with the expectations of the Arduino Library Manager.&lt;/p&gt;

&lt;p&gt;The first step was aligning the repository with the standard structure expected by Arduino libraries.&lt;/p&gt;

&lt;p&gt;A typical Arduino library layout looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ESP32Wiimote
 ├── src/
 │   ├── ESP32Wiimote.cpp
 │   └── ESP32Wiimote.h
 ├── examples/
 │   └── wiimote_demo/
 ├── &lt;span class="nb"&gt;test&lt;/span&gt;/
 ├── docs/
 ├── library.properties
 ├── keywords.txt
 └── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure is recommended by Arduino because it clearly separates different parts of the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;src/&lt;/code&gt;&lt;/strong&gt; contains the library implementation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;examples/&lt;/code&gt;&lt;/strong&gt; provides sketches demonstrating how to use the library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;docs/&lt;/code&gt;&lt;/strong&gt; contains additional documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;test/&lt;/code&gt;&lt;/strong&gt; holds automated tests for development&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two metadata files were also added:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;library.properties&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This file describes the library for the Arduino ecosystem, including its name, version, architecture compatibility, and author information. The Arduino Library Manager uses this metadata to index and distribute the library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;keywords.txt&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This file enables syntax highlighting for library classes and functions inside the Arduino IDE, improving the developer experience.&lt;/p&gt;

&lt;p&gt;In addition to the structural changes, the repository was cleaned up to follow common Arduino library practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ensuring headers and source files are organized inside &lt;code&gt;src/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;improving documentation and examples&lt;/li&gt;
&lt;li&gt;adding consistent formatting to the codebase&lt;/li&gt;
&lt;li&gt;preparing the project for automated testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These changes do not alter the fundamental behavior of the library. Instead, they make the project easier to maintain, easier to install through Arduino tooling, and easier for other developers to understand and contribute to.&lt;/p&gt;

&lt;p&gt;Aligning the project with these conventions also made it possible to submit the library to the Arduino Library Manager, which significantly improves accessibility for users of the Arduino ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. New Runtime Features
&lt;/h2&gt;

&lt;p&gt;Beyond structural improvements, the fork introduces several runtime capabilities that make the library easier to integrate into real applications.&lt;/p&gt;

&lt;p&gt;When working with wireless controllers, especially over Bluetooth, applications often need more visibility into the state of the device. The new features focus on improving observability and control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection State Detection
&lt;/h3&gt;

&lt;p&gt;Bluetooth peripherals can disconnect for many reasons: signal loss, power issues, or the controller simply turning off. Applications therefore need a reliable way to determine whether a device is currently connected.&lt;/p&gt;

&lt;p&gt;The library now exposes a simple method for checking connection status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;isConnected()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows firmware to react appropriately when a controller disconnects. For example, a program can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trigger reconnection logic&lt;/li&gt;
&lt;li&gt;reset controller state&lt;/li&gt;
&lt;li&gt;update user feedback such as LEDs or displays&lt;/li&gt;
&lt;li&gt;disable actions until the controller reconnects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This functionality becomes particularly important for long-running systems where the ESP32 may stay powered on for days or weeks.&lt;/p&gt;




&lt;h3&gt;
  
  
  Battery Monitoring
&lt;/h3&gt;

&lt;p&gt;Another addition is access to the Wiimote’s battery level.&lt;/p&gt;

&lt;p&gt;The library now provides two related functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;getBatteryLevel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;requestBatteryUpdate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows applications to monitor controller battery status in real time. In systems where controllers are used frequently, battery monitoring enables useful behaviors such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;displaying battery status on a dashboard&lt;/li&gt;
&lt;li&gt;sending alerts when battery levels are low&lt;/li&gt;
&lt;li&gt;preventing unexpected controller shutdown during operation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For home automation scenarios, battery information can also be forwarded to monitoring systems through MQTT or similar telemetry mechanisms.&lt;/p&gt;




&lt;h3&gt;
  
  
  Improved Logging and Debugging
&lt;/h3&gt;

&lt;p&gt;Debugging Bluetooth communication can be difficult when limited to raw serial output. To make troubleshooting easier, the library introduces a configurable logging system.&lt;/p&gt;

&lt;p&gt;Different logging levels allow developers to control how much information is printed during operation. This provides insight into key events such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pairing and connection setup&lt;/li&gt;
&lt;li&gt;controller initialization&lt;/li&gt;
&lt;li&gt;input parsing&lt;/li&gt;
&lt;li&gt;extension controller detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Structured logging makes it much easier to diagnose issues during development or integration.&lt;/p&gt;




&lt;h3&gt;
  
  
  Expanded Example Sketch
&lt;/h3&gt;

&lt;p&gt;The example sketch included in the repository was also expanded to better demonstrate the library’s capabilities.&lt;/p&gt;

&lt;p&gt;The updated example now illustrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the full connection lifecycle&lt;/li&gt;
&lt;li&gt;button input decoding&lt;/li&gt;
&lt;li&gt;accelerometer readings&lt;/li&gt;
&lt;li&gt;Nunchuk extension data&lt;/li&gt;
&lt;li&gt;battery reporting&lt;/li&gt;
&lt;li&gt;periodic update statistics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of acting only as a minimal demo, the example now serves as a &lt;strong&gt;reference implementation&lt;/strong&gt; for developers integrating the library into their own projects.&lt;/p&gt;

&lt;p&gt;This combination of new runtime features and improved examples makes the library more suitable for real-world systems where reliability, observability, and maintainability are essential.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Adding Automated Testing
&lt;/h2&gt;

&lt;p&gt;One improvement I wanted to introduce early was &lt;strong&gt;automated testing&lt;/strong&gt;. While testing is common in most software projects, it is still relatively uncommon in Arduino libraries, largely because embedded systems interact with hardware and peripherals that are difficult to simulate.&lt;/p&gt;

&lt;p&gt;However, even when hardware is involved, there are still many parts of a library that benefit from automated validation. For example, data parsing logic, internal structures, and event handling can often be tested independently of the physical device.&lt;/p&gt;

&lt;p&gt;To support this, the project now includes a &lt;code&gt;test/&lt;/code&gt; directory with a basic testing setup. The goal is not to simulate the entire ESP32 environment, but to create a framework where core components of the library can be validated as the code evolves.&lt;/p&gt;

&lt;p&gt;This approach provides several benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safer refactoring&lt;/strong&gt; – changes can be validated before running them on hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regression prevention&lt;/strong&gt; – previously fixed issues are less likely to reappear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved contributor confidence&lt;/strong&gt; – developers can verify their changes locally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In addition to automated tests, the example sketch serves as a &lt;strong&gt;hardware validation reference&lt;/strong&gt;. By running the example on a real ESP32 connected to a Wiimote, developers can quickly verify that button events, sensors, and extensions behave as expected.&lt;/p&gt;

&lt;p&gt;Testing embedded software will always involve some interaction with real hardware, but combining automated tests with structured examples makes it much easier to maintain the project over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Publishing to the Arduino Library Manager
&lt;/h2&gt;

&lt;p&gt;After aligning the repository structure with Arduino conventions and improving the library itself, the final step was to make the project easier for others to install and use.&lt;/p&gt;

&lt;p&gt;The Arduino ecosystem distributes libraries through the &lt;strong&gt;Arduino Library Manager&lt;/strong&gt;, which indexes libraries from a central repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/arduino/library-registry?tab=readme-ov-file#adding-a-library-to-library-manager" rel="noopener noreferrer"&gt;Arduino Library Registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make a library available there, it must meet several requirements, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a valid &lt;code&gt;library.properties&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;a repository layout compatible with Arduino tooling&lt;/li&gt;
&lt;li&gt;semantic versioning&lt;/li&gt;
&lt;li&gt;a tagged release&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those requirements were met, the library was submitted to the registry through a pull request:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESP32Wiimote Arduino Library Manager submission: &lt;a href="https://github.com/arduino/library-registry/pull/7883" rel="noopener noreferrer"&gt;https://github.com/arduino/library-registry/pull/7883&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the submission was reviewed and the automated checks passed, the library was accepted into the index.&lt;/p&gt;

&lt;p&gt;This means the library can now be installed directly from the Arduino IDE using the &lt;strong&gt;Library Manager&lt;/strong&gt;, without needing to manually clone the repository.&lt;/p&gt;

&lt;p&gt;For developers, this provides several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple installation directly from the IDE&lt;/li&gt;
&lt;li&gt;automatic updates when new versions are released&lt;/li&gt;
&lt;li&gt;easier discovery within the Arduino ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Making the library available through the Library Manager helps ensure that ESP32 developers who want to use Wii controllers can install and use the project with minimal setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Future Improvements
&lt;/h2&gt;

&lt;p&gt;While the library is now easier to use and integrates well with the Arduino ecosystem, there are still several areas where it could evolve further.&lt;/p&gt;

&lt;p&gt;One potential improvement is &lt;strong&gt;support for multiple Wiimotes connected to a single ESP32&lt;/strong&gt;. The current implementation focuses on managing a single controller, which is sufficient for many projects. However, some use cases—such as robotics, gaming interfaces, or interactive installations—could benefit from handling multiple controllers simultaneously.&lt;/p&gt;

&lt;p&gt;Supporting multiple Wiimotes would likely require improvements in areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection management and pairing workflows&lt;/li&gt;
&lt;li&gt;tracking controller identities and connection states&lt;/li&gt;
&lt;li&gt;handling concurrent input streams&lt;/li&gt;
&lt;li&gt;managing Bluetooth resource limits on the ESP32&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another area that could be explored is &lt;strong&gt;expanded support for Wiimote extensions&lt;/strong&gt;. The Nunchuk is already supported, but the Wii ecosystem includes several other extension devices, such as the Classic Controller and MotionPlus. Adding support for these devices would expand the range of inputs available to ESP32-based projects.&lt;/p&gt;

&lt;p&gt;There is also room for improving &lt;strong&gt;event handling abstractions&lt;/strong&gt;. Currently, applications interact with decoded controller state and events directly. A higher-level event system could make it easier to write applications that react to button presses, motion events, or controller changes without having to process low-level state updates.&lt;/p&gt;

&lt;p&gt;Additional improvements could include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improving reconnection behavior after controller disconnects&lt;/li&gt;
&lt;li&gt;adding optional callback-based input handling&lt;/li&gt;
&lt;li&gt;expanding the test suite to cover more scenarios&lt;/li&gt;
&lt;li&gt;providing additional example sketches for common use cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As with many open-source projects, the direction of these improvements will largely depend on the needs of the community and the projects that adopt the library.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The original &lt;strong&gt;&lt;a href="https://github.com/hrgraf/ESP32Wiimote" rel="noopener noreferrer"&gt;ESP32Wiimote&lt;/a&gt;&lt;/strong&gt; already provided a solid implementation for connecting Wii controllers to ESP32 boards. This work focused on building on top of that foundation by applying Arduino ecosystem best practices and introducing several practical improvements.&lt;/p&gt;

&lt;p&gt;The fork aligns the project with the expectations of the Arduino Library Manager, improves maintainability, and introduces new runtime capabilities that make the library easier to integrate into real applications.&lt;/p&gt;

&lt;p&gt;Some of the key improvements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arduino-compliant project structure and metadata&lt;/li&gt;
&lt;li&gt;improved documentation and examples&lt;/li&gt;
&lt;li&gt;code quality and formatting improvements&lt;/li&gt;
&lt;li&gt;automated testing support&lt;/li&gt;
&lt;li&gt;improved logging and debugging&lt;/li&gt;
&lt;li&gt;runtime features such as connection state detection and battery monitoring&lt;/li&gt;
&lt;li&gt;availability through the Arduino Library Manager&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is a library that keeps the strengths of the original implementation while making it easier for developers to install, use, and extend.&lt;/p&gt;

&lt;p&gt;The library is available here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESP32Wiimote: &lt;a href="https://github.com/andremmfaria/ESP32Wiimote" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/ESP32Wiimote&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are interested in using Wii controllers with ESP32 boards, this library provides a solid starting point—and hopefully a foundation for even more creative projects in the future.&lt;/p&gt;

</description>
      <category>wii</category>
      <category>esp32</category>
      <category>arduino</category>
    </item>
    <item>
      <title>Mastering Technical Interviews A Practical Guide to the Algorithms That Appear Again and Again</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Mon, 02 Mar 2026 18:03:30 +0000</pubDate>
      <link>https://forem.com/andremmfaria/mastering-technical-interviews-a-practical-guide-to-the-algorithms-that-appear-again-and-again-3b65</link>
      <guid>https://forem.com/andremmfaria/mastering-technical-interviews-a-practical-guide-to-the-algorithms-that-appear-again-and-again-3b65</guid>
      <description>&lt;p&gt;Technical interviews are often framed as a test of memorization: recognize a pattern, recall a solution, write it under time pressure. This framing has fuelled an entire industry around grinding problem sets and rehearsing answers, as if strong engineers were pattern-recognition machines trained to replay known solutions on demand. &lt;a href="https://en.wikipedia.org/wiki/Coding_interview" rel="noopener noreferrer"&gt;Technical interviews&lt;/a&gt; are generally designed to evaluate problem-solving ability, reasoning, and coding skills rather than rote recall. &lt;a href="https://www.researchgate.net/publication/393378712_How_do_Software_Engineering_Candidates_Prepare_for_Technical_Interviews" rel="noopener noreferrer"&gt;Research has shown&lt;/a&gt; that many candidates prepare in ways that do not reflect real engineering work, often relying on memorization rather than authentic problem-solving practice.&lt;/p&gt;

&lt;p&gt;That isn’t how real engineering works. In practice, developers are expected to analyze incomplete information, reason about trade-offs, gather additional data when needed, and choose an approach that fits the constraints at hand. The best solutions rarely come from recalling a memorized template verbatim; they emerge from understanding the problem deeply and applying the right tools deliberately.&lt;/p&gt;

&lt;p&gt;The algorithmic patterns discussed in this article (two pointers, sliding windows, heaps, traversals, dynamic programming, and others) are not meant to be memorized as answers. They are mental models: reusable ways of structuring thought when facing certain classes of problems. When understood properly, they guide reasoning rather than replace it. Many interview-preparation guides emphasize that &lt;a href="https://www.codinginterview.com/blog/leetcode-vs-coding-interview-patterns/" rel="noopener noreferrer"&gt;patterns are meant to teach structured problem decomposition&lt;/a&gt;, not memorized solutions.&lt;/p&gt;

&lt;p&gt;This guide focuses on those patterns not as a checklist to grind through, but as a toolbox to support problem analysis. The goal is not to “pass interviews by rote”, but to approach technical problems (interview or real-world) with clarity, structure, and sound judgement. &lt;a href="https://www.lockedinai.com/blog/master-15-leetcode-patterns" rel="noopener noreferrer"&gt;Pattern-based preparation&lt;/a&gt; is most effective when it builds reasoning skills rather than memorization, reinforcing a problem-solving mindset instead of recall.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Two Pointers
&lt;/h2&gt;

&lt;p&gt;Two pointers are useful when an array or string must be processed from two directions or when you need to maintain a pair of indices representing a candidate solution. This approach reduces nested loops into linear scans. It is most effective when the input is sorted, or when the problem involves distances, sums, comparisons between ends, or in-place modifications without extra memory.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The array is sorted or can be sorted.&lt;/li&gt;
&lt;li&gt;The task involves pairwise relationships: sum to target, maximize or minimize distance, compare left vs. right properties.&lt;/li&gt;
&lt;li&gt;The problem asks for in-place rearrangement or partitioning.&lt;/li&gt;
&lt;li&gt;You want to eliminate a nested loop and reduce complexity from O(n²) to O(n).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Typical patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Opposite-direction pointers moving toward each other (summing, container area, water trapping).&lt;/li&gt;
&lt;li&gt;Same-direction pointers, where one pointer marks the “write” position (Move Zeroes, Dutch Flag sorting).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example template (sum-based):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example template (in-place compaction):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;moveZeroes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;insert&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;insert&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Sliding Window
&lt;/h2&gt;

&lt;p&gt;Sliding windows handle problems involving contiguous subarrays or substrings. The key idea is maintaining a window [l, r] with properties that can be updated as r expands and l contracts. This avoids recomputation and typically yields O(n) complexity. Sliding windows come in fixed-size and variable-size forms.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem explicitly requires considering contiguous sequences.&lt;/li&gt;
&lt;li&gt;The goal is to maximize/minimize length, find the longest substring with constraints, or compute sums efficiently.&lt;/li&gt;
&lt;li&gt;There is a property that can be updated incrementally when the window expands or shrinks.&lt;/li&gt;
&lt;li&gt;Hash maps or counters are used to track window validity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fixed-size window:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used when the window size is given (e.g., “subarray of size k”).&lt;/li&gt;
&lt;li&gt;Simply slide by removing leftmost element and adding rightmost.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Variable-size window:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used when the window grows until invalid and then shrinks to restore validity.&lt;/li&gt;
&lt;li&gt;Common in distinct-character constraints or frequency-based problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example fixed-size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;max_sum_subarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;window_sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window_sum&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;window_sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;window_sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example variable-size:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lengthOfLongestSubstring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Intervals
&lt;/h2&gt;

&lt;p&gt;Interval problems revolve around operations on ranges [start, end]. Solutions almost always begin with sorting intervals, and reasoning about overlaps, merges, or gaps. Correct management of boundaries is essential. Many problems reduce to merging, insertion, or counting overlapping intervals.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input consists of ranges and you must merge, insert, or count overlaps.&lt;/li&gt;
&lt;li&gt;You are asked whether intervals overlap or conflict.&lt;/li&gt;
&lt;li&gt;You must determine available or free time.&lt;/li&gt;
&lt;li&gt;Greedy techniques become effective after sorting by start or end times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Core techniques:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sort by start time when merging or inserting.&lt;/li&gt;
&lt;li&gt;Sort by end time when minimizing conflicts.&lt;/li&gt;
&lt;li&gt;Maintain a running "current end" to detect overlap or free space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example merge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example non-overlapping (minimum removals):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eraseOverlapIntervals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;last_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-inf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;last_end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;last_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Stack
&lt;/h2&gt;

&lt;p&gt;Stacks are suitable for problems involving nested structures, reversing order, parsing, or tracking monotonic sequences. A stack keeps context: what has been seen but not yet closed or resolved. Monotonic stacks allow efficient next-greater-element or histogram computations.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parentheses or encoded strings must be validated or decoded.&lt;/li&gt;
&lt;li&gt;You need "previous greater/smaller" or "next greater/smaller".&lt;/li&gt;
&lt;li&gt;Problems require evaluating expressions or parsing nested formats.&lt;/li&gt;
&lt;li&gt;You want to track elements in sorted order while maintaining O(n) amortized time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Classic push/pop for matching delimiters.&lt;/li&gt;
&lt;li&gt;Monotonic stack: maintain increasing or decreasing order to compute ranges efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example parentheses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;isValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;([{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example monotonic (Daily Temperatures):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dailyTemperatures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;
        &lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Linked List
&lt;/h2&gt;

&lt;p&gt;Linked list techniques rely on pointer manipulation, often requiring careful handling of node references. Many solutions hinge on using fast/slow pointers to detect cycles, identify midpoints, or perform operations relative to the end of the list. Extra memory is usually unnecessary, and elegance depends on pointer management.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You must detect cycles or intersections.&lt;/li&gt;
&lt;li&gt;The task involves reversing part or all of a list.&lt;/li&gt;
&lt;li&gt;Operations depend on the nth node from the end.&lt;/li&gt;
&lt;li&gt;You must reorder nodes without converting to arrays.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast/slow pointer to find cycles or midpoints.&lt;/li&gt;
&lt;li&gt;Dummy nodes to simplify edge-case manipulation.&lt;/li&gt;
&lt;li&gt;Two-pointer offset technique for “remove nth from end”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example cycle detection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hasCycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
        &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example remove nth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;removeNthFromEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ListNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
        &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Binary Search
&lt;/h2&gt;

&lt;p&gt;Binary search applies to sorted arrays or to problems where the answer lies in a monotonic search space. You can binary-search over indices, values, or even abstract answers (binary search on “feasibility”). A solution is valid if increasing or decreasing the parameter changes feasibility in a predictable (monotonic) way.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The array is sorted, rotated, or partially sorted.&lt;/li&gt;
&lt;li&gt;The problem asks for first/last occurrence, boundary, or pivot index.&lt;/li&gt;
&lt;li&gt;You can express the question as: “Is x feasible?” and feasibility changes monotonically.&lt;/li&gt;
&lt;li&gt;You must optimize or minimize some parameter, such as speed, capacity, or rate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standard binary search on sorted arrays.&lt;/li&gt;
&lt;li&gt;Modified binary search for rotated sorted arrays.&lt;/li&gt;
&lt;li&gt;Binary search on answer when the value domain is large but checking feasibility is O(n).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example binary search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;binary_search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example binary search on answer (Koko Eating Bananas):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;minEatingSpeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;piles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hours&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Heap (Priority Queue)
&lt;/h2&gt;

&lt;p&gt;Heaps are ideal when the problem requires repeatedly extracting the minimum or maximum element, or maintaining a dynamic set where only the top-k items matter. They guarantee O(log n) insertion and extraction and are essential when selecting the smallest/largest elements without fully sorting. Heaps shine in multi-way merging, streaming problems, and any scenario where you need efficient “best candidate” retrieval.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task asks for the k smallest/largest items.&lt;/li&gt;
&lt;li&gt;You need to continuously push/pop values while keeping only the top k.&lt;/li&gt;
&lt;li&gt;You must merge multiple sorted lists or streams.&lt;/li&gt;
&lt;li&gt;A greedy algorithm relies on always selecting the current minimum or maximum.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Min-heap&lt;/strong&gt; for selecting smallest; use negative values for max-heap behavior.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size-k heaps&lt;/strong&gt; to ensure O(n log k) solutions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tuples in heaps&lt;/strong&gt; for ordering by multiple properties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Kth Largest Element&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;findKthLargest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heapify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:]:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heapreplace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Merge K Sorted Lists&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mergeKLists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lists&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;dummy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ListNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;
        &lt;span class="n"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;heapq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;heappush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;next&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Depth-First Search (DFS)
&lt;/h2&gt;

&lt;p&gt;DFS is used for exploring deep paths in trees or graphs, inspecting components, and performing recursive structural computations. It is especially useful when the problem requires visiting all nodes in a connected component, generating all possible paths, or computing metrics that depend on recursive aggregation. DFS works on both trees and general graphs, using visited sets to avoid cycles.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem requires exploring all paths or all nodes in a region.&lt;/li&gt;
&lt;li&gt;Tree problems that involve computing depth, height, tilt, diameter, or checking validity.&lt;/li&gt;
&lt;li&gt;Graph problems involving connected components, cloning, or traversal.&lt;/li&gt;
&lt;li&gt;Grid problems identifying islands, regions, or flood fill behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursive DFS for tree or grid problems.&lt;/li&gt;
&lt;li&gt;Stack-based DFS for graph problems.&lt;/li&gt;
&lt;li&gt;Mark visited nodes to prevent infinite loops.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Maximum Depth of Binary Tree&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;maxDepth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Number of Islands (grid DFS)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numIslands&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Breadth-First Search (BFS)
&lt;/h2&gt;

&lt;p&gt;BFS excels at shortest-path problems on unweighted graphs, level-order processing in trees, and multi-source propagation (spreading effects over steps). BFS processes nodes layer by layer, guaranteeing the minimum number of steps to reach targets. It is the appropriate choice when the question involves minimum distances, time steps, or systematic level traversal.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem asks for the shortest number of steps in an unweighted setting.&lt;/li&gt;
&lt;li&gt;You must process a tree or graph level by level.&lt;/li&gt;
&lt;li&gt;Multi-source diffusion problems: rotting oranges, spread of signals, BFS from multiple starting states.&lt;/li&gt;
&lt;li&gt;Grid problems requiring finding the minimal distance to something.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use a queue and process nodes per level.&lt;/li&gt;
&lt;li&gt;Use visited sets for cycles in graphs.&lt;/li&gt;
&lt;li&gt;Push all initial sources before starting (multi-source BFS).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Level Order Traversal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;levelOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Rotting Oranges (multi-source BFS)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;orangesRotting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;dr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;dc&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;nr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;cols&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
                &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;nr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;minutes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fresh&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Backtracking
&lt;/h2&gt;

&lt;p&gt;Backtracking is the algorithmic backbone for generating all valid configurations under constraints. It searches through the solution space using depth-first exploration while pruning invalid options as early as possible. This allows concise solutions for combinatorial problems, exhaustive enumeration, and constructing sequences step-by-step while maintaining validity.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem requires generating all subsets, permutations, or combinations.&lt;/li&gt;
&lt;li&gt;There is a need to explore choices step-by-step while respecting constraints.&lt;/li&gt;
&lt;li&gt;Validity can be checked incrementally, allowing pruning of branches.&lt;/li&gt;
&lt;li&gt;Search space is exponential and requires efficient pruning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recursive function with state &lt;code&gt;path&lt;/code&gt; and decision index.&lt;/li&gt;
&lt;li&gt;Undo action (&lt;code&gt;path.pop()&lt;/code&gt;) after exploring each branch.&lt;/li&gt;
&lt;li&gt;Prune early when the partial solution already violates constraints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Subsets&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;[:])&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;dfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Generate Parentheses&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generateParenthesis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;open_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;close_count&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;open_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;open_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;close_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;close_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;open_count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;open_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;close_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;backtrack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  11. Graphs (Topological Sort)
&lt;/h2&gt;

&lt;p&gt;Topological sort is applied to directed acyclic graphs when you must determine an order of tasks respecting prerequisites. Cycle detection is inherent: if no valid ordering exists, the graph contains a cycle. It is frequently used for scheduling, dependency resolution, and course prerequisite problems.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem mentions prerequisites, dependencies, ordering, or sequence validity.&lt;/li&gt;
&lt;li&gt;You must determine if a cycle exists in a directed graph.&lt;/li&gt;
&lt;li&gt;You must output a valid order of completion.&lt;/li&gt;
&lt;li&gt;Nodes represent tasks; edges represent dependencies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compute in-degree of nodes.&lt;/li&gt;
&lt;li&gt;Use a queue to process nodes with in-degree zero.&lt;/li&gt;
&lt;li&gt;Remove edges gradually and collect nodes in order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Can Finish (detect feasibility)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;canFinish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prerequisites&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indegree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;numCourses&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prerequisites&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numCourses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;taken&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;numCourses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Course Schedule II (return ordering)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;findOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numCourses&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prerequisites&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;
    &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;indegree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;numCourses&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prerequisites&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numCourses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;indegree&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;numCourses&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  12. Dynamic Programming (DP)
&lt;/h2&gt;

&lt;p&gt;Dynamic programming is appropriate when a problem can be decomposed into overlapping subproblems with optimal substructure. DP trades space for time, storing intermediate results to avoid recomputation. Problems involving counting ways, optimizing values, or building solutions from smaller components often map directly to DP formulations.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimal solutions depend on solutions to smaller subproblems.&lt;/li&gt;
&lt;li&gt;The problem has overlapping subproblems and cannot be solved greedily.&lt;/li&gt;
&lt;li&gt;You recognize patterns like knapsack, subsequences, paths, decoding, or interval DP.&lt;/li&gt;
&lt;li&gt;The recurrence relation naturally emerges from the problem statement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1D DP&lt;/strong&gt; for sequences (Decode Ways, Word Break).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2D DP&lt;/strong&gt; for grids (Unique Paths, Maximal Square).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DP + binary search&lt;/strong&gt; for LIS-style problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DP on intervals&lt;/strong&gt; or structure-dependent DP when combining segments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Decode Ways&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;numDecodings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;dp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Longest Increasing Subsequence (DP + binary search)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bisect&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lengthOfLIS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bisect_left&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  13. Greedy Algorithms
&lt;/h2&gt;

&lt;p&gt;Greedy algorithms make locally optimal decisions at each step with the expectation that these choices lead to a global optimum. They rely on the problem having a structure where greedy-choice and optimal substructure properties naturally hold. Once you commit to a choice, you do not revisit it, making solutions efficient and typically O(n) or O(n log n).&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem can be solved by repeatedly taking the best immediate option.&lt;/li&gt;
&lt;li&gt;Sorting helps reveal an order that makes greedy decisions valid.&lt;/li&gt;
&lt;li&gt;You are maximizing or minimizing a metric such as profit, number of intervals, or fuel balance.&lt;/li&gt;
&lt;li&gt;Backtracking or DP is unnecessary because future steps do not depend on alternative past choices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track running min/max (Best Time to Buy/Sell Stock).&lt;/li&gt;
&lt;li&gt;Maintain cumulative resource balance (Gas Station).&lt;/li&gt;
&lt;li&gt;Advance by the farthest reachable index each step (Jump Game).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Best Time to Buy and Sell Stock&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;maxProfit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;min_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inf&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;min_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;min_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Jump Game&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;canJump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;reachable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jump&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;reachable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;reachable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reachable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;jump&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  14. Trie
&lt;/h2&gt;

&lt;p&gt;Tries efficiently store and query large sets of strings, especially when prefix operations are frequent. They organize characters in a tree-like structure where each path from root to node represents a prefix. Tries allow O(m) lookup where m is the word length, independent of how many words exist. They are fundamental for autocomplete, prefix filtering, and dictionary checks.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The task involves prefix search or prefix matching.&lt;/li&gt;
&lt;li&gt;You must repeatedly query or insert strings with overlapping prefixes.&lt;/li&gt;
&lt;li&gt;Problems ask whether any word starts with a given prefix.&lt;/li&gt;
&lt;li&gt;Searching character-by-character offers more efficiency than scanning all strings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each node contains a map of children.&lt;/li&gt;
&lt;li&gt;Mark &lt;code&gt;end = True&lt;/code&gt; for completed words.&lt;/li&gt;
&lt;li&gt;Walk the trie for searching or prefix validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Trie Implementation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TrieNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Trie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TrieNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;TrieNode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;children&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example use case indicator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: many words, many queries → trie fits.&lt;/li&gt;
&lt;li&gt;Task: “return the number of words with a given prefix” or “determine if any word begins with prefix”.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  15. Prefix Sum
&lt;/h2&gt;

&lt;p&gt;Prefix sums transform cumulative operations into O(1) queries by precomputing running totals. They allow rapid calculation of subarray sums, difference queries, and frequency-based insights. Instead of recomputing from scratch, you subtract two prefix values to get the sum of any range.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The problem involves frequent sum-of-subarray queries.&lt;/li&gt;
&lt;li&gt;You must detect subarrays with a target sum or pattern.&lt;/li&gt;
&lt;li&gt;Overlapping subarrays need efficient comparison.&lt;/li&gt;
&lt;li&gt;A running balance or cumulative measure is helpful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;prefix[i] = nums[0] + ... + nums[i-1]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Subarray sum from i to j: prefix[j+1] – prefix[i]&lt;/li&gt;
&lt;li&gt;Hash map of prefix sums to detect subarrays with specific targets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Subarray Sum Equals K&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subarraySum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;freq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nums&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Count subarrays with sum k.”&lt;/li&gt;
&lt;li&gt;“Find how many substrings satisfy some cumulative constraint.”&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  16. Matrices
&lt;/h2&gt;

&lt;p&gt;Matrix problems require structured 2D traversal, manipulation, or transformation. Many tasks involve row/column operations, rotation, flooding, or spiral traversal. Solutions often rely on systematic scans or in-place transformations to maintain O(1) space. Index manipulation is the core challenge: understanding how rows and columns shift relative to one another.&lt;/p&gt;

&lt;p&gt;Use when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The question involves grid-based movement or transformations.&lt;/li&gt;
&lt;li&gt;Problems require rotating, flipping, zeroing rows and columns.&lt;/li&gt;
&lt;li&gt;Spiral-order traversal or layer-by-layer operations apply.&lt;/li&gt;
&lt;li&gt;2D constraints create natural boundaries for iteration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use boundary pointers for spirals.&lt;/li&gt;
&lt;li&gt;Matrix transpositions and reversals for rotations.&lt;/li&gt;
&lt;li&gt;Row/column flags for operations like Set Matrix Zeroes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: Spiral Matrix&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;spiralOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;bottom&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Rotate Image (90° clockwise)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# transpose
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Set Matrix Zeroes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First pass: mark zero rows and columns.&lt;/li&gt;
&lt;li&gt;Second pass: zero out cells in marked rows/columns.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Technical interviews should not reward the ability to memorize solutions or replay patterns on cue. Engineering is not an SAT exam, and developers are not pattern-recognition machines. In real systems, problems are ambiguous, data is incomplete, and the correct approach often emerges only after careful analysis or after asking better questions and gathering more information.&lt;/p&gt;

&lt;p&gt;The algorithmic techniques covered in this article are best understood as tools, not answers. They are ways of shaping thought, reason about constraints, structure data, and reduce complexity. Used correctly, they help engineers arrive at solutions; used mechanically, they become blunt instruments.&lt;/p&gt;

&lt;p&gt;For candidates, this means focusing less on grinding problems and more on understanding why a technique applies, when it does not, and how to adapt it when conditions change. Short, deliberate practice sessions that reinforce reasoning and trade-off analysis are far more valuable than endless repetition.&lt;/p&gt;

&lt;p&gt;For interviewers, it means designing interviews that reflect real engineering work: encouraging exploration, validating assumptions, and thoughtful decision-making, rather than forcing candidates to perform another memorization exercise under time pressure. There is growing discussion in the engineering community about moving beyond purely &lt;a href="https://en.wikipedia.org/wiki/LeetCode" rel="noopener noreferrer"&gt;LeetCode&lt;/a&gt;-style interviews toward &lt;a href="https://hoffm.medium.com/six-coding-interview-formats-to-replace-leetcode-84f3c770b5c1" rel="noopener noreferrer"&gt;formats that better reflect real-world problem solving&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Master the concepts, not the scripts. Treat patterns as a toolbox, not a collection of hammers. The goal isn’t luck or recall. It’s clarity, judgement, and the ability to reason your way to a solution.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>interview</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>When Chat Turns into Control - Security Lessons from Running a Local AI Agent using OpenClaw</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Sun, 22 Feb 2026 01:49:59 +0000</pubDate>
      <link>https://forem.com/andremmfaria/when-chat-turns-into-control-security-lessons-from-running-a-local-ai-agent-21l0</link>
      <guid>https://forem.com/andremmfaria/when-chat-turns-into-control-security-lessons-from-running-a-local-ai-agent-21l0</guid>
      <description>&lt;p&gt;Running large language models locally is easier than ever. With tools like Ollama and frameworks such as OpenClaw, it’s now trivial to deploy AI agents that reason, keep state, and execute actions on private hardware.&lt;/p&gt;

&lt;p&gt;That convenience comes with a catch.&lt;/p&gt;

&lt;p&gt;Once an LLM is wired to tools and exposed through a platform like Discord, it stops being “just a chatbot.” It becomes a control surface driven by natural language, where user input can directly influence system behaviour. In that context, traditional security assumptions like clear trust boundaries, strict input validation, predictable execution no longer hold ground.&lt;/p&gt;

&lt;p&gt;This article is not an installation guide. It’s a security-focused reflection on running a local AI agent: where the real risks appear, why “self-hosted” does not automatically mean “safe,” and which design choices actually reduce the blast radius when things go wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Context and setup
&lt;/h2&gt;

&lt;p&gt;Running LLMs locally has become easy enough that many people now treat them like “just another service.” Tools like &lt;strong&gt;&lt;a href="https://openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;&lt;/strong&gt; push this further by turning an LLM into an &lt;em&gt;agent&lt;/em&gt;: something that can reason, keep state, and execute actions.&lt;/p&gt;

&lt;p&gt;In this setup, the agent is controlled through &lt;strong&gt;&lt;a href="https://discord.com/" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/strong&gt;, backed by a local &lt;strong&gt;&lt;a href="https://ollama.com/" rel="noopener noreferrer"&gt;Ollama&lt;/a&gt;&lt;/strong&gt; instance. The deployment looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; runs on a dedicated &lt;strong&gt;TrueNAS host&lt;/strong&gt; with an &lt;strong&gt;RTX 3070&lt;/strong&gt;, handling all LLM inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt;: &lt;strong&gt;Qwen3 8B&lt;/strong&gt;, chosen for being fast and efficient on consumer GPUs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenClaw&lt;/strong&gt; runs on a separate &lt;strong&gt;Linux VM&lt;/strong&gt;, acting as the agent control plane.&lt;/li&gt;
&lt;li&gt;The two hosts communicate over the local network.&lt;/li&gt;
&lt;li&gt;Discord is the primary user interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is self-hosted and not directly exposed to the internet. At first glance, this feels “safe enough.” But once you let an agent &lt;em&gt;do things&lt;/em&gt;, not just chat, you’re no longer dealing with a toy system. You’re running automation driven by natural language, which changes the security model completely.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Architecture and trust boundaries
&lt;/h2&gt;

&lt;p&gt;At a high level, the system has three layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Discord&lt;/strong&gt; – where humans talk to the agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenClaw&lt;/strong&gt; – where decisions, memory, and tool execution happen&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama + LLM&lt;/strong&gt; – where language is generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each layer crosses a trust boundary.&lt;/p&gt;

&lt;p&gt;Discord is an &lt;strong&gt;untrusted input surface&lt;/strong&gt;, even if the users themselves are trusted. Messages can include pasted text, links, logs, or content copied from elsewhere. Research on prompt injection shows that attackers don’t need direct access to the model—indirect injection through user-supplied content is often enough to override intended behaviour (&lt;a href="https://www.mdpi.com/2078-2489/17/1/54" rel="noopener noreferrer"&gt;MDPI, 2024&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;OpenClaw sits in the middle as a &lt;strong&gt;control plane&lt;/strong&gt;. It turns text into actions. The problem is that LLMs don’t distinguish between “instructions” and “data.” Everything is just language. This is a known and well-documented weakness of LLM systems, and it’s why prompt injection keeps showing up as the dominant failure mode in agent-based designs (&lt;a href="https://arxiv.org/abs/2601.09625" rel="noopener noreferrer"&gt;arXiv:2601.09625&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Finally, when the agent can execute tools (filesystem access, memory writes, or web fetches) the risk escalates. Academic and industry analyses consistently show that once an injected prompt can &lt;em&gt;chain actions&lt;/em&gt;, the impact is no longer limited to bad answers; it can affect the system itself (&lt;a href="https://arxiv.org/abs/2410.23308" rel="noopener noreferrer"&gt;arXiv:2410.23308&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;One important takeaway: running Ollama and OpenClaw on separate hosts improves performance and resilience, but it does &lt;strong&gt;not&lt;/strong&gt; automatically solve these security problems. The weakest link is still the language interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The security problem with small models
&lt;/h2&gt;

&lt;p&gt;Qwen3 8B is a great fit for a home lab: it’s fast, it runs well on a consumer GPU (RTX 3070), and it’s cheap to keep online. The downside is that small-ish models are &lt;strong&gt;easier to steer off course&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That matters because agents don’t just “answer questions.” They can &lt;strong&gt;call tools&lt;/strong&gt;, update memory, and sometimes fetch or interpret external content. Prompt injection is now widely treated as a top-tier LLM risk for exactly this reason: language is both &lt;em&gt;data&lt;/em&gt; and &lt;em&gt;instructions&lt;/em&gt;, and the model can be tricked into treating untrusted text as “policy.” OWASP calls this out directly as a primary risk category for LLM apps. (&lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications" rel="noopener noreferrer"&gt;OWASP&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Where it gets nasty is &lt;strong&gt;indirect prompt injection&lt;/strong&gt;: the attacker doesn’t need to DM your bot with an obviously malicious prompt. They just need your agent to &lt;em&gt;consume&lt;/em&gt; content that contains hidden instructions (HTML, docs, logs, etc.). This has been demonstrated repeatedly for web agents, where malicious strings embedded in a page can hijack agent behaviour. (&lt;a href="https://arxiv.org/abs/2507.14799" rel="noopener noreferrer"&gt;arXiv:2507.14799&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;So the core issue isn’t “Qwen is bad.” It’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Small model + tool access = higher chance of bad tool calls&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small model + web/content ingestion = bigger prompt injection surface&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Once it’s an agent, you have to assume the model will occasionally do the wrong thing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why the security posture for small models tends to be: contain the blast radius (sandbox) and remove the easiest injection paths (web fetch / browser). (&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP Cheat Sheet Series&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Discord as an attack surface
&lt;/h2&gt;

&lt;p&gt;Discord feels like a friendly UI, but from a security perspective it’s an &lt;strong&gt;untrusted command channel&lt;/strong&gt;. Anything users paste (logs, URLs, config snippets) can become “model input,” and that’s enough for prompt injection to show up.&lt;/p&gt;

&lt;p&gt;The two main problems are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scope creep&lt;/strong&gt;: “it’s only our server” slowly becomes “it’s in more channels than intended”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Permission drift&lt;/strong&gt;: roles change, new channels get created, people invite the bot elsewhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the safe baseline is: deny by default, then allow only what you actually need.&lt;/p&gt;

&lt;p&gt;In practice, that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lock the bot to specific guild(s)&lt;/strong&gt; (server allowlisting)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict usage to a specific role&lt;/strong&gt; (role gating)&lt;/li&gt;
&lt;li&gt;Decide whether normal messages must be mention-gated (reduce accidental triggers)&lt;/li&gt;
&lt;li&gt;Handle &lt;strong&gt;slash commands&lt;/strong&gt; explicitly (they have their own permissions model in Discord)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Discord itself supports controlling who can use slash commands through its permissions system (and it’s worth doing that at the Discord layer, not just in the bot). (&lt;a href="https://discord.com/blog/slash-commands-permissions-discord-apps-bots" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This is the key mental shift: even if the model runs locally and the gateway isn’t public, Discord is still a big input funnel. Treat it like an API surface: least privilege, explicit allowlists, and “assume someone will paste something dumb eventually.” OWASP’s guidance maps well here: prompt injection is not rare, and the best defenses are limiting what the model can do when it gets it wrong. (&lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications" rel="noopener noreferrer"&gt;OWASP&lt;/a&gt;)&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Sandboxing and tool restriction
&lt;/h2&gt;

&lt;p&gt;Once the agent was wired to Discord and running a small model, the real risk wasn’t wrong/bad answers. It was &lt;strong&gt;uncontrolled side effects&lt;/strong&gt;. This is where sandboxing becomes essential.&lt;/p&gt;

&lt;p&gt;In OpenClaw, sandboxing means &lt;strong&gt;session-level isolation for tool execution&lt;/strong&gt;. Each conversation runs inside a constrained environment, with no access to the host filesystem or other sessions. If the model does something wrong, the impact is contained.&lt;/p&gt;

&lt;p&gt;Enabling sandboxing globally is a single configuration change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw config &lt;span class="nb"&gt;set &lt;/span&gt;agents.defaults.sandbox.mode all
openclaw config &lt;span class="nb"&gt;set &lt;/span&gt;agents.defaults.sandbox.scope session
openclaw config &lt;span class="nb"&gt;set &lt;/span&gt;agents.defaults.sandbox.workspaceAccess none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This follows OpenClaw’s sandboxing model, which prioritizes containment over perfect prevention (&lt;a href="https://docs.openclaw.ai/sandbox" rel="noopener noreferrer"&gt;docs.openclaw.ai/sandbox&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The second part of the fix was disabling web-based tools. Web access is the most common prompt-injection vector in agent systems: arbitrary, attacker-controlled text gets fed directly into the model. This has been repeatedly demonstrated in both academic work and industry analyses of indirect prompt injection (&lt;a href="https://arxiv.org/abs/2507.14799" rel="noopener noreferrer"&gt;arXiv:2507.14799&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;In practice, this meant explicitly turning off web fetch and denying the entire web tool group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw config &lt;span class="nb"&gt;set &lt;/span&gt;tools.web.fetch.enabled &lt;span class="nb"&gt;false
&lt;/span&gt;openclaw config &lt;span class="nb"&gt;set &lt;/span&gt;tools.deny &lt;span class="s1"&gt;'["group:web","browser"]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last item to complete the fix was to add a rate limiting on the auth attempts on the gateway&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw config &lt;span class="nb"&gt;set &lt;/span&gt;gateway.auth.rateLimit &lt;span class="s1"&gt;'{ "maxAttempts": 10, "windowMs": 60000, "lockoutMs": 300000 }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that there are a max of 10 failed attempts per minute and it locks out for 5 minutes after that.&lt;/p&gt;

&lt;p&gt;After these changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool execution became more predictable&lt;/li&gt;
&lt;li&gt;Web-based injection paths were removed&lt;/li&gt;
&lt;li&gt;OpenClaw’s built-in security audit reported &lt;strong&gt;zero critical or warning findings&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matches OWASP’s guidance for LLM applications: assume prompt injection will eventually happen, and focus on &lt;strong&gt;reducing blast radius&lt;/strong&gt; instead of relying on model behaviour alone (&lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/" rel="noopener noreferrer"&gt;OWASP LLM Top 10&lt;/a&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Takeaways
&lt;/h2&gt;

&lt;p&gt;A few clear lessons came out of this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local LLMs are not automatically safe just because they are self-hosted&lt;/li&gt;
&lt;li&gt;Discord is an attack surface, not just a chat UI&lt;/li&gt;
&lt;li&gt;Small models like Qwen3 8B are efficient, but need &lt;strong&gt;more&lt;/strong&gt; guardrails&lt;/li&gt;
&lt;li&gt;Sandboxing matters more than model choice&lt;/li&gt;
&lt;li&gt;Removing web access dramatically reduces risk&lt;/li&gt;
&lt;li&gt;Separating Ollama and OpenClaw hosts improves resilience, not security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of these conclusions line up with existing research and security guidance. Prompt injection, permission drift, and over-trusted tools are &lt;strong&gt;expected failure modes&lt;/strong&gt;, not edge cases (&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP Prompt Injection Cheat Sheet&lt;/a&gt;), (&lt;a href="https://arxiv.org/abs/2410.23308" rel="noopener noreferrer"&gt;arXiv:2410.23308&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The takeaway is simple: once an LLM can act, it must be treated like infrastructure. With sandboxing, explicit allowlists, and tool restrictions, a local agent can be both powerful and reasonably safe — but only if security is part of the design from the start.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>I wanted to know how malware works, so I built an analyser</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Wed, 10 Dec 2025 11:41:32 +0000</pubDate>
      <link>https://forem.com/andremmfaria/i-wanted-to-know-how-malware-works-so-i-built-an-analyser-483g</link>
      <guid>https://forem.com/andremmfaria/i-wanted-to-know-how-malware-works-so-i-built-an-analyser-483g</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction &amp;amp; Motivation
&lt;/h2&gt;

&lt;p&gt;When I began thinking about what to do for my Master’s thesis, one question kept resurfacing: &lt;strong&gt;How do people actually classify malware?&lt;/strong&gt; I had always been curious about the internal logic behind malware categorization, not just at a high level, but at the level of processes, features, and decision-making.&lt;/p&gt;

&lt;p&gt;In the end, the thesis became more of a &lt;em&gt;means to an end&lt;/em&gt;: a structured excuse to finally build something I’d wanted for years, my own static malware analyser.&lt;/p&gt;

&lt;p&gt;To do that, I needed a system that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproducible&lt;/strong&gt;, so others could follow the same steps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interpretable&lt;/strong&gt;, so each decision had a clear explanation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated&lt;/strong&gt;, so large numbers of samples could be processed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modular&lt;/strong&gt;, so rules, enrichment, or extraction could evolve over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article describes how I designed the &lt;strong&gt;baseline analysis pipeline&lt;/strong&gt;, what I learned from it, and why building it was the most effective way to understand how malware works (see survey: &lt;a href="https://www.researchgate.net/publication/328760930_A_Survey_on_Malware_Analysis_Techniques_Static_Dynamic_Hybrid_and_Memory_Analysis" rel="noopener noreferrer"&gt;ResearchGate&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Static Analysis not Dynamic analysis or both?
&lt;/h3&gt;

&lt;p&gt;I chose static analysis because it’s the simplest, safest way to make progress fast. You can point mature tools like Ghidra at a binary and immediately get structure, imports and strings—no sandbox to provision, no risk of executing the sample, and results that are easy to trace back to rules. That makes static ideal for batch triage and for learning: it’s repeatable, quick, and interpretable.&lt;/p&gt;

&lt;p&gt;Of course, static has blind spots. Dynamic analysis shows what the program actually does at runtime—process creation, network I/O, registry and file changes—and it can expose unpacking or decryption that static won’t see. The trade‑off is overhead and fragility: running malware safely requires instrumentation and isolation, it’s slower per sample, and many families try to evade sandboxes. My approach was to start with static to build a clear baseline, then layer enrichment (and later, hybrid methods) where deeper behaviour visibility is needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. High-Level Architecture of the Baseline Pipeline
&lt;/h2&gt;

&lt;p&gt;The baseline pipeline is intentionally simple. It follows a straight, modular workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Feature Extraction&lt;/strong&gt; – gather structural and semantic information from the PE file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heuristic Evaluation&lt;/strong&gt; – apply rule-based checks to detect suspicious patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional Data Enrichment&lt;/strong&gt; – pull external intelligence (e.g., VirusTotal) for reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision Fusion&lt;/strong&gt; – combine heuristic signals with enrichment (if available).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reporting&lt;/strong&gt; – output structured evidence, classification, and metadata.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each component has a narrow purpose and produces structured data that the next stage consumes. This keeps the design predictable and transparent.&lt;/p&gt;

&lt;h3&gt;
  
  
  On the optional enrichment step
&lt;/h3&gt;

&lt;p&gt;The enrichment layer is intentionally &lt;strong&gt;optional&lt;/strong&gt;. In theory, it makes the classification stronger because the heuristic output can be cross-checked against external intelligence.&lt;/p&gt;

&lt;p&gt;But enrichment also introduces an unexpected trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the heuristic analysis is &lt;strong&gt;roughly aligned&lt;/strong&gt; with the enrichment data, the result improves.&lt;/li&gt;
&lt;li&gt;If the heuristic analysis is &lt;strong&gt;far off&lt;/strong&gt; from the enrichment (e.g., near-random heuristics), the fusion process can skew the final label in unhelpful ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So enrichment is useful, but only when the baseline heuristics are not too noisy. This became a recurring theme in the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Extracting Features from Malware Samples
&lt;/h2&gt;

&lt;p&gt;Static analysis begins with extraction gathering every meaningful property of a file without running it (overview: &lt;a href="https://www.ijraset.com/research-paper/a-static-approach-for-malware-analysis-a-guide-to-analysis-tools-and-techniques" rel="noopener noreferrer"&gt;IJRASET&lt;/a&gt;). This includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PE metadata&lt;/li&gt;
&lt;li&gt;Section layout&lt;/li&gt;
&lt;li&gt;Import tables&lt;/li&gt;
&lt;li&gt;Strings&lt;/li&gt;
&lt;li&gt;Function signatures and decompiler output&lt;/li&gt;
&lt;li&gt;Embedded resources&lt;/li&gt;
&lt;li&gt;Other structural features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the baseline, the decompiler stage writes a per-sample features JSON you can reuse downstream. Typical fields include &lt;code&gt;program&lt;/code&gt; (name, format, language, compiler, image_base, size, sha256), &lt;code&gt;functions&lt;/code&gt;, &lt;code&gt;imports&lt;/code&gt;, &lt;code&gt;sections&lt;/code&gt;, &lt;code&gt;strings&lt;/code&gt;, and optional &lt;code&gt;decompiled&lt;/code&gt; function records. For runs, artifacts are written under a run folder (e.g., &lt;code&gt;decompile-&amp;lt;RUN_ID&amp;gt;/&amp;lt;sha256&amp;gt;.features.json&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why only PE binaries (and how to adapt)
&lt;/h3&gt;

&lt;p&gt;For the experiments in this article, I focused on &lt;strong&gt;PE (Portable Executable)&lt;/strong&gt; binaries (&lt;code&gt;.exe&lt;/code&gt;, &lt;code&gt;.dll&lt;/code&gt;, &lt;code&gt;.sys&lt;/code&gt;). Two practical reasons guided this decision:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PE is the most widespread format in desktop malware telemetry (Windows dominance in consumer endpoints).&lt;/li&gt;
&lt;li&gt;Tooling and ecosystem maturity are strongest around PE (Ghidra processors, import table conventions, common packers/obfuscators), which reduces ambiguity when building a baseline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That focus simplified feature extraction (e.g., sections, imports, entry points) and made heuristic authoring more reliable (static vs dynamic context: &lt;a href="https://scholarworks.sjsu.edu/cgi/viewcontent.cgi?article=1488&amp;amp;context=etd_projects" rel="noopener noreferrer"&gt;SJSU ScholarWorks&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Adapting to other formats is feasible with incremental changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ELF (Linux): switch language/processor in Ghidra, adjust extractors for ELF sections/segments, symbol tables, and &lt;code&gt;libc&lt;/code&gt;/syscall imports; re-map heuristics to Linux TTPs (e.g., &lt;code&gt;ptrace&lt;/code&gt;, &lt;code&gt;/proc&lt;/code&gt; tampering, init/systemd persistence).&lt;/li&gt;
&lt;li&gt;Mach-O (macOS): use Mach-O program metadata, dyld imports, code signatures/entitlements; adapt persistence/networking rules to macOS paths and launch agents.&lt;/li&gt;
&lt;li&gt;Android APK/Dex: pivot to bytecode/decompiled Java/Kotlin; extract manifest, permissions, receivers/services; heuristics on exfil domains, trackers, sensitive API calls.&lt;/li&gt;
&lt;li&gt;Scripted binaries (e.g., .NET, Python, JS packagers): add language-aware parsers, focus on runtime loaders, reflection/dynamic resolution, embedded payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concretely, the baseline needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A format detector in the orchestrator to choose the right decompiler/extractor path.&lt;/li&gt;
&lt;li&gt;Format-specific feature schemas with a shared core (program info, strings, imports/exports, sections), plus optional blocks per format.&lt;/li&gt;
&lt;li&gt;A heuristic ruleset per format (or parametric rules) and a tagging map aligned to each platform’s taxonomy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these adaptations, the same pipeline (decompile → heuristics → optional enrichment → fusion → report) extends beyond PE with minimal structural changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Ghidra (and not radare2, IDA, or Ada-based tools)?
&lt;/h3&gt;

&lt;p&gt;A few people ask why I didn’t use Ada or other specialized tools. The answer is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ghidra is fully open source&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;It provides &lt;strong&gt;Python bindings&lt;/strong&gt; through PyGhidra&lt;/li&gt;
&lt;li&gt;It integrates a powerful decompiler&lt;/li&gt;
&lt;li&gt;It can be automated in a pipeline without licensing issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, Ghidra’s Python bindings are &lt;strong&gt;not trivial&lt;/strong&gt; to use. Because Python and Java operate differently (different memory models, threading assumptions, and API expectations), interacting with Ghidra programmatically can become clunky. But it remained the most practical option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limits of the extraction approach
&lt;/h3&gt;

&lt;p&gt;Because this is a lightweight baseline pipeline, the extraction steps are intentionally simple. This leads to a major limitation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The analysis depends heavily on readable strings and predictable patterns. If the malware is encrypted, packed, or obfuscated, the extracted data becomes almost useless.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This constraint shapes everything downstream in the pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Heuristics Engine: How the Rules Work
&lt;/h2&gt;

&lt;p&gt;The heuristics engine is the simplest component of the pipeline by design. A heuristic rule is just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pure function&lt;/li&gt;
&lt;li&gt;That examines extracted features&lt;/li&gt;
&lt;li&gt;And returns structured evidence if a condition is met&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The logic behind the rules is intentionally basic. Most rules rely on &lt;strong&gt;simple string-matching&lt;/strong&gt; or pattern detection, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Suspicious API calls&lt;/li&gt;
&lt;li&gt;Writable/executable sections&lt;/li&gt;
&lt;li&gt;Unusual import patterns&lt;/li&gt;
&lt;li&gt;Indicators in metadata or strings&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A double limitation
&lt;/h3&gt;

&lt;p&gt;Because rules depend on literal string matching:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The input must closely match what the rule expects&lt;/strong&gt;, or the rule will not fire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cryptographed, packed, or obfuscated malware evades the heuristics almost completely.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The upside is interpretability: every rule hit produces clear evidence.&lt;br&gt;
The downside is coverage: many modern malware families will not match at all.&lt;/p&gt;
&lt;h3&gt;
  
  
  Rule shape and evidence contract
&lt;/h3&gt;

&lt;p&gt;Rules are pure functions that take extracted &lt;code&gt;features&lt;/code&gt; and return either &lt;code&gt;Evidence&lt;/code&gt; or a miss reason. In &lt;a href="https://github.com/andremmfaria/rexis" rel="noopener noreferrer"&gt;REXIS&lt;/a&gt; they follow a signature like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rule_example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}):&lt;/span&gt;
 &lt;span class="c1"&gt;# return (Evidence, "reason") on hit, or (None, "miss reason") on miss
&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Evidence is structured with &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;detail&lt;/code&gt;, &lt;code&gt;severity&lt;/code&gt; (&lt;code&gt;info|warn|error&lt;/code&gt;) and a raw &lt;code&gt;score&lt;/code&gt; in [0,1]. The analyser attaches a &lt;code&gt;reason&lt;/code&gt; and per-evidence &lt;code&gt;categories&lt;/code&gt; (derived from a tagging map) to aid traceability.&lt;/p&gt;

&lt;p&gt;Tuning is externalized: a rules config (YAML/JSON) can reweight rules, pass per‑rule params via &lt;code&gt;rule_args&lt;/code&gt;, filter by &lt;code&gt;allow_rules&lt;/code&gt;/&lt;code&gt;deny_rules&lt;/code&gt;, and define &lt;code&gt;label_overrides&lt;/code&gt; for strong signals. Tag inference (e.g., ransomware, stealer, backdoor) is computed from evidence via a configurable &lt;code&gt;tagging&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;Tip: Always return a miss reason; it surfaces in &lt;code&gt;rule_misses&lt;/code&gt; and makes rule calibration easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authoring and wiring a rule (concrete example)
&lt;/h3&gt;

&lt;p&gt;Here is a simplified example that flags mutex creation APIs, showing the recommended return contract and tunable &lt;code&gt;rule_score&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rexis.utils.types&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Evidence&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rexis.tools.heuristics_analyser.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_imports_set&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rule_suspicious_mutex_creation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;rule_score&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Evidence&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
 &lt;span class="n"&gt;imps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_imports_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;mutex_apis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;createmutexa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;createmutexw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openmutexa&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openmutexw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;imps&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;mutex_apis&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no mutex-related imports found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
 &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;Evidence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suspicious_mutex_creation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mutex creation/manipulation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Imports include: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule_score&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matched mutex imports: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To wire it, register the function with a stable id in the analyser’s ruleset and add a default weight in the config. At runtime, you can raise/lower its impact via &lt;code&gt;weights.suspicious_mutex_creation&lt;/code&gt; and pass parameters through &lt;code&gt;rule_args&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tuning via config (weights, thresholds, tags)
&lt;/h3&gt;

&lt;p&gt;In your rules YAML/JSON you can control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scoring.combine&lt;/code&gt; (&lt;code&gt;weighted_sum|max&lt;/code&gt;) and &lt;code&gt;label_thresholds.{malicious,suspicious}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;weights&lt;/code&gt;: per‑rule caps; contribution is &lt;code&gt;min(1.0, ev.score * weight)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allow_rules&lt;/code&gt; / &lt;code&gt;deny_rules&lt;/code&gt;: enable/disable subsets&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;label_overrides&lt;/code&gt;: force a label if a rule fires&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rule_args&lt;/code&gt;: &lt;code&gt;(rule_score, params)&lt;/code&gt; per rule&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tagging&lt;/code&gt;: map evidence to tags (e.g., &lt;code&gt;ransomware&lt;/code&gt;, &lt;code&gt;stealer&lt;/code&gt;, &lt;code&gt;backdoor&lt;/code&gt;) with &lt;code&gt;tag_weights&lt;/code&gt;, &lt;code&gt;threshold&lt;/code&gt;, &lt;code&gt;top_k&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps rule code simple while giving you environment‑specific control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing a rule quickly (ad‑hoc)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rexis.tools.heuristics_analyser.main&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;heuristic_classify&lt;/span&gt;

&lt;span class="n"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;program&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample.exe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x86&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imports&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CreateMutexA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GetProcAddress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;size&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}],&lt;/span&gt;
 &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VirtualBox&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;heuristic_classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;      &lt;span class="c1"&gt;# inspect overall score/label
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;             &lt;span class="c1"&gt;# list of evidence with reasons
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tags&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;                 &lt;span class="c1"&gt;# tag candidates with scores
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rule_misses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;          &lt;span class="c1"&gt;# why a rule didn’t fire
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Enrichment Through External Intelligence (Optional Step)
&lt;/h2&gt;

&lt;p&gt;Enrichment was added only after early experiments revealed a problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The heuristics alone generated output that was “too weak” to stand on its own.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not because the system was flawed, but because simple static heuristics have very limited visibility into modern malware. To counter that, enrichment allows the analyser to pull external data, such as (background: &lt;a href="https://docs.virustotal.com/docs/virustotal-intelligence-introduction" rel="noopener noreferrer"&gt;VirusTotal docs&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash reputation&lt;/li&gt;
&lt;li&gt;Threat vendor classifications&lt;/li&gt;
&lt;li&gt;Historical submissions&lt;/li&gt;
&lt;li&gt;Known malicious families associated with a SHA-256&lt;/li&gt;
&lt;li&gt;Community tags or detection ratios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a baseline to compare the heuristic output against. But enrichment was never meant to override the heuristics, only to contextualize them (what enrichment adds and caveats: &lt;a href="https://www.wiz.io/academy/enrichment-in-threat-intelligence" rel="noopener noreferrer"&gt;Wiz Academy&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why enrichment is useful but imperfect
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It improves confidence &lt;em&gt;when heuristics are directionally correct&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It destabilizes classification &lt;em&gt;when heuristics are very noisy&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It introduces dependency on an external service (API, rate limiting, coverage gaps).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite its imperfections, enrichment helped ground the pipeline’s outputs and made the entire system more meaningful.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Decision Fusion: Combining All Signals
&lt;/h2&gt;

&lt;p&gt;Once both the heuristic engine and the optional enrichment layer produce their outputs, the pipeline needs a final step that decides:&lt;br&gt;
&lt;strong&gt;What is the most reasonable label for this sample?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The decision fusion module combines the available signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Heuristic evidence&lt;/strong&gt; (rule hits, counts, weights)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional enrichment&lt;/strong&gt; (external reputation, vendor labels, known families)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fusion logic uses a simple, weighted approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If heuristics show strong, consistent evidence → they carry more weight.&lt;/li&gt;
&lt;li&gt;If heuristics are weak but enrichment is strong → enrichment influences the decision more.&lt;/li&gt;
&lt;li&gt;If both are weak → the sample defaults to &lt;em&gt;suspicious&lt;/em&gt; or &lt;em&gt;unknown&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;If they strongly disagree → the system emits a warning, and the final label is conservative.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This prevents the analyser from being “overconfident,” which is a real risk when combining noisy static heuristics with external reputation data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Confidence-weighted fusion (with disagreement penalty)
&lt;/h3&gt;

&lt;p&gt;The reconciler computes a final score using per-source confidences and weights:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S_final = clip_01( w_h &lt;em&gt;C_h&lt;/em&gt; S_h + w_vt &lt;em&gt;C_vt&lt;/em&gt; S_vt − penalty(|S_h − S_vt|) )&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;S_h&lt;/code&gt;, &lt;code&gt;S_vt&lt;/code&gt;: heuristics and VT scores in [0,1]&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;C_h&lt;/code&gt;, &lt;code&gt;C_vt&lt;/code&gt;: confidences in &lt;a href="https://dev.toclamped%20floors/ceilings"&gt;0,1&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;w_h&lt;/code&gt;, &lt;code&gt;w_vt&lt;/code&gt;: relative weights&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;penalty(...)&lt;/code&gt;: applied when both signals exist and disagree beyond a policy threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When both sources are high‑confidence yet strongly disagree, a conservative hard‑override can force a mid score and an &lt;code&gt;abstain&lt;/code&gt;/&lt;code&gt;suspicious&lt;/code&gt; label. Final labels are then chosen via calibrated thresholds (e.g., &lt;code&gt;T_mal=0.70&lt;/code&gt;, &lt;code&gt;T_susp=0.40&lt;/code&gt;).&lt;/p&gt;
&lt;h3&gt;
  
  
  The core idea
&lt;/h3&gt;

&lt;p&gt;The fusion layer isn’t meant to be clever, just &lt;strong&gt;balanced&lt;/strong&gt;.&lt;br&gt;
It ensures that neither heuristics nor enrichment dominate blindly, and that the final classification reflects the overall confidence of the system rather than any individual signal.&lt;/p&gt;


&lt;h2&gt;
  
  
  7. Output, Reporting, and Traceability
&lt;/h2&gt;

&lt;p&gt;Every run of the baseline pipeline produces structured output that makes the analysis reproducible and auditable. For each sample, the system stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extracted features&lt;/li&gt;
&lt;li&gt;All heuristic rule evidence&lt;/li&gt;
&lt;li&gt;Optional enrichment results&lt;/li&gt;
&lt;li&gt;The fused classification label&lt;/li&gt;
&lt;li&gt;Metadata (hashes, timestamps, config parameters)&lt;/li&gt;
&lt;li&gt;A JSON report representing the entire reasoning chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This traceability was crucial for the thesis.&lt;br&gt;
It allowed me to re-run experiments, refine rules, compare outputs, and understand how every decision was made. When you are building an analyser from scratch, having visibility into &lt;em&gt;why&lt;/em&gt; something happened is as important as the result itself.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why reporting matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;It makes the pipeline reproducible.&lt;/li&gt;
&lt;li&gt;It allows for manual inspection when results are unclear.&lt;/li&gt;
&lt;li&gt;It provides ground truth for later LLM/RAG experiments.&lt;/li&gt;
&lt;li&gt;It helps identify weak rules, noisy features, or misaligned fusion logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reporting layer ended up being one of the most valuable parts of the pipeline, even though it was initially treated as a simple output function.&lt;/p&gt;
&lt;h3&gt;
  
  
  Concrete artifact paths
&lt;/h3&gt;

&lt;p&gt;For a run directory like &lt;code&gt;baseline-analysis-&amp;lt;RUN_ID&amp;gt;/&lt;/code&gt;, you’ll typically see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Features: &lt;code&gt;decompile-&amp;lt;RUN_ID&amp;gt;/&amp;lt;sha256&amp;gt;.features.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Heuristics: &lt;code&gt;&amp;lt;sha256&amp;gt;.baseline.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Final report (fusion): &lt;code&gt;&amp;lt;sha256&amp;gt;.report.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Batch runs: &lt;code&gt;baseline_summary.json&lt;/code&gt; plus a per‑run &lt;code&gt;baseline-analysis-&amp;lt;RUN_ID&amp;gt;.report.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  8. Lessons Learned from Building a Static Malware Analyser
&lt;/h2&gt;

&lt;p&gt;Building a malware analyser, even a simple baseline one, teaches you a lot about both malware &lt;em&gt;and&lt;/em&gt; tooling. A few reflections stood out.&lt;/p&gt;
&lt;h3&gt;
  
  
  What worked well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The architecture was clear, modular, and easy to extend.&lt;/li&gt;
&lt;li&gt;The rule engine was transparent and interpretable.&lt;/li&gt;
&lt;li&gt;The pipeline could analyse large sets of files quickly.&lt;/li&gt;
&lt;li&gt;It established a solid foundation for later ML and LLM-based experiments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What didn’t work as well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Static analysis alone struggles with packed or cryptographed malware (see recent studies: &lt;a href="https://www.sciencedirect.com/science/article/pii/S2772918424000389" rel="noopener noreferrer"&gt;ScienceDirect&lt;/a&gt;, &lt;a href="https://www.mdpi.com/2624-800X/5/4/98" rel="noopener noreferrer"&gt;MDPI&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The heuristic engine is only as good as the extracted strings and it often isn’t enough.&lt;/li&gt;
&lt;li&gt;Simple string matching has obvious limits in modern malware ecosystems.&lt;/li&gt;
&lt;li&gt;Enrichment, while useful, can distort results when heuristics are too weak.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What surprised me
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;How quickly the heuristics break when input patterns change.&lt;/li&gt;
&lt;li&gt;How hard it is to design “general” rules that work across many families.&lt;/li&gt;
&lt;li&gt;How often malware authors rely on simple tricks that defeat static inspection.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How this shaped the next phase of the thesis
&lt;/h3&gt;

&lt;p&gt;These lessons directly informed the development of the &lt;strong&gt;LLM + RAG-enhanced pipeline&lt;/strong&gt; (which will be covered on a dedicated article).&lt;br&gt;
Static heuristics gave me structure, data, and understanding. But not enough depth.&lt;br&gt;
The next logical step was to use LLMs to interpret extracted features more flexibly, grounded by retrieval to avoid hallucinations.&lt;/p&gt;

&lt;p&gt;The baseline pipeline provided the &lt;em&gt;scaffolding&lt;/em&gt; needed to move forward.&lt;/p&gt;
&lt;h3&gt;
  
  
  Analysis Results &amp;amp; Repository Structure
&lt;/h3&gt;

&lt;p&gt;The complete artefacts from my experiments live in the repository under &lt;code&gt;analysis/&lt;/code&gt;. It has two main branches of outputs and a simple aggregate:&lt;/p&gt;

&lt;p&gt;Note: &lt;strong&gt;The LLM + RAG pipeline is only referenced here for structure and comparison; I’ll cover its design, prompts, retrieval strategy, and results in a dedicated follow‑up article.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;analysis/baseline/&lt;/code&gt;: results from the baseline static pipeline (with and without VirusTotal enrichment) (&lt;a href="https://github.com/andremmfaria/rexis/tree/main/analysis/baseline" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;analysis/llmrag/&lt;/code&gt;: results from the LLM + RAG pipeline (&lt;a href="https://github.com/andremmfaria/rexis/tree/main/analysis/llmrag" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;analysis/aggregation-output.json&lt;/code&gt; and &lt;code&gt;analysis/aggregation-report.csv&lt;/code&gt;: quick roll‑ups of the per‑run outputs (&lt;a href="https://github.com/andremmfaria/rexis/tree/main/analysis" rel="noopener noreferrer"&gt;link&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Directory layout (overview)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;analysis/baseline/baseline-analysis-&amp;lt;family&amp;gt;-run-2508/&lt;/code&gt;: baseline runs per family (e.g., botnet, ransomware, rootkit, trojan) (&lt;a href="https://github.com/andremmfaria/rexis/tree/main/analysis/baseline" rel="noopener noreferrer"&gt;examples&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;analysis/baseline/baseline-analysis-&amp;lt;family&amp;gt;-run-vt-2508/&lt;/code&gt;: same families with VirusTotal enrichment enabled (&lt;a href="https://github.com/andremmfaria/rexis/tree/main/analysis/baseline" rel="noopener noreferrer"&gt;examples&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;analysis/llmrag/llmrag-analysis-&amp;lt;family&amp;gt;-run-2508/&lt;/code&gt;: LLM + RAG runs per family (&lt;a href="https://github.com/andremmfaria/rexis/tree/main/analysis/llmrag" rel="noopener noreferrer"&gt;examples&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside each run directory you’ll find the per‑sample artefacts described earlier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;decompile-&amp;lt;RUN_ID&amp;gt;/&amp;lt;sha256&amp;gt;.features.json&lt;/code&gt;: extracted features&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;sha256&amp;gt;.baseline.json&lt;/code&gt;: heuristics output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;sha256&amp;gt;.report.json&lt;/code&gt;: fused final report (label, score, trace)S&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baseline-analysis-&amp;lt;RUN_ID&amp;gt;.report.json&lt;/code&gt;: batch‑level summary for the run&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;baseline_summary.json&lt;/code&gt;: compact summary across all processed samples&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Baseline analysis: what the runs show
&lt;/h3&gt;

&lt;p&gt;Across the baseline folders, you can inspect how the simple heuristics behave for different malware families and how optional VirusTotal enrichment shifts confidence and labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without enrichment (&lt;code&gt;baseline-analysis-&amp;lt;family&amp;gt;-run-2508/&lt;/code&gt;), evidence is driven purely by structural/string‑based signals; many samples land in &lt;code&gt;suspicious&lt;/code&gt; or &lt;code&gt;unknown&lt;/code&gt; when strings are sparse or obfuscated.&lt;/li&gt;
&lt;li&gt;With enrichment (&lt;code&gt;baseline-analysis-&amp;lt;family&amp;gt;-run-vt-2508/&lt;/code&gt;), labels tend to stabilize when external reputation aligns with the heuristics; disagreement cases are explicitly noted in the fused reports via the reconciliation policy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a quick, high‑level view across runs, open &lt;code&gt;analysis/aggregation-report.csv&lt;/code&gt; or the machine‑readable &lt;code&gt;analysis/aggregation-output.json&lt;/code&gt;. These aggregate files summarize per‑run counts and label distributions without having to traverse each directory.&lt;/p&gt;

&lt;p&gt;If you want to reproduce similar outputs, run the commands in Section 9 and point &lt;code&gt;-o&lt;/code&gt; to a top‑level &lt;code&gt;analysis/&lt;/code&gt; directory; the pipeline will create run‑specific folders and the same artefact structure.&lt;/p&gt;


&lt;h2&gt;
  
  
  9. How to Run It Yourself
&lt;/h2&gt;

&lt;p&gt;The analyser is open-source and can be run with only a few prerequisites:&lt;/p&gt;
&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python environment (follow the &lt;a href="https://github.com/andremmfaria/rexis/blob/main/README.md#%EF%B8%8F-installation--setup" rel="noopener noreferrer"&gt;installation setup on the repository's README.md&lt;/a&gt; for setting up the environment)&lt;/li&gt;
&lt;li&gt;Ghidra + PyGhidra (Ghidra installed at &lt;code&gt;/opt/ghidra&lt;/code&gt; on Linux). If you need a fast, distro‑agnostic setup, follow my guide: &lt;a href="https://dev.to/andremmfaria/ghidra-on-linux-zero-fuss-install-1b07"&gt;Ghidra on Linux: Zero Fuss Install&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A directory of PE files&lt;/li&gt;
&lt;li&gt;(Optional) VirusTotal API key for enrichment (set &lt;code&gt;[baseline].virus_total_api_key&lt;/code&gt; in &lt;code&gt;config/settings.toml&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Basic usage
&lt;/h3&gt;

&lt;p&gt;Once installed, running the baseline pipeline is straightforward (Typer CLI):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdm run rexis analyse baseline &lt;span class="nt"&gt;-i&lt;/span&gt; ./data/samples/&amp;lt;file&amp;gt;.exe &lt;span class="nt"&gt;-o&lt;/span&gt; ./data/analysis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or for batch mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdm run rexis analyse baseline &lt;span class="nt"&gt;-i&lt;/span&gt; ./data/samples &lt;span class="nt"&gt;-o&lt;/span&gt; ./data/analysis &lt;span class="nt"&gt;--parallel&lt;/span&gt; 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-i, --input&lt;/code&gt;: file or directory to analyse (required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o, --out-dir&lt;/code&gt;: output directory (defaults to CWD)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-r, --run-name&lt;/code&gt;: logical run name (default: UUID)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-y, --overwrite&lt;/code&gt;: overwrite existing artifacts&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p, --parallel&lt;/code&gt;: workers for directory mode&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--rules&lt;/code&gt;: path to heuristics rules config (YAML/JSON)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-m, --min-severity&lt;/code&gt;: filter returned evidence (&lt;code&gt;info|warn|error&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--vt&lt;/code&gt;: enable VirusTotal enrichment (requires API key in &lt;code&gt;config/settings.toml&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--vt-timeout&lt;/code&gt;, &lt;code&gt;--vt-qpm&lt;/code&gt;: timeout and queries-per-minute budget&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rule customization
&lt;/h3&gt;

&lt;p&gt;Users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new heuristic rules&lt;/li&gt;
&lt;li&gt;Tune weights and thresholds&lt;/li&gt;
&lt;li&gt;Enable or disable individual rules&lt;/li&gt;
&lt;li&gt;Adjust fusion parameters&lt;/li&gt;
&lt;li&gt;Add their own enrichment sources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where to start
&lt;/h3&gt;

&lt;p&gt;All documentation is available in the repository:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Baseline pipeline guide: &lt;a href="https://github.com/andremmfaria/rexis/blob/main/guides/BaselinePipeline.md" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/rexis/blob/main/guides/BaselinePipeline.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Heuristic rule‑writing guide: &lt;a href="https://github.com/andremmfaria/rexis/blob/main/guides/WritingHeuristicRules.md" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/rexis/blob/main/guides/WritingHeuristicRules.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Reconciliation (fusion) details: &lt;a href="https://github.com/andremmfaria/rexis/blob/main/guides/Reconciliation.md" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/rexis/blob/main/guides/Reconciliation.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example configurations and sample reports in the repo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it easy to experiment, modify, or build your own extensions.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Conclusion: Why Building Tools Is the Best Way to Learn
&lt;/h2&gt;

&lt;p&gt;I started this project because I wanted to understand how malware classification works.&lt;br&gt;
Building my own analyser forced me to confront all the assumptions, shortcuts, limitations, and edge cases that textbooks and blog posts never mention.&lt;/p&gt;

&lt;p&gt;What I gained was not just a working pipeline, but a practical understanding of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how static analysis actually behaves&lt;/li&gt;
&lt;li&gt;where heuristics break&lt;/li&gt;
&lt;li&gt;why enrichment matters&lt;/li&gt;
&lt;li&gt;how evidence should be combined&lt;/li&gt;
&lt;li&gt;and how analysts think about classification&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The baseline pipeline is not perfect. It was never meant to be.&lt;br&gt;
But it gave me the foundation I needed to build more advanced approaches, including the LLM + RAG pipeline that became the core of the second half of my thesis. This will be covered in a future article.&lt;/p&gt;

&lt;p&gt;Most importantly, it taught me this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If you want to learn how something works, build a tool that does it.&lt;br&gt;
You’ll understand the entire problem far more deeply.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>malware</category>
      <category>security</category>
      <category>staticanalysis</category>
    </item>
    <item>
      <title>Building a Transparent LAGG (LACP) Bridge with OPNsense, UDM, and UniFi — A Practical Guide</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Mon, 01 Dec 2025 02:52:40 +0000</pubDate>
      <link>https://forem.com/andremmfaria/building-a-transparent-lagg-lacp-bridge-with-opnsense-udm-and-unifi-a-practical-guide-1d21</link>
      <guid>https://forem.com/andremmfaria/building-a-transparent-lagg-lacp-bridge-with-opnsense-udm-and-unifi-a-practical-guide-1d21</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;I have a pretty heavy network topology at my home. The result of years of IoT devices, user devices, servers and miscellaneous IP enabled things. Those pose some security threats that I longed to correct or, at the very least, monitor. So I had this idea to place a transparent firewall between my UniFi Dream Machine (UDM) and the rest of my network.&lt;/p&gt;

&lt;p&gt;I wanted to do this without replacing/reconfiguring the UDM, without breaking VLANs, without touching DNS/DHCP or anything else other than what was really necessary. The goal was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;perform minimal changes&lt;/li&gt;
&lt;li&gt;stay fully transparent to the network&lt;/li&gt;
&lt;li&gt;add filtering/inspection intelligence&lt;/li&gt;
&lt;li&gt;do it using &lt;strong&gt;LAGG (LACP)&lt;/strong&gt; to increase throughput (and because it is cool).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article documents how I built a &lt;strong&gt;Layer 2–only transparent bridge&lt;/strong&gt; using OPNsense with two aggregated links toward the UDM and two aggregated links toward my UniFi switch. It also covers the mistakes I made so you don’t repeat them.&lt;/p&gt;

&lt;p&gt;To set the stage, here is the physical setup as it exists today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091640053.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091640053.jpg" alt="Overall setup with the UDM, OPNsense box, and UniFi switch" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the top shelf we have, from left to right, a pod charger for xbox controller batteries, the USW-16 we will be using for this guide, the BACKUP bay and a small application host. And on the bottom shelf we got the OPNSense box and the UDM.&lt;/p&gt;

&lt;p&gt;That host and the BACKUP bay are part of a &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt; installation I did with their &lt;a href="https://developers.home-assistant.io/docs/operating-system/" rel="noopener noreferrer"&gt;HAOS system&lt;/a&gt;. If you'd like to know more about it let me know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;p&gt;I followed these EXCELLENT guides in order to do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Video:&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;How to Configure LAG-LACP&lt;/em&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/watch?v=Rb4vlN_Hf-U" rel="noopener noreferrer"&gt;https://www.youtube.com/watch?v=Rb4vlN_Hf-U&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Article:&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Configure LAG/LACP on SFP Ports (TP-Link Example)&lt;/em&gt;&lt;br&gt;
&lt;a href="https://homenetworkguy.com/how-to/configure-lag-lacp-on-sfp-ports-two-tp-link-switches-with-vlans/" rel="noopener noreferrer"&gt;https://homenetworkguy.com/how-to/configure-lag-lacp-on-sfp-ports-two-tp-link-switches-with-vlans/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Network Topology
&lt;/h2&gt;

&lt;p&gt;This entire design operates strictly at &lt;strong&gt;Layer 2&lt;/strong&gt;. OPNsense is merely a bump-in-the-wire — it does not route, it does not NAT, and it does not participate in DHCP or DNS.&lt;/p&gt;

&lt;p&gt;Those services remain entirely on the UDM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UDM:&lt;/strong&gt; DNS, DHCP, routing, VLANs, firewall&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OPNsense:&lt;/strong&gt; Transparent inline bridge with LACP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;USW:&lt;/strong&gt; Downstream switching with LACP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Traffic Flow
&lt;/h3&gt;

&lt;p&gt;UDM → ingresslagg → laggbridge → egresslagg → UniFi Switch&lt;/p&gt;

&lt;p&gt;Because the bridge sits inline and has no IP addresses assigned to it, the rest of the network behaves normally. All VLAN tags pass through untouched, and the UDM continues to see the whole network exactly as before.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. System Setup (Hardware &amp;amp; Software Overview)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Hardware Origin
&lt;/h3&gt;

&lt;p&gt;This OPNsense box came to life after my old laptop suddenly died. Instead of throwing away perfectly good components, I salvaged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;32 GB DDR4 RAM&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;two SSDs&lt;/strong&gt; I later mirrored as RAID-1,&lt;/li&gt;
&lt;li&gt;and the &lt;strong&gt;Wi-Fi card&lt;/strong&gt;,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and reused them inside a &lt;strong&gt;barebones mini-PC&lt;/strong&gt; I purchased from &lt;a href="https://www.amazon.co.uk/dp/B0BJQ1LX28" rel="noopener noreferrer"&gt;Amazon UK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The chassis ships without RAM or storage, making it ideal for a rebuild using recycled parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Specs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CPU:&lt;/strong&gt; Intel® Celeron® J6413 @ 1.80 GHz (4 cores / 4 threads)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAM:&lt;/strong&gt; 32 GB DDR4-2166&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; 128 GB SSD (RAID-1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NICs:&lt;/strong&gt; 6 × Intel 1G&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS:&lt;/strong&gt; OPNsense &lt;strong&gt;25.7.5-amd64&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here is the firewall itself, with all six Ethernet interfaces populated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091707592.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091707592.jpg" alt="OPNsense box with all NICs connected" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Why Use LAGG in Transparent Mode?
&lt;/h2&gt;

&lt;p&gt;I chose a transparent firewall because I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;redundancy&lt;/strong&gt; — two links on each side, loss-tolerant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;throughput headroom&lt;/strong&gt; — LACP distributes sessions across NICs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;zero impact on existing network design&lt;/strong&gt; — no routing changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;full compatibility with UniFi&lt;/strong&gt; — VLANs, DHCP, and DNS remain untouched&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layer 2 Only
&lt;/h3&gt;

&lt;p&gt;This cannot be overstated:&lt;br&gt;
&lt;strong&gt;OPNsense in this setup is 100% Layer 2.&lt;/strong&gt;&lt;br&gt;
It is not acting as a router, DHCP server, DNS resolver, or gateway.&lt;/p&gt;

&lt;p&gt;It simply passes frames, while optionally filtering or inspecting them inline.&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Configuring LAGG on OPNsense
&lt;/h2&gt;

&lt;p&gt;On the OpnSense management web interface go to &lt;strong&gt;Interfaces → Devices → LAGG&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There, two LAGGs are created:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;LAGG&lt;/th&gt;
&lt;th&gt;Interface&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Members&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ingresslagg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lagg0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;toward UDM&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;igc1&lt;/code&gt;, &lt;code&gt;igc2&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;egresslagg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lagg1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;toward UniFi Switch&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;igc4&lt;/code&gt;, &lt;code&gt;igc5&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Click the "+" sign on the far right side of the table in the interface to add an entry. Repeat this for both entries on the table above.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_dMHnmsUE5S.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_dMHnmsUE5S.png" alt="Create lagg device" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Physical interface 1&lt;/li&gt;
&lt;li&gt;Physical interface 2&lt;/li&gt;
&lt;li&gt;Hash layer set for L2. Following the bump-in-the-wire approach.&lt;/li&gt;
&lt;li&gt;Provide a meaningful description. Believe me, you will need it later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both LAGGs are configured in &lt;strong&gt;LACP&lt;/strong&gt; mode. You can select more than 2 interfaces here, it only depends on how much interfaces you have. I only had 6 so in order to have a management interface (outside of the scope of this guide) i opted to have it be 2x2 (2 interfaces in and 2 interfaces out).&lt;/p&gt;

&lt;p&gt;After this is set, it will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_G9nOq4XmwG.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_G9nOq4XmwG.png" alt="LAGG devices" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click apply.&lt;/p&gt;

&lt;p&gt;Next, we need to create an interface from the newly created devices. Go to &lt;strong&gt;Interfaces → Assignments&lt;/strong&gt; and assign a new interface to each of the devices on the &lt;em&gt;Assign a new interface&lt;/em&gt; menu. Remember to provide a name in the description field. That will be the interface names.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_kD6nuMSFyT.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_kD6nuMSFyT.png" alt="Assign new interface" width="747" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have &lt;strong&gt;lagg-test&lt;/strong&gt; and &lt;strong&gt;lagtest&lt;/strong&gt; but you can pretend that is either &lt;strong&gt;ingress-lagg/egress-lagg&lt;/strong&gt; and &lt;strong&gt;ingresslagg/egresslagg&lt;/strong&gt;. Click &lt;em&gt;add&lt;/em&gt; to create it on the interfaces table like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_C6ITcEW0KS.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_C6ITcEW0KS.png" alt="Interface assignments" width="800" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to add both interfaces to the bridge configuration. Go to &lt;strong&gt;Interfaces → Devices → Bridge&lt;/strong&gt; and click the "+" sign on the far right side of the table in the interface to add an entry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_xpq9Rlv8C8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_xpq9Rlv8C8.png" alt="Bridge device" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;1st LAGG interface&lt;/li&gt;
&lt;li&gt;2nd LAGG interface&lt;/li&gt;
&lt;li&gt;GOOD description of what this device is.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click &lt;em&gt;Save&lt;/em&gt; to add it to the bridge interface table. From here, the newly created LAGG interfaces are added to a single bridge device. This device needs to be assigned to an interface by repeating the process we did for the lagg interface creation. After this is done, we have something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_G8LEI9d9Dd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_G8LEI9d9Dd.png" alt="Bridge Interface" width="800" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After all of this is done, we now have &lt;strong&gt;&lt;code&gt;laggbridge&lt;/code&gt; = ingresslagg + egresslagg&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now, we enable the interfaces. After this is done, under the &lt;strong&gt;Interfaces&lt;/strong&gt; menu, there should appear all interfaces we created (i.e. &lt;strong&gt;laggbridge&lt;/strong&gt;, &lt;strong&gt;ingresslagg&lt;/strong&gt;, &lt;strong&gt;egresslagg&lt;/strong&gt;). Enable them all by clicking on each of the interface names under the &lt;strong&gt;Interfaces&lt;/strong&gt; menu, check the &lt;em&gt;Enable&lt;/em&gt; checkbox and click &lt;em&gt;Save&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Management and Services Interfaces
&lt;/h3&gt;

&lt;p&gt;I host some services on this OPNSense box like DynamicDNS and AdGuard. The installation/management of those are outside of the scope of this article, but if you would like for me to write something about those, let me know.&lt;/p&gt;

&lt;p&gt;These are independent interfaces I set with fixed IPs (on the UDM side). They stay &lt;strong&gt;outside&lt;/strong&gt; of the bridge and ensure I can always reach OPNsense even if the bridge goes offline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;management&lt;/code&gt; (&lt;code&gt;igc3&lt;/code&gt;) → 192.168.1.x/24&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;services&lt;/code&gt; (&lt;code&gt;igc0&lt;/code&gt;) → 192.168.1.y/24&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  ⚠️ Common mistakes to avoid
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don’t assign IPs to lagg0, lagg1, or the bridge:&lt;/strong&gt; Assigning IPs would make the bridge participate in Layer 3 (routing), breaking its transparent Layer 2 operation. This could disrupt network traffic, cause routing conflicts, and interfere with VLANs and DHCP.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don’t enable the physical NICs individually, only the LAGGs:&lt;/strong&gt; Enabling NICs outside the LAGG group can cause duplicate connections, loops, or flapping, as the LAGG protocol expects to manage all member interfaces. This can destabilize the link aggregation and the bridge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don’t mix LACP Active/Passive between devices:&lt;/strong&gt; LACP requires both ends to be in compatible modes (usually Active). Mixing Active and Passive can prevent proper negotiation, causing the aggregated link to fail or operate unreliably.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don’t skip a reboot, LAGG + bridge changes apply more cleanly afterward:&lt;/strong&gt; Network interface and bridge changes may not fully apply until after a reboot. Skipping this step can leave the system in a partially configured state, leading to unpredictable behavior or connectivity issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  6. Configuring LACP on the UDM (Ingress Side)
&lt;/h2&gt;

&lt;p&gt;The UDM handles the WAN, DHCP, DNS, routing, and VLAN assignments. In this setup, we only need it to expose &lt;strong&gt;two LACP ports&lt;/strong&gt; toward the OPNsense box.&lt;/p&gt;

&lt;p&gt;On the updated UniFi interface:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Profiles → Switch Ports&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Create a new &lt;strong&gt;Aggregate&lt;/strong&gt; profile&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;LACP Mode: Active&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Apply this profile to &lt;strong&gt;ports 7 and 8&lt;/strong&gt; (or whichever pair you use)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Follow this very useful guide from &lt;a href="https://support.hostifi.com/en/" rel="noopener noreferrer"&gt;Hostify&lt;/a&gt; if anything is not clear:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://support.hostifi.com/en/articles/6454249-unifi-how-to-enable-link-aggregation-on-switches-lag" rel="noopener noreferrer"&gt;https://support.hostifi.com/en/articles/6454249-unifi-how-to-enable-link-aggregation-on-switches-lag&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Only after the profile is in place should you plug in the cables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091658367.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091658367.jpg" alt="UDM with WAN + two LACP ports (7 and 8) connected" width="800" height="1422"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚠️ Don’t #1 — Don’t plug cables in before creating the LACP group
&lt;/h3&gt;

&lt;p&gt;UniFi auto-profiling may assign them to something else (like LAN, PoE, or VLAN-only), breaking negotiation.&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚠️ Don’t #2 — Don’t assume LACP comes up instantly
&lt;/h3&gt;

&lt;p&gt;Give it &lt;strong&gt;5–10 seconds&lt;/strong&gt; to negotiate.&lt;br&gt;
If one side is up and the other is not, it will flap.&lt;/p&gt;


&lt;h2&gt;
  
  
  7. Configuring LACP on the UniFi Switch (Egress Side)
&lt;/h2&gt;

&lt;p&gt;On the USW-16, the process is similar:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open UniFi Controller → Devices → USW&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;ports 7 and 8&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Aggregate&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;LACP Active&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Save and wait for synchronization&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These two ports will form the downstream side of the bridge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091720219.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2FPXL_20251119_091720219.jpg" alt="UniFi switch with aggregated ports 7 and 8 plus the rest of the network devices" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚠️ Don’t #3 — Don’t mix up NIC-to-port mapping
&lt;/h3&gt;

&lt;p&gt;Label the NICs and cables before you start.&lt;br&gt;
One swapped cable is enough to break the bundle or cause intermittent LACP flaps.&lt;/p&gt;


&lt;h2&gt;
  
  
  8. Testing the Transparent LAGG Bridge
&lt;/h2&gt;

&lt;p&gt;Once everything is connected, verify the bridge and both LAGGs:&lt;/p&gt;
&lt;h3&gt;
  
  
  On OPNsense
&lt;/h3&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ifconfig lagg0
ifconfig lagg1
ifconfig laggbridge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see all LACP members in &lt;strong&gt;ACTIVE&lt;/strong&gt; state.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the UniFi Controller
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;UDM ports 7/8 should show &lt;strong&gt;Aggregate — Active&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;USW ports 7/8 should show &lt;strong&gt;Aggregate — Active&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No errors, no flapping, no “Blocking” states&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Optional: Throughput Testing
&lt;/h3&gt;

&lt;p&gt;You can run an &lt;code&gt;iperf3&lt;/code&gt; test from a LAN device to something outside the bridge. Traffic should flow, no bottleneck should be observed and failover should work if you temporarily unplug one cable from each LAGG&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠️ Don’t #4 — Don’t enable hardware offloading prematurely
&lt;/h3&gt;

&lt;p&gt;Keep the following &lt;strong&gt;disabled&lt;/strong&gt; until you verify stable operation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TSO&lt;/li&gt;
&lt;li&gt;LRO&lt;/li&gt;
&lt;li&gt;Checksum offload&lt;/li&gt;
&lt;li&gt;VLAN offload&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some Intel i225/i226 NICs misbehave with offloading in bridge mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Here are the most common issues — and their causes:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Issue: LAGG fails to negotiate&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;UDM or USW set to Passive instead of Active&lt;/li&gt;
&lt;li&gt;Cables on the wrong ports&lt;/li&gt;
&lt;li&gt;One side Active, the other Disabled&lt;/li&gt;
&lt;li&gt;An interface accidentally assigned directly instead of via LAGG&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Issue: Bridge appears up but traffic drops&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Hardware offloading left enabled&lt;/li&gt;
&lt;li&gt;A NIC is flapping or mismatched&lt;/li&gt;
&lt;li&gt;Member NIC enabled individually by mistake&lt;/li&gt;
&lt;li&gt;Duplicate MAC confusion (if using the same NIC model)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Issue: Network meltdown (loop)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the most catastrophic one.&lt;/p&gt;

&lt;p&gt;It happens when something like this occurs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UDM → OPNsense → USW
 ↑────────────────↓
  accidental loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single extra cable or an unconfigured LAGG member can create a broadcast storm.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚠️ Don’t #5 — Don’t create a second path between UDM and USW
&lt;/h3&gt;

&lt;p&gt;There must be &lt;strong&gt;exactly one&lt;/strong&gt; traffic path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UDM → ingresslagg → laggbridge → egresslagg → USW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more, no less.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Final Working Architecture
&lt;/h2&gt;

&lt;p&gt;Once configured, the final architecture looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          WAN
           │
        [ UDM ] Ports 7+8 (LACP Active)
          │  │
          ╰╮╭╯
          ingresslagg (lagg0)
           │
          [ laggbridge ]
           │
          egresslagg (lagg1)
          ╭╯╰╮
          │  │
        [ UniFi USW ] Ports 7+8 (LACP Active)
           │
         rest of LAN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fully &lt;strong&gt;transparent Layer 2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No IPs on LAGGs or the bridge&lt;/li&gt;
&lt;li&gt;UDM retains all core services (DHCP, DNS, routing, VLANs)&lt;/li&gt;
&lt;li&gt;Redundant links on both sides&lt;/li&gt;
&lt;li&gt;Clean inline filtering option for OPNsense&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Conclusion and results
&lt;/h2&gt;

&lt;p&gt;Building a transparent bridge with LAGG on both sides is a great way to introduce a firewall into your network without redesigning the entire topology. OPNsense, combined with UniFi hardware, handles this setup surprisingly well — once everything is wired and configured correctly.&lt;/p&gt;

&lt;p&gt;Along the way, I learned (the hard way) that LACP is extremely sensitive to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mismatched modes&lt;/li&gt;
&lt;li&gt;cable swaps&lt;/li&gt;
&lt;li&gt;auto-profiling quirks&lt;/li&gt;
&lt;li&gt;enabling NICs individually&lt;/li&gt;
&lt;li&gt;and forgetting to disable hardware offloading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But once the pieces fall into place, the result is a rock-solid, redundant inline bridge that just works.&lt;/p&gt;

&lt;p&gt;With this setup you are able to see and/or filter EVERYTHING that is happening on your network (at least what passes through the bridge). You can set firewall rules for traffic that passes through the bridge and see cool graphs like this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_kszk5m5F4y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FTransparent%2520LAGG%2520%28LACP%29%2520Bridge%2520with%2520OPNsense%2C%2520UDM%2C%2520and%2520UniFi%2520%25E2%2580%2594%2520A%2520Practical%2520Guide%2Ffirefox_kszk5m5F4y.png" alt="Cool graph" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup has a lot that can be improved upon. I am open for feedback and would love ideas on how to make this better. Leave a comment below and tell me what you think. Thank you!!!&lt;/p&gt;

</description>
      <category>opnsense</category>
      <category>networking</category>
      <category>homelab</category>
    </item>
    <item>
      <title>Ghidra on Linux Zero Fuss Install</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Tue, 11 Nov 2025 00:04:13 +0000</pubDate>
      <link>https://forem.com/andremmfaria/ghidra-on-linux-zero-fuss-install-1b07</link>
      <guid>https://forem.com/andremmfaria/ghidra-on-linux-zero-fuss-install-1b07</guid>
      <description>&lt;p&gt;A straightforward, distro-agnostic way to install Ghidra under /opt without touching your system Java alternatives, plus environment wiring that makes upgrades safe and re-runs idempotent.&lt;/p&gt;

&lt;p&gt;Disclaimer:&lt;br&gt;
This post assumes you’re comfortable using the terminal with sudo, have basic knowledge of environment variables, and can install a few command‑line tools (wget, unzip, tar) with your distro’s package manager.&lt;/p&gt;
&lt;h2&gt;
  
  
  What you’ll get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ghidra 11.4.1 installed under &lt;code&gt;/opt/ghidra&lt;/code&gt; (atomic replace with backups on re-run)&lt;/li&gt;
&lt;li&gt;A local Temurin (Adoptium) JDK 21 under &lt;code&gt;/opt/java-temurin&lt;/code&gt; without touching system alternatives&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GHIDRA_INSTALL_DIR&lt;/code&gt; and &lt;code&gt;GHIDRA_JAVA_HOME&lt;/code&gt; added to your shell RC (&lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A process that is safe to re-run for upgrades and repairs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: The approach works across common Linux distributions. The automated script tries to install helper tools via &lt;code&gt;apt&lt;/code&gt; when available, but it gracefully skips on other distros—so on RPM/Arch-based systems, just install &lt;code&gt;wget&lt;/code&gt;, &lt;code&gt;unzip&lt;/code&gt;, and &lt;code&gt;tar&lt;/code&gt; via your package manager first.&lt;/p&gt;

&lt;p&gt;Customization tip:&lt;br&gt;
You can change the Ghidra version, release date, download URL, or even inject checksum verification simply by editing the constants at the top of the referenced script (&lt;code&gt;GHIDRA_VERSION&lt;/code&gt;, &lt;code&gt;GHIDRA_DATE&lt;/code&gt;, &lt;code&gt;GHIDRA_URL&lt;/code&gt;, &lt;code&gt;GHIDRA_SHA256&lt;/code&gt;, plus the Java bits like &lt;code&gt;TEMURIN_MAJOR&lt;/code&gt;, &lt;code&gt;TEMURIN_API_URL&lt;/code&gt;, &lt;code&gt;TEMURIN_SHA256&lt;/code&gt;). Adjust them, re-run the script, and it will perform an atomic upgrade while backing up previous installs.&lt;/p&gt;
&lt;h2&gt;
  
  
  1) Prerequisites
&lt;/h2&gt;

&lt;p&gt;Install the few tools we’ll use to fetch and unpack files.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debian/Ubuntu:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install -y wget unzip tar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Fedora/RHEL/CentOS:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sudo dnf install -y wget unzip tar&lt;/code&gt; (or &lt;code&gt;yum&lt;/code&gt; on older releases)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Arch/Manjaro:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo pacman -S --needed wget unzip tar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also ensure you have sudo privileges.&lt;/p&gt;
&lt;h2&gt;
  
  
  2) Install a private Temurin JDK 21 under /opt
&lt;/h2&gt;

&lt;p&gt;Why: Ghidra runs best on a modern LTS JDK. Installing a local Temurin JDK avoids messing with system-wide Java alternatives and keeps Ghidra isolated.&lt;/p&gt;

&lt;p&gt;Key details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Location: &lt;code&gt;/opt/java-temurin/&amp;lt;jdk-version&amp;gt;&lt;/code&gt; with a stable symlink at &lt;code&gt;/opt/java-temurin/current&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://api.adoptium.net/" rel="noopener noreferrer"&gt;Adoptium API&lt;/a&gt; for the latest GA JDK 21 (linux x64, HotSpot) — or browse &lt;a href="https://adoptium.net/temurin/releases" rel="noopener noreferrer"&gt;Temurin releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Optional integrity: You can verify checksums if you provide &lt;code&gt;TEMURIN_SHA256&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Steps (high level):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the base directory: &lt;code&gt;/opt/java-temurin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Download the latest GA tarball for JDK 21 from Adoptium’s API&lt;/li&gt;
&lt;li&gt;(Optional) Verify the tarball with &lt;code&gt;sha256sum&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Extract to a versioned folder under &lt;code&gt;/opt/java-temurin&lt;/code&gt; and update the &lt;code&gt;current&lt;/code&gt; symlink atomically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On re-run, the previous version is backed up and replaced. The stable &lt;code&gt;current&lt;/code&gt; symlink is what we’ll point Ghidra to.&lt;/p&gt;
&lt;h2&gt;
  
  
  3) Install Ghidra 11.4.1 under /opt/ghidra
&lt;/h2&gt;

&lt;p&gt;Ghidra publishes zip archives per release. We’ll unpack into &lt;code&gt;/opt/ghidra&lt;/code&gt; and keep upgrades atomic.&lt;/p&gt;

&lt;p&gt;Key details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download from the official NSA GitHub &lt;a href="https://github.com/NationalSecurityAgency/ghidra/releases" rel="noopener noreferrer"&gt;Ghidra releases&lt;/a&gt; page for 11.4.1&lt;/li&gt;
&lt;li&gt;Optional integrity: provide &lt;code&gt;GHIDRA_SHA256&lt;/code&gt; from the release page to verify with &lt;code&gt;sha256sum&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The process backs up any existing &lt;code&gt;/opt/ghidra&lt;/code&gt; before replacing it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Steps (high level):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download the Ghidra 11.4.1 zip&lt;/li&gt;
&lt;li&gt;(Optional) Verify its checksum&lt;/li&gt;
&lt;li&gt;Unzip into a temporary directory, then move to &lt;code&gt;/opt/ghidra&lt;/code&gt; atomically&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  4) Wire your shell environment
&lt;/h2&gt;

&lt;p&gt;Two environment variables make life easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GHIDRA_INSTALL_DIR&lt;/code&gt;: points to &lt;code&gt;/opt/ghidra&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GHIDRA_JAVA_HOME&lt;/code&gt;: points to the Temurin JDK (preferred) or falls back to an existing valid &lt;code&gt;JAVA_HOME&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What gets added to your shell RC (&lt;code&gt;~/.bashrc&lt;/code&gt; for bash, &lt;code&gt;~/.zshrc&lt;/code&gt; for zsh):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;export GHIDRA_INSTALL_DIR=/opt/ghidra&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;export PATH="$PATH:$GHIDRA_INSTALL_DIR/support"&lt;/code&gt; (so &lt;code&gt;analyzeHeadless&lt;/code&gt; is on PATH)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;export GHIDRA_JAVA_HOME=/opt/java-temurin/current&lt;/code&gt; (or another detected JDK)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open a new shell or &lt;code&gt;source ~/.bashrc&lt;/code&gt; / &lt;code&gt;source ~/.zshrc&lt;/code&gt; to apply the changes.&lt;/p&gt;
&lt;h3&gt;
  
  
  Optional: add Ghidra binaries to PATH
&lt;/h3&gt;

&lt;p&gt;If you want to call both the headless tools and the GUI launcher from anywhere, add both the &lt;code&gt;support&lt;/code&gt; directory and the root install dir to PATH.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bash:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export GHIDRA_INSTALL_DIR=/opt/ghidra'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$PATH:$GHIDRA_INSTALL_DIR/support:$GHIDRA_INSTALL_DIR"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Zsh:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export GHIDRA_INSTALL_DIR=/opt/ghidra'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="$PATH:$GHIDRA_INSTALL_DIR/support:$GHIDRA_INSTALL_DIR"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  5) Verify the installation
&lt;/h2&gt;

&lt;p&gt;Quick checks you can run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Print Ghidra’s internal version from its properties file&lt;/li&gt;
&lt;li&gt;Run a short, non-interactive headless/CLI command to validate scripts are reachable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^application\.version='&lt;/span&gt; /opt/ghidra/Ghidra/application.properties | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="s1"&gt;'='&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GHIDRA_INSTALL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/support/analyzeHeadless &lt;span class="nt"&gt;-version&lt;/span&gt;

&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GHIDRA_INSTALL_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/ghidraRun &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If those commands execute without errors, you’re set.&lt;/p&gt;

&lt;h2&gt;
  
  
  6) Upgrades and re-runs
&lt;/h2&gt;

&lt;p&gt;This setup is designed to be re-run safely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Temurin: new JDK drops into a versioned folder, &lt;code&gt;current&lt;/code&gt; symlink is updated atomically&lt;/li&gt;
&lt;li&gt;Ghidra: any existing &lt;code&gt;/opt/ghidra&lt;/code&gt; is backed up to &lt;code&gt;/opt/ghidra.bak-&amp;lt;timestamp&amp;gt;&lt;/code&gt; before replacement&lt;/li&gt;
&lt;li&gt;Env: &lt;code&gt;GHIDRA_JAVA_HOME&lt;/code&gt; is updated or inserted once; subsequent runs update it if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To upgrade to a newer Ghidra release, adjust the version/date variables (or pull the latest script) and re-run. Your environment variables remain compatible.&lt;/p&gt;

&lt;h2&gt;
  
  
  7) Uninstall or roll back
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Remove Ghidra: &lt;code&gt;sudo rm -rf /opt/ghidra&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Remove Temurin: &lt;code&gt;sudo rm -rf /opt/java-temurin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Clean your shell RC: remove the lines that export &lt;code&gt;GHIDRA_INSTALL_DIR&lt;/code&gt;, modify &lt;code&gt;PATH&lt;/code&gt;, and &lt;code&gt;GHIDRA_JAVA_HOME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;To roll back: if you see a recent backup like &lt;code&gt;/opt/ghidra.bak-YYYYmmddHHMMSS&lt;/code&gt;, you can move it back to &lt;code&gt;/opt/ghidra&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8) Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Missing tools on non-Debian distros: install &lt;code&gt;wget&lt;/code&gt;, &lt;code&gt;unzip&lt;/code&gt;, &lt;code&gt;tar&lt;/code&gt; via your package manager&lt;/li&gt;
&lt;li&gt;Permission denied under &lt;code&gt;/opt&lt;/code&gt;: you need &lt;code&gt;sudo&lt;/code&gt; privileges for system locations&lt;/li&gt;
&lt;li&gt;GUI on servers/WSL: Ghidra’s GUI requires an X server; use headless mode with &lt;code&gt;analyzeHeadless&lt;/code&gt; if you don’t have a display&lt;/li&gt;
&lt;li&gt;Java detection: if Ghidra prompts for a JDK, ensure &lt;code&gt;GHIDRA_JAVA_HOME&lt;/code&gt; is set and points to a valid JDK with &lt;code&gt;bin/java&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9) Optional: headless workflow teaser
&lt;/h2&gt;

&lt;p&gt;Ghidra ships &lt;code&gt;analyzeHeadless&lt;/code&gt; for automation. For example, to run a simple analysis in a CI runner, you can call it with a temporary project directory and scripts. This is out of scope for this post, but the steps above already place the tool on your PATH for easy use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full automation script (reference)
&lt;/h2&gt;

&lt;p&gt;All the steps above are automated in a single idempotent script that you can read and run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Script: &lt;a href="https://github.com/andremmfaria/rexis/blob/main/scripts/install-ghidra.sh" rel="noopener noreferrer"&gt;https://github.com/andremmfaria/rexis/blob/main/scripts/install-ghidra.sh&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it does for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installs Temurin JDK 21 under &lt;code&gt;/opt/java-temurin&lt;/code&gt; and creates a stable &lt;code&gt;current&lt;/code&gt; symlink&lt;/li&gt;
&lt;li&gt;Downloads and installs Ghidra 11.4.1 under &lt;code&gt;/opt/ghidra&lt;/code&gt; with atomic replace and timestamped backups&lt;/li&gt;
&lt;li&gt;Wires &lt;code&gt;GHIDRA_INSTALL_DIR&lt;/code&gt;, updates &lt;code&gt;PATH&lt;/code&gt;, and sets &lt;code&gt;GHIDRA_JAVA_HOME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verifies the installation non‑interactively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Safe to re-run. If you want checksum verification for either download, export &lt;code&gt;GHIDRA_SHA256&lt;/code&gt; and/or &lt;code&gt;TEMURIN_SHA256&lt;/code&gt; before running it.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and happy reversing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Further references
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ghidra official site: &lt;a href="https://ghidra-sre.org/" rel="noopener noreferrer"&gt;https://ghidra-sre.org/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ghidra GitHub repository: &lt;a href="https://github.com/NationalSecurityAgency/ghidra" rel="noopener noreferrer"&gt;https://github.com/NationalSecurityAgency/ghidra&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ghidra releases (official zips + checksums): &lt;a href="https://github.com/NationalSecurityAgency/ghidra/releases" rel="noopener noreferrer"&gt;https://github.com/NationalSecurityAgency/ghidra/releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ghidra Getting Started: &lt;a href="https://github.com/NationalSecurityAgency/ghidra/blob/master/GhidraDocs/GettingStarted.md" rel="noopener noreferrer"&gt;https://github.com/NationalSecurityAgency/ghidra/blob/master/GhidraDocs/GettingStarted.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Adoptium Temurin releases: &lt;a href="https://adoptium.net/temurin/releases" rel="noopener noreferrer"&gt;https://adoptium.net/temurin/releases&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Adoptium API (programmatic downloads): &lt;a href="https://api.adoptium.net/" rel="noopener noreferrer"&gt;https://api.adoptium.net/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ghidra</category>
      <category>linux</category>
      <category>java</category>
    </item>
    <item>
      <title>Continuous integration with containers and inceptions</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Mon, 10 Nov 2025 23:50:19 +0000</pubDate>
      <link>https://forem.com/andremmfaria/continuous-integration-with-containers-and-inceptions-43gd</link>
      <guid>https://forem.com/andremmfaria/continuous-integration-with-containers-and-inceptions-43gd</guid>
      <description>&lt;p&gt;How to create a CI system using container recursion and how to create a CI system using container recursion&lt;/p&gt;

&lt;p&gt;As many of you already know, containers are something of wonder. They exist since the old days of computing in a concept called &lt;a href="https://en.wikipedia.org/wiki/OS-level_virtualization" rel="noopener noreferrer"&gt;OS-level virtualization&lt;/a&gt;. Since then, for their flexibility, they have been used in an orchestrated manner by many awesome tools, like &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;, &lt;a href="https://dcos.io/" rel="noopener noreferrer"&gt;DC/OS&lt;/a&gt;, &lt;a href="http://mesos.apache.org/" rel="noopener noreferrer"&gt;Apache Mesos&lt;/a&gt; and many more. This provides not only an abstraction layer on OS-Level but also enables a great deal of automation where there was none before.&lt;/p&gt;

&lt;p&gt;I used the &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;Semantic Versioning&lt;/a&gt; but you can easily replace this for any versioning patterns that you like. E.g., &lt;a href="https://calver.org/" rel="noopener noreferrer"&gt;Calendar Versioning&lt;/a&gt; or any &lt;a href="https://en.wikipedia.org/wiki/Software_versioning" rel="noopener noreferrer"&gt;other&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am now going to show you a model of continuous integration (CI) with some "inception".&lt;/p&gt;

&lt;p&gt;Disclaimer:  &lt;br&gt;
This article assumes you have some knowledge about versioning, CI, CD, containers and orchestration, along with the tooling associated with those concepts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;For code versioning we will be using the so popular &lt;a href="https://git-scm.com/" rel="noopener noreferrer"&gt;Git&lt;/a&gt; on &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. You can use pretty much any versioning system in the market with this, I only used git for familiarity. And GitHub for popularity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jenkins.io/" rel="noopener noreferrer"&gt;Jenkins&lt;/a&gt; is an orchestration tool first devised for CI, but since the creation of the &lt;a href="https://wiki.jenkins.io/display/JENKINS/Pipeline+Plugin" rel="noopener noreferrer"&gt;pipeline plugin&lt;/a&gt; it has become more of a general-purpose orchestrator.&lt;/p&gt;

&lt;p&gt;The docker project, or just &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, is a very neat application that lets you manage containers in a easy and inexpensive way using their command line and API.&lt;/p&gt;

&lt;p&gt;For container registry I used &lt;a href="https://www.sonatype.com/nexus-repository-sonatype" rel="noopener noreferrer"&gt;Nexus&lt;/a&gt;. The free version is quite nice and is more than enough to everything we need. It has a plugin for storage on &lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;Amazon's S3&lt;/a&gt; which makes your storage &lt;em&gt;virtually&lt;/em&gt; limitless. (Use at your own discretion)&lt;/p&gt;

&lt;p&gt;Note1: For container storage you can use any registry available in applications like &lt;a href="https://jfrog.com/artifactory/" rel="noopener noreferrer"&gt;Artifactory&lt;/a&gt; but you can also use cloud services like &lt;a href="https://aws.amazon.com/ecr/" rel="noopener noreferrer"&gt;AWS's ECR&lt;/a&gt;, &lt;a href="https://azure.microsoft.com/en-us/services/container-registry/" rel="noopener noreferrer"&gt;AZURE's Container Registry&lt;/a&gt; or &lt;a href="https://cloud.google.com/container-registry/" rel="noopener noreferrer"&gt;GCP's Container Registry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note2: There are many other steps you can input on the pipeline for testing, validation, hardening, etc. But I will be covering only the general steps of the pipeline, like code checkout, container pull, app build, container build and container storage.&lt;/p&gt;
&lt;h2&gt;
  
  
  Model
&lt;/h2&gt;

&lt;p&gt;Some time ago I made a presentation on this topic on a meetup that happened in São Paulo - Brazil. The slides for that presentation can be found &lt;a href="https://slides.com/andremmfaria/inception#/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FContinuous%2520integration%2520with%2520containers%2520and%2520inceptions%2F1_kgv6NFcemt2QESbB0d8dOg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FContinuous%2520integration%2520with%2520containers%2520and%2520inceptions%2F1_kgv6NFcemt2QESbB0d8dOg.png" alt="CI model diagram" width="602" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This model outlines the basic actors and actions made on the process. The orchestrator, in our case Jenkins, contacts the container engine's, in our case Docker, API over TCP and creates the build container. Then it connects to a random SSH port directly on the build container. From there, the orchestrator has a few workflows based on the build workflow. The workflows on this model build two structures, the app container and other container builders.&lt;/p&gt;
&lt;h2&gt;
  
  
  Structures
&lt;/h2&gt;

&lt;p&gt;This section shows the results of the mentioned workflows.&lt;/p&gt;
&lt;h3&gt;
  
  
  Container builds container builders (Recursions are always nice :D)
&lt;/h3&gt;

&lt;p&gt;The build container is a specialized image that is built specifically to build other things, among them other build containers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/andremmfaria/linux-dind-build-image" rel="noopener noreferrer"&gt;Linux definition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/andremmfaria/windows-dind-build-image" rel="noopener noreferrer"&gt;Windows definition&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From this image, the orchestrator pulls container definitions for specific application languages/build engines, like &lt;a href="https://github.com/andremmfaria/java-build-image" rel="noopener noreferrer"&gt;Java/gradle&lt;/a&gt;, &lt;a href="https://github.com/andremmfaria/netcore-build-image" rel="noopener noreferrer"&gt;c#/.NETcore&lt;/a&gt; or &lt;a href="https://github.com/andremmfaria/angular-build-image" rel="noopener noreferrer"&gt;javascript/npm&lt;/a&gt;, builds and stores them onto the container registry.&lt;/p&gt;
&lt;h3&gt;
  
  
  Container builds app containers
&lt;/h3&gt;

&lt;p&gt;From the definitions outlined above, the applications can be built from a container specific for their case. I will be using java's case as an example. It should be similar for other cases.&lt;/p&gt;

&lt;p&gt;Jenkins provides a nice way to bundle pipelines definitions, among other actions, using &lt;a href="https://www.jenkins.io/doc/book/pipeline/shared-libraries/" rel="noopener noreferrer"&gt;shared libraries&lt;/a&gt;. Those are basically &lt;a href="https://groovy-lang.org/" rel="noopener noreferrer"&gt;Groovy&lt;/a&gt; scripts and exist to extend Jenkins functionality.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/andremmfaria/jenkins-shared-lib-multiple-build" rel="noopener noreferrer"&gt;Multi-build shared library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/andremmfaria/java-build-image" rel="noopener noreferrer"&gt;Java build image definition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/andremmfaria/jenkins-shared-lib-multiple-build/blob/7e1a8b3921fdfa8e51966ed9e394628afe1b2050/vars/buildApplication.groovy#L5" rel="noopener noreferrer"&gt;Java definition on the shared lib&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You also need to have a Jenkinsfile on your code's repo for it to know which app engine is to be used for the build. Here is an example Jenkinsfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Library&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'execPipeline'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;

&lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[:]&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'engine'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'java'&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s1"&gt;'type'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'app'&lt;/span&gt;

&lt;span class="n"&gt;execPipeline&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are the configurations the pipeline needs to know where to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;These are the configurations for each tool. This might change a bit overtime but the documentation is (or should) be always true.&lt;/p&gt;

&lt;h4&gt;
  
  
  Jenkins
&lt;/h4&gt;

&lt;p&gt;As stated, above Jenkins is using shared libraries. Its config is fairly simple, you just input the git repo for the shared lib on the config and you're good to go. I used the global shared lib definitions for this example. The full steps can be found &lt;a href="https://www.jenkins.io/doc/book/pipeline/shared-libraries/#retrieval-method" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Jenkins also talks to docker on a remote host for the container's management. You need to install a docker plugin and configure it properly. The steps on Jenkins's plugin side can be found &lt;a href="https://plugins.jenkins.io/docker-plugin/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. On the docker's side you can follow &lt;a href="https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option" rel="noopener noreferrer"&gt;these steps&lt;/a&gt; to enable remote access using various methods.&lt;/p&gt;

&lt;p&gt;This assumes that you are using &lt;a href="https://plugins.jenkins.io/workflow-multibranch/" rel="noopener noreferrer"&gt;Jenkins multibranch pipelines&lt;/a&gt; to create your pipeline's definition. From this, just input your Git repo on the multibranch pipeline and it should roll out by itself.&lt;br&gt;
You can follow this example to configure everything: &lt;a href="https://www.jenkins.io/doc/tutorials/build-a-multibranch-pipeline-project/" rel="noopener noreferrer"&gt;Build a multibranch pipeline project&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Nexus
&lt;/h4&gt;

&lt;p&gt;Configuring Nexus is easy. Just install it following the &lt;a href="https://help.sonatype.com/repomanager3/installation" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; and make sure you have storage space. You'll need it.&lt;/p&gt;

&lt;p&gt;Set up repositories as you feel like. You can separate per environment (gamma, beta, prod, etc), per app engine type (java, node, .net core, etc), both or none. A good recommendation is to separate each artefact repository on a different port. E.g.: beta repo on port 5000, gamma repo on port 5001, etc.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker
&lt;/h4&gt;

&lt;p&gt;Depending on your situation Docker's installation and usage, is be "easy". I mean, if your infrastructure still uses windows for a .net framework app, I feel sorry for you but this method still works.&lt;/p&gt;

&lt;p&gt;For Linux you can follow &lt;a href="https://docs.docker.com/engine/install/#server" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;. For windows you can follow &lt;a href="https://docs.docker.com/docker-for-windows/install/" rel="noopener noreferrer"&gt;this one&lt;/a&gt;. For mac… well… simply don't.&lt;/p&gt;

&lt;h4&gt;
  
  
  Git
&lt;/h4&gt;

&lt;p&gt;You don't need any specific configuration on git's side. Just make sure that Jenkins can clone your repository and your repository contains the Jenkinsfile mentioned in section 3.2 of this article. If both are good, you're golden.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FContinuous%2520integration%2520with%2520containers%2520and%2520inceptions%2F1_b9UFVPyYo_zoTwh4MbhYwQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FContinuous%2520integration%2520with%2520containers%2520and%2520inceptions%2F1_b9UFVPyYo_zoTwh4MbhYwQ.png" alt="Jenkins multibranch pipelines screenshot" width="602" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After these configurations are setup, you will see the pipelines for each branch rollout, and your nexus registry being populated with your app containers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FContinuous%2520integration%2520with%2520containers%2520and%2520inceptions%2F1_l_pIEHaUOPvQ10NJ7TEvuA.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fandremmfaria%2Farticles%2Fmain%2Farticles%2FContinuous%2520integration%2520with%2520containers%2520and%2520inceptions%2F1_l_pIEHaUOPvQ10NJ7TEvuA.png" alt="Nexus registry being populated" width="602" height="158"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading this article till the end. Have a nice day/afternoon/evening :D&lt;/p&gt;

</description>
      <category>ci</category>
      <category>containers</category>
      <category>devops</category>
    </item>
    <item>
      <title>Service metrics and its meanings</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Mon, 10 Nov 2025 23:50:18 +0000</pubDate>
      <link>https://forem.com/andremmfaria/service-metrics-and-its-meanings-ki6</link>
      <guid>https://forem.com/andremmfaria/service-metrics-and-its-meanings-ki6</guid>
      <description>&lt;p&gt;Whenever you have a system, it doesn't really matters what it does, it is of good measure to… well… measure it.&lt;/p&gt;

&lt;p&gt;The purposes are multiple, make sure that it is performing well, measure its overall cost, measure its latency to costumers, etc. It is a fact that it is quite important to do so, but then the following question arises: how?&lt;/p&gt;

&lt;p&gt;Well, the answer to that question is not easy, as there are aspects of each service/case/app that are to be considered.&lt;/p&gt;

&lt;p&gt;A good example of that is Google itself, and I'm not saying that because they have THE book for &lt;a href="https://sre.google/books/" rel="noopener noreferrer"&gt;Site Reliability Engineering&lt;/a&gt;, I am talking about relations between the metrics and their inherent meaning on a myriad of services so big that it covers the entire world. On those books they cover a great deal of trial and error on how to measure production systems and how to focus on what is important. See also the companion &lt;a href="https://sre.google/books/workbook/" rel="noopener noreferrer"&gt;SRE Workbook&lt;/a&gt; and background on &lt;a href="https://sre.google/sre-book/postmortem-culture/" rel="noopener noreferrer"&gt;postmortem culture&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Albeit to that, you might find that those examples are irrelevant for your use case and you'll have to figure everything out for yourself. And that is OK. But one thing is undeniable, those concepts help a bit when they fall on a use case that fits.&lt;/p&gt;

&lt;p&gt;From my point of view, there are two basic types of metrics that you can apply those concepts to. They are inherently interconnected, in the sense that one does not exist without the other. Related framing: product "North Star" metrics (see &lt;a href="https://amplitude.com/blog/product-north-star-metric" rel="noopener noreferrer"&gt;Amplitude guide&lt;/a&gt;) versus engineering reliability KPIs (e.g. &lt;a href="https://sre.google/sre-book/monitoring-distributed-systems/#the-four-golden-signals" rel="noopener noreferrer"&gt;Four Golden Signals&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Business meaning
&lt;/h2&gt;

&lt;p&gt;When we talk about metrics from a business standpoint we are trying to quantify whether the service is succeeding in creating (and keeping) value. A metric is a proxy for an outcome; no proxy is perfect, but good ones are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Connected to customer impact (they move when users are happier or churn when they are not)&lt;/li&gt;
&lt;li&gt;Hard to game (improving the number tends to improve the underlying reality)&lt;/li&gt;
&lt;li&gt;Stable enough to trend yet responsive enough to detect meaningful change&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Leading vs. lagging indicators
&lt;/h3&gt;

&lt;p&gt;Lagging indicators describe results you usually discover too late (monthly revenue, quarterly churn). Leading indicators move earlier and let you correct course (signup conversion, time-to-value, task success rate). A healthy metric set pairs each critical lagging metric to at least one leading metric that you can act upon. For a primer, see &lt;a href="https://www.bmc.com/blogs/leading-vs-lagging-indicators/" rel="noopener noreferrer"&gt;Leading vs. Lagging Indicators&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Outcome (Lagging)&lt;/th&gt;
&lt;th&gt;Example Leading Metrics&lt;/th&gt;
&lt;th&gt;Intervention Window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customer retention&lt;/td&gt;
&lt;td&gt;Onboarding completion %, First action latency&lt;/td&gt;
&lt;td&gt;Days/weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revenue growth&lt;/td&gt;
&lt;td&gt;Trial -&amp;gt; paid conversion, Expansion feature adoption&lt;/td&gt;
&lt;td&gt;Weeks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NPS / Satisfaction (&lt;a href="https://www.atlassian.com/agile/product-management/nps-score" rel="noopener noreferrer"&gt;What is NPS?&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Error-free session %, Page performance (p95) (&lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;Web Vitals&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;Minutes/hours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  North Star and guardrails
&lt;/h3&gt;

&lt;p&gt;The North Star metric is the clearest expression of sustained value creation (e.g., "weekly active collaborative documents"). Guardrail metrics prevent optimizing the North Star at the expense of health (support tickets backlog, cost per transaction, reliability SLO compliance). If a push increases the North Star but violates a guardrail, you slow down.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metric layers
&lt;/h3&gt;

&lt;p&gt;Think in concentric layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;North Star (one, maybe two)&lt;/li&gt;
&lt;li&gt;Strategic pillars (fairly stable: acquisition, activation, retention, efficiency)&lt;/li&gt;
&lt;li&gt;Operational KPIs (change more often, tie to teams OKRs)&lt;/li&gt;
&lt;li&gt;Diagnostic metrics (rich, detailed; used to explain movement, rarely reported upward)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Business questions to validate
&lt;/h3&gt;

&lt;p&gt;Before adopting a business metric ask: What decision will change if this metric moves? Who owns reacting to it? How fast must we respond? What thresholds define success vs. acceptable vs. alert?&lt;/p&gt;

&lt;h3&gt;
  
  
  Common business metric mistakes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Measuring everything and prioritizing nothing&lt;/li&gt;
&lt;li&gt;Declaring a vanity metric (raw signups) as success without a quality filter&lt;/li&gt;
&lt;li&gt;Lacking a clear owner; metrics without owners decay&lt;/li&gt;
&lt;li&gt;Setting targets without historical baselines or variance analysis&lt;/li&gt;
&lt;li&gt;Not revisiting metrics when the product stage changes (growth vs. efficiency phase)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Translating to technical metrics
&lt;/h3&gt;

&lt;p&gt;Each business metric should map (not 1:1, but traceably) to technical signals. If "time-to-value" matters, you must instrument latency of first key workflow and session error rates. This translation is the handshake between product and engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical meaning
&lt;/h2&gt;

&lt;p&gt;On the technical side, metrics become the nervous system of operating the service. Their meaning comes from how precisely they reflect user-visible behavior and how actionable they are for engineers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability pillars vs. service metrics
&lt;/h3&gt;

&lt;p&gt;Observability often cites three pillars: metrics (numeric aggregations), logs (discrete events with context), traces (distributed request flows). Service metrics sit at the top as &lt;em&gt;interpreted&lt;/em&gt; numbers distilled from raw telemetry. You rarely alert on raw logs; you derive counters, rates, percentiles. See &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; for standardized telemetry and this discussion on &lt;a href="https://aws.amazon.com/compare/the-difference-between-monitoring-and-observability/" rel="noopener noreferrer"&gt;Monitoring vs. Observability&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Golden signals
&lt;/h3&gt;

&lt;p&gt;Borrowing from SRE practice, the four golden signals of a user-facing system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Latency – How long it takes to serve a request (track both success and error paths, p50/p95/p99).&lt;/li&gt;
&lt;li&gt;Traffic – Demand size: requests/second, concurrent sessions.&lt;/li&gt;
&lt;li&gt;Errors – Failure rate: explicit errors, timeouts, correctness failures.&lt;/li&gt;
&lt;li&gt;Saturation – Resource exhaustion proximity: CPU, memory, queue length.
Add a fifth in many modern systems: Cost – Unit economics per request/job.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  SLIs, SLOs, SLAs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;SLI (Service Level Indicator): Precisely defined measurement of user experience (e.g., "fraction of read API requests completed under 300 ms and returning 2xx").&lt;/li&gt;
&lt;li&gt;SLO (Service Level Objective): Target for SLI over a window ("99.9% weekly").&lt;/li&gt;
&lt;li&gt;SLA (Service Level Agreement): Contractual externally visible commitment; breaching may have penalties. Always set SLO tighter than SLA.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Error Budget = 1 - SLO. It is the allowed unreliability used for change velocity (deploys, experiments). If you burn budget too fast: slow releases, add reliability work. If you never spend budget: you may be over-investing. For alerting strategy, consider &lt;a href="https://sre.google/workbook/alerting-on-slos/" rel="noopener noreferrer"&gt;multi-window, multi-burn rate SLO alerts&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metric types (Prometheus style)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Counter: Monotonic increase (e.g., total requests). Alert on &lt;em&gt;rate&lt;/em&gt; not raw value. (&lt;a href="https://prometheus.io/docs/concepts/metric_types/#counter" rel="noopener noreferrer"&gt;Prometheus counter&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Gauge: Arbitrary up/down (e.g., memory usage, queue depth). (&lt;a href="https://prometheus.io/docs/concepts/metric_types/#gauge" rel="noopener noreferrer"&gt;Prometheus gauge&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Histogram: Buckets of observations (latency). Enables percentiles &amp;amp; tail analysis. (&lt;a href="https://prometheus.io/docs/concepts/metric_types/#histogram" rel="noopener noreferrer"&gt;Prometheus histogram&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Summary: Client-side calculated quantiles; use sparingly due to aggregation limits. (&lt;a href="https://prometheus.io/docs/concepts/metric_types/#summary" rel="noopener noreferrer"&gt;Prometheus summary&lt;/a&gt;)
Prefer histograms for latency &amp;amp; size; counters for events; gauges for states. Ensure unit consistency (seconds, bytes). Document each metric: name, type, unit, cardinality dimensions. Good primers: &lt;a href="https://prometheus.io/docs/practices/histograms/" rel="noopener noreferrer"&gt;Prometheus histograms and summaries&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=lJ8ydIuPFeU" rel="noopener noreferrer"&gt;Gil Tene on latency percentiles&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cardinality discipline
&lt;/h3&gt;

&lt;p&gt;High-cardinality labels (user_id, session_id) explode storage and slow queries. Guidelines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reserve high cardinality for traces/logs, not primary metrics. (&lt;a href="https://prometheus.io/docs/practices/naming/" rel="noopener noreferrer"&gt;Prometheus best practices&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Dimension by stable groupings (region, API endpoint, plan tier). (&lt;a href="https://grafana.com/docs/grafana/latest/alerting/fundamentals/alert-rules/annotation-label/" rel="noopener noreferrer"&gt;Grafana labels guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Keep total series per metric under sane thresholds (e.g., &amp;lt; 10k) unless justified. (&lt;a href="https://www.honeycomb.io/blog/cardinality-explained/" rel="noopener noreferrer"&gt;Cardinality explained&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Instrumentation checklist
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Define SLIs first (user perspective) then choose raw signals.&lt;/li&gt;
&lt;li&gt;Standard naming: service_namespace_subsystem_metric_unit (e.g., checkout_api_request_latency_seconds). See &lt;a href="https://prometheus.io/docs/practices/naming/" rel="noopener noreferrer"&gt;Prometheus naming best practices&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Include outcome labels: status="success|error|timeout".&lt;/li&gt;
&lt;li&gt;Separate pathologically slow from normal via buckets (e.g., 50,100,200,300,500,800,1200,2000 ms).&lt;/li&gt;
&lt;li&gt;Emit from a well-tested middleware layer to ensure coverage.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Aggregation &amp;amp; rollups
&lt;/h3&gt;

&lt;p&gt;Store both raw series and periodic rollups (1m, 5m, 1h) to enable long-range trends affordably. Tail metrics (p99) need raw-ish resolution; cost/traffic can tolerate coarser granularity. See &lt;a href="https://prometheus.io/docs/practices/rules/" rel="noopener noreferrer"&gt;Recording rules&lt;/a&gt; and &lt;a href="https://medium.com/timescale/real-time-analytics-for-time-series-a-devs-intro-to-continuous-aggregates-b9c38b5746f0/" rel="noopener noreferrer"&gt;continuous aggregates&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dashboards vs. alerts
&lt;/h3&gt;

&lt;p&gt;Dashboards are for exploration &amp;amp; storytelling; alerts for actionable interruption. An alert should meet: clear owner, severity classification, runbook link, deduplication logic, and auto-silence conditions (maintenance windows, downstream known incidents). Too many unactionable alerts create alert fatigue; measure mean time to acknowledge (MTTA) and percent of alerts yielding tickets. See &lt;a href="https://www.pagerduty.com/resources/digital-operations/learn/alert-fatigue/" rel="noopener noreferrer"&gt;PagerDuty on alert fatigue&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency metrics
&lt;/h3&gt;

&lt;p&gt;Track upstream SLIs you depend on (e.g., database p99 latency). Decompose incidents faster by correlating service SLI dip with dependency saturation metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous improvement loop
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Observe SLI trends and error budget consumption.&lt;/li&gt;
&lt;li&gt;Perform weekly reliability review: anomalies, top regression sources.&lt;/li&gt;
&lt;li&gt;Propose experiments (cache change, index) with expected movement.&lt;/li&gt;
&lt;li&gt;Deploy and compare before/after via change-focused dashboards.&lt;/li&gt;
&lt;li&gt;Feed learnings into next quarter's reliability &amp;amp; efficiency OKRs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bridging business and technical metrics
&lt;/h2&gt;

&lt;p&gt;The most powerful metrics tell a dual story: "p95 checkout latency improved 20%, and conversion rose 3%". Maintain a mapping document linking each business KPI to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Primary SLI(s)&lt;/li&gt;
&lt;li&gt;Key technical driver metrics (cache hit rate, DB lock wait)&lt;/li&gt;
&lt;li&gt;Leading indicator hypotheses (client render time)
Revisit mapping quarterly; prune metrics that no longer explain variance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Metric taxonomy cheat sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;North Star&lt;/td&gt;
&lt;td&gt;Weekly active collaborative docs&lt;/td&gt;
&lt;td&gt;Product leadership&lt;/td&gt;
&lt;td&gt;Weekly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLI&lt;/td&gt;
&lt;td&gt;Successful doc save latency p95 &amp;lt; 400ms&lt;/td&gt;
&lt;td&gt;SRE/Engineering&lt;/td&gt;
&lt;td&gt;Continuous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLO&lt;/td&gt;
&lt;td&gt;99.95% saves &amp;lt; 400ms weekly&lt;/td&gt;
&lt;td&gt;SRE&lt;/td&gt;
&lt;td&gt;Weekly review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guardrail&lt;/td&gt;
&lt;td&gt;Infra cost per save &amp;lt; $0.001&lt;/td&gt;
&lt;td&gt;Finance/Eng&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diagnostic&lt;/td&gt;
&lt;td&gt;DB write lock wait time&lt;/td&gt;
&lt;td&gt;Eng team&lt;/td&gt;
&lt;td&gt;Ad hoc&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common anti-patterns
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Alerting on averages (hide tail pain) instead of percentiles. See &lt;a href="https://queue.acm.org/detail.cfm?id=1814327" rel="noopener noreferrer"&gt;ACM Queue on percentiles&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Chasing "100%" reliability (diminishing returns) vs. defined SLO + error budget. See &lt;a href="https://sre.google/sre-book/service-level-objectives/#error-budgets" rel="noopener noreferrer"&gt;Error Budgets&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Overloading a single metric with too many labels causing cardinality blow-up. See &lt;a href="https://prometheus.io/docs/practices/instrumentation/#avoid-missing-labels" rel="noopener noreferrer"&gt;Prometheus instrumentation pitfalls&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Building dashboards no one uses: track dashboard views, retire stale boards. See &lt;a href="https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/best-practices/" rel="noopener noreferrer"&gt;Grafana dashboard tips&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Confusing throughput with performance (more requests could mean retries from errors). Contrast &lt;a href="https://grafana.com/blog/2018/08/02/the-red-method-how-to-instrument-your-services/" rel="noopener noreferrer"&gt;RED method&lt;/a&gt; vs. &lt;a href="https://www.brendangregg.com/usemethod.html" rel="noopener noreferrer"&gt;USE method&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical example (API service)
&lt;/h2&gt;

&lt;p&gt;Scenario: Public REST API for document editing.&lt;br&gt;
Defined SLIs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read latency: fraction of GET /doc/{id} served &amp;lt; 250ms.&lt;/li&gt;
&lt;li&gt;Write success: fraction of PUT /doc/{id} returning 2xx.&lt;/li&gt;
&lt;li&gt;Editing session stability: sessions without disconnect &amp;gt; 5 minutes.
SLOs: 99.9%, 99.95%, 99% respectively (weekly). Error budget alarms at 50%, 75%, 100% consumption. Business KPI mapped: active editing minutes per user. Hypothesis: Improving read latency p95 will raise active minutes by reducing initial load friction. Run experiment: introduce edge caching; monitor cache hit rate (target &amp;gt; 80%), origin latency drop (expect -30%). Outcome metrics decide rollout.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting started checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;List top 3 user journeys; define one SLI each.&lt;/li&gt;
&lt;li&gt;Set initial SLOs using historical 4-week median performance minus a modest stretch.&lt;/li&gt;
&lt;li&gt;Instrument counters for requests, errors; histograms for latency.&lt;/li&gt;
&lt;li&gt;Create one "golden dashboard" with SLIs + dependency saturation metrics.&lt;/li&gt;
&lt;li&gt;Define 3 alerts only: SLI burn rate high (short &amp;amp; long window), dependency slowdown, error spike.&lt;/li&gt;
&lt;li&gt;Write runbooks before enabling alerts.&lt;/li&gt;
&lt;li&gt;Review after 2 weeks: adjust buckets, remove noisy labels, refine SLO.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Metrics are a language; alignment happens when business and engineering speak a shared dialect rooted in user experience. Start small, be explicit, iterate continuously. The goal is not more graphs; it's faster, better decisions.&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>sre</category>
      <category>observability</category>
    </item>
  </channel>
</rss>
