<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andre Faria</title>
    <description>The latest articles on DEV Community by Andre Faria (@andremmfaria).</description>
    <link>https://dev.to/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>DEV Community: Andre Faria</title>
      <link>https://dev.to/andremmfaria</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andremmfaria"/>
    <language>en</language>
    <item>
      <title>MCPs Are Eating Your Context Window (And What To Do About It)</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Sun, 24 May 2026 02:53:21 +0000</pubDate>
      <link>https://dev.to/andremmfaria/mcps-are-eating-your-context-window-and-what-to-do-about-it-1905</link>
      <guid>https://dev.to/andremmfaria/mcps-are-eating-your-context-window-and-what-to-do-about-it-1905</guid>
      <description>&lt;p&gt;I was looking at my &lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; token usage data when I noticed something odd. The numbers were dominated by cache reads, tens of millions of tokens per week, on a setup where the actual conversations were relatively short. The output tokens, the ones where the model is actually thinking, were a small fraction of the total.&lt;/p&gt;

&lt;p&gt;The culprit turned out to be something I had not thought to question: MCP servers.&lt;/p&gt;

&lt;p&gt;This article is about what MCP tool schemas actually cost, why most people miss it, and how skills solve the problem by loading lazily instead of front-loading everything into every turn. The numbers are real, measured from a real setup, priced against real provider rates.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What MCP servers actually inject
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is a standard for connecting AI agents to external services. The idea is straightforward: define a set of tools, and the model can call them. OPNsense integration? Here are 133 tools. TrueNAS SCALE? Here are 278. GitHub? Here are 101.&lt;/p&gt;

&lt;p&gt;The problem is how those tools reach the model. Every tool ships a JSON schema describing its name, description, parameters, types, enums, and constraints. When an MCP server is active, &lt;a href="https://www.mindstudio.ai/blog/claude-code-mcp-server-token-overhead" rel="noopener noreferrer"&gt;every single one of those schemas gets serialised and injected with every API call&lt;/a&gt;, whether you are going to use any of them or not. This is not a quirk of any particular client. It is how the MCP spec works. The tools array goes with every request.&lt;/p&gt;

&lt;p&gt;Here is what that looks like in practice, measured from my homelab setup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Tools&lt;/th&gt;
&lt;th&gt;Estimated tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TrueNAS MCP&lt;/td&gt;
&lt;td&gt;278&lt;/td&gt;
&lt;td&gt;~27,800&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPNsense MCP&lt;/td&gt;
&lt;td&gt;133&lt;/td&gt;
&lt;td&gt;~13,300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright MCP&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;~3,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Native agent tools&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;~2,500&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workspace files (AGENTS.md, SOUL.md, etc.)&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;~3,400&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Total first-turn context: approximately &lt;strong&gt;41,000 tokens&lt;/strong&gt;. Workspace files account for 8% of that. The other 92% is tool schemas.&lt;/p&gt;

&lt;p&gt;Run 215 turns per day (a moderate multi-agent setup) and you are pushing roughly 9 million context tokens daily just to describe tools you rarely use.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. This is not a homelab problem
&lt;/h2&gt;

&lt;p&gt;A few well-known MCP servers to put scale in perspective:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MCP Server&lt;/th&gt;
&lt;th&gt;Total tools&lt;/th&gt;
&lt;th&gt;Default/active&lt;/th&gt;
&lt;th&gt;Tokens (full)&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/github/github-mcp-server" rel="noopener noreferrer"&gt;GitHub MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;101&lt;/td&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;~64,600 / ~30,300&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/github/github-mcp-server/discussions/1182" rel="noopener noreferrer"&gt;Official discussion #1182&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/truenas/api_client" rel="noopener noreferrer"&gt;TrueNAS MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;278&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~27,800&lt;/td&gt;
&lt;td&gt;Measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/yourusername/opnsense-mcp-server" rel="noopener noreferrer"&gt;OPNsense MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;133&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~13,300&lt;/td&gt;
&lt;td&gt;Measured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.datadoghq.com/bits_ai/mcp_server" rel="noopener noreferrer"&gt;Datadog MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;40+ (16 core)&lt;/td&gt;
&lt;td&gt;16 core&lt;/td&gt;
&lt;td&gt;~4,000+&lt;/td&gt;
&lt;td&gt;Datadog docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/awslabs/mcp" rel="noopener noreferrer"&gt;AWS MCP suite&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;50+ across 20 servers&lt;/td&gt;
&lt;td&gt;Per server (5-15)&lt;/td&gt;
&lt;td&gt;~1,500-3,000 each&lt;/td&gt;
&lt;td&gt;AWS Labs repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/atlassian/atlassian-mcp-server" rel="noopener noreferrer"&gt;Atlassian Rovo MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~12-20&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~3,000-5,000&lt;/td&gt;
&lt;td&gt;Estimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://docs.stripe.com/mcp" rel="noopener noreferrer"&gt;Stripe MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~20 official&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~5,000&lt;/td&gt;
&lt;td&gt;Stripe docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://api.slack.com/mcp" rel="noopener noreferrer"&gt;Slack MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~3,250&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.speakeasy.com/product/mcp-gateway/catalog/slack" rel="noopener noreferrer"&gt;Speakeasy catalog&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/makenotion/notion-mcp-server" rel="noopener noreferrer"&gt;Notion MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~14&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~3,500&lt;/td&gt;
&lt;td&gt;Docker MCP catalog&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/getsentry/sentry-mcp" rel="noopener noreferrer"&gt;Sentry MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~10-15&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~2,500-3,750&lt;/td&gt;
&lt;td&gt;Estimated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/modelcontextprotocol/servers/tree/main/src/postgres" rel="noopener noreferrer"&gt;PostgreSQL MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~5-12&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~1,250-3,000&lt;/td&gt;
&lt;td&gt;MCP reference server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/strowk/mcp-k8s-go" rel="noopener noreferrer"&gt;Kubernetes MCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~15-25&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;~3,750-6,250&lt;/td&gt;
&lt;td&gt;Community&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A developer running GitHub MCP, Slack MCP, and a Postgres MCP alongside their native tools is starting every single message with roughly &lt;strong&gt;40,000 tokens&lt;/strong&gt; of context overhead before they have typed a word. GitHub MCP alone at full capacity burns &lt;strong&gt;64,600 tokens&lt;/strong&gt;, consuming 32% of Claude Sonnet's 200K context window before the conversation starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. This affects every tool that uses MCP
&lt;/h2&gt;

&lt;p&gt;This is not an OpenClaw issue. It is a consequence of how MCP works architecturally, and it affects every AI tool that integrates with MCP servers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;MCP support&lt;/th&gt;
&lt;th&gt;Injection pattern&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Full native&lt;/td&gt;
&lt;td&gt;Eager, every API call&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/44536" rel="noopener noreferrer"&gt;Issue #44536&lt;/a&gt;: ToolSearch experiment, 85% reduction when enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/openai/codex" rel="noopener noreferrer"&gt;Codex CLI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Eager, per turn&lt;/td&gt;
&lt;td&gt;Used in &lt;a href="https://techcommunity.microsoft.com/blog/appsonazureblog/get-started-with-datadog-mcp-server-in-azure-sre-agent/4497123" rel="noopener noreferrer"&gt;Datadog + Codex integration examples&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Eager by default; lazy via &lt;a href="https://lobehub.com/mcp/francisco-m001-opencode-mcp-tool-search" rel="noopener noreferrer"&gt;opencode-mcp-tool-search plugin&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Same underlying problem; community plugin fixes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenClaw&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Eager, per turn&lt;/td&gt;
&lt;td&gt;What this article is about&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The MCP spec itself requires the tools array to be sent with each API call. The only documented escape valve is "ToolSearch", a meta-tool that lets the model search for tools by name rather than receiving all schemas upfront. Claude Code introduced this experimentally, with a &lt;a href="https://github.com/anthropics/claude-code/issues/44536" rel="noopener noreferrer"&gt;reported 85% token reduction&lt;/a&gt;. GitHub MCP reduced its default toolset from 101 to 52 tools specifically in response to &lt;a href="https://github.com/github/github-mcp-server/discussions/1182" rel="noopener noreferrer"&gt;user complaints about context overhead&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. What it costs per provider
&lt;/h2&gt;

&lt;p&gt;On a flat-rate plan like GitHub Copilot, this overhead is invisible. You pay a fixed monthly fee regardless of token volume. But most serious usage of Claude, GPT, or Gemini goes through the API, where every token has a price.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input $/M&lt;/th&gt;
&lt;th&gt;Cached input $/M&lt;/th&gt;
&lt;th&gt;Output $/M&lt;/th&gt;
&lt;th&gt;Context window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.anthropic.com/pricing" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$0.30&lt;/td&gt;
&lt;td&gt;$15.00&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.anthropic.com/pricing" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Claude Haiku 4.5&lt;/td&gt;
&lt;td&gt;$1.00&lt;/td&gt;
&lt;td&gt;$0.10&lt;/td&gt;
&lt;td&gt;$5.00&lt;/td&gt;
&lt;td&gt;200K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://openai.com/api/pricing/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GPT-5&lt;/td&gt;
&lt;td&gt;$1.25&lt;/td&gt;
&lt;td&gt;~$0.31&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;td&gt;272K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://openai.com/api/pricing/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GPT-4.1&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$8.00&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ai.google.dev/pricing" rel="noopener noreferrer"&gt;Google&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;$1.25&lt;/td&gt;
&lt;td&gt;~$0.25&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://ai.google.dev/pricing" rel="noopener noreferrer"&gt;Google&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;$0.30&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://aws.amazon.com/bedrock/pricing/" rel="noopener noreferrer"&gt;AWS Bedrock&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;~$0.30&lt;/td&gt;
&lt;td&gt;$15.00&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://aws.amazon.com/bedrock/pricing/" rel="noopener noreferrer"&gt;AWS Bedrock&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Amazon Nova Pro&lt;/td&gt;
&lt;td&gt;$0.96&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$3.84&lt;/td&gt;
&lt;td&gt;300K tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/" rel="noopener noreferrer"&gt;Azure OpenAI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GPT-4.1&lt;/td&gt;
&lt;td&gt;~$2.00&lt;/td&gt;
&lt;td&gt;~$0.50&lt;/td&gt;
&lt;td&gt;~$8.00&lt;/td&gt;
&lt;td&gt;1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://openrouter.ai/pricing" rel="noopener noreferrer"&gt;OpenRouter&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;(aggregator)&lt;/td&gt;
&lt;td&gt;pass-through&lt;/td&gt;
&lt;td&gt;model-dependent&lt;/td&gt;
&lt;td&gt;pass-through&lt;/td&gt;
&lt;td&gt;varies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What 44,500 tokens of MCP overhead costs per message at different providers, assuming prompt caching is active (best case):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider + Model&lt;/th&gt;
&lt;th&gt;Per message (cached)&lt;/th&gt;
&lt;th&gt;Per message (uncached)&lt;/th&gt;
&lt;th&gt;Monthly (215 turns/day, 22 days)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic Claude Sonnet 4.6&lt;/td&gt;
&lt;td&gt;$0.013&lt;/td&gt;
&lt;td&gt;$0.134&lt;/td&gt;
&lt;td&gt;$62 (cached) / $622 (uncached)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI GPT-4.1&lt;/td&gt;
&lt;td&gt;$0.022&lt;/td&gt;
&lt;td&gt;$0.089&lt;/td&gt;
&lt;td&gt;$104 (cached) / $416 (uncached)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI GPT-5&lt;/td&gt;
&lt;td&gt;$0.014&lt;/td&gt;
&lt;td&gt;$0.056&lt;/td&gt;
&lt;td&gt;$65 (cached) / $260 (uncached)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini 2.5 Flash&lt;/td&gt;
&lt;td&gt;$0.013&lt;/td&gt;
&lt;td&gt;$0.013&lt;/td&gt;
&lt;td&gt;$62 (no caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Bedrock Nova Pro&lt;/td&gt;
&lt;td&gt;$0.009&lt;/td&gt;
&lt;td&gt;$0.043&lt;/td&gt;
&lt;td&gt;$42 (cached) / $200 (uncached)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are costs from overhead alone, before any actual work is done. On Sonnet without prompt caching, 44,500 tokens per message at 215 turns/day adds up to over $600/month in context overhead.&lt;/p&gt;

&lt;p&gt;Prompt caching helps significantly for repeated context (the tool schemas do not change turn-to-turn, so they cache well). But even at the cached rate, the overhead is material at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Skills: lazy loading as the fix
&lt;/h2&gt;

&lt;p&gt;The alternative is skills. In OpenClaw and in tools like &lt;a href="https://github.com/code-yeongyu/oh-my-openagent" rel="noopener noreferrer"&gt;oh-my-openagent&lt;/a&gt; for OpenCode, a skill is a markdown file that tells the model how to use a capability. Only a name and a short description enter the context upfront. The full instructions are loaded when the model actually needs them.&lt;/p&gt;

&lt;p&gt;A skill entry in the context 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;truenas: Manage TrueNAS SCALE: storage, sharing, services, VMs, alerts, replication.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is roughly 24 tokens. Compare that to the ~27,800 tokens for the TrueNAS MCP schema.&lt;/p&gt;

&lt;p&gt;The model retains full capability. When it needs to interact with TrueNAS, it reads the skill and executes shell commands: &lt;code&gt;midclt&lt;/code&gt; websocket calls, &lt;code&gt;curl&lt;/code&gt; against the REST API, or short Python scripts. The capability is the same. The context cost is not.&lt;/p&gt;

&lt;p&gt;The token savings from replacing three MCP servers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Replaced&lt;/th&gt;
&lt;th&gt;Tokens before&lt;/th&gt;
&lt;th&gt;Tokens after&lt;/th&gt;
&lt;th&gt;Saved per turn&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TrueNAS MCP&lt;/td&gt;
&lt;td&gt;~27,800&lt;/td&gt;
&lt;td&gt;~24&lt;/td&gt;
&lt;td&gt;~27,776&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPNsense MCP&lt;/td&gt;
&lt;td&gt;~13,300&lt;/td&gt;
&lt;td&gt;~24&lt;/td&gt;
&lt;td&gt;~13,276&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Playwright MCP&lt;/td&gt;
&lt;td&gt;~3,500&lt;/td&gt;
&lt;td&gt;~24&lt;/td&gt;
&lt;td&gt;~3,476&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~44,600&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~72&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~44,528&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;First-turn context drops from ~41,000 tokens to roughly ~10,000. A 75% reduction in baseline overhead per turn.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. What skills look like in practice
&lt;/h2&gt;

&lt;p&gt;A skill is a SKILL.md file with a short frontmatter description and usage instructions. The model reads it when needed. The skill documents three things: how to authenticate, what the primary command pattern is, and what the fallback is when the primary does not cover the full surface.&lt;/p&gt;

&lt;p&gt;Credentials live in the environment, not in the skill file. In OpenClaw, env vars are declared in &lt;code&gt;openclaw.json&lt;/code&gt; and injected into every agent turn. Other frameworks use &lt;code&gt;.env&lt;/code&gt; files, secrets stores, or per-agent config blocks. The skill does not care how the variables arrive, only that they exist at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"TRUENAS_URL"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://truenas.host:50443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"TRUENAS_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For API key auth, that is all the setup needed. For OAuth-based services, the approach shifts to pre-authenticated CLI state: &lt;code&gt;gh auth login&lt;/code&gt; stores credentials in &lt;code&gt;~/.config/gh/hosts.yml&lt;/code&gt;; &lt;code&gt;jira init&lt;/code&gt; writes an API token to &lt;code&gt;~/.config/.jira/.config.yml&lt;/code&gt;. After that one-time setup, skill calls carry no credentials in the command itself.&lt;/p&gt;

&lt;p&gt;Each skill documents a primary path and a fallback. For TrueNAS that is &lt;code&gt;midclt&lt;/code&gt; (websocket) with &lt;code&gt;curl&lt;/code&gt; as fallback:&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="c"&gt;# Primary: dedicated CLI&lt;/span&gt;
midclt &lt;span class="nt"&gt;-u&lt;/span&gt; ws://truenas.host:50443/api/current &lt;span class="nt"&gt;--api-key&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRUENAS_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; call pool.query

&lt;span class="c"&gt;# Fallback: curl REST&lt;/span&gt;
curl &lt;span class="nt"&gt;-sk&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TRUENAS_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRUENAS_URL&lt;/span&gt;&lt;span class="s2"&gt;/api/v2.0/pool"&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more complex operations (bulk queries, job polling, conditional logic), a short Python script is cleaner than chained shell commands:&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;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="n"&gt;sys&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;insert&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/home/user/.local/lib/python3.14/site-packages&lt;/span&gt;&lt;span class="sh"&gt;'&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;truenas_api_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Client&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TRUENAS_WS_URL&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TRUENAS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&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;c&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;ds&lt;/span&gt; &lt;span class="ow"&gt;in&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;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zfs.dataset.query&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="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;select&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;used&lt;/span&gt;&lt;span class="sh"&gt;'&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;ds&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="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;used&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each skill also ships a &lt;code&gt;check.sh&lt;/code&gt; that verifies the CLI is installed, env vars are set, and the host is reachable before the agent tries to use it. Validation moves from MCP schema enforcement (happens automatically before every call) to &lt;code&gt;check.sh&lt;/code&gt; (happens at load time, once). For stable infrastructure with a single operator that is a reasonable trade. For production systems with many contributors and rapidly evolving APIs, MCPs may still be the right call.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Real audit numbers
&lt;/h2&gt;

&lt;p&gt;Before starting this work I pulled six days of session data from my setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;92 million cache read tokens in six days&lt;/li&gt;
&lt;li&gt;Average daily cost at Sonnet direct API rates: $15/day&lt;/li&gt;
&lt;li&gt;Projected monthly: $285-390/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was on a flat-rate plan where none of it showed up in the bill. But GitHub Copilot is &lt;a href="https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing" rel="noopener noreferrer"&gt;actively transitioning to usage-based billing&lt;/a&gt;. When that change completes, token volume will directly translate to cost for the first time.&lt;/p&gt;

&lt;p&gt;The right time to fix token obesity is before you are paying per token, not after.&lt;/p&gt;

&lt;p&gt;I also found a secondary problem during the audit: AGENTS.md had grown to 99% of the 12,000-character per-file bootstrap limit, meaning it was being silently truncated on every turn. The workspace files, which everyone assumes are the main context cost, were actually only 8% of the total. The other 92% was tool schemas that nobody had looked at.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. The replacement stack
&lt;/h2&gt;

&lt;p&gt;For reference, this is what replaced the three MCP servers in my setup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TrueNAS:&lt;/strong&gt; &lt;a href="https://github.com/truenas/api_client" rel="noopener noreferrer"&gt;&lt;code&gt;truenas_api_client&lt;/code&gt;&lt;/a&gt; (official iXsystems library) and &lt;code&gt;midclt&lt;/code&gt; CLI for websocket API access. REST API via &lt;code&gt;curl&lt;/code&gt; as fallback. Full coverage of the 278-tool surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OPNsense:&lt;/strong&gt; &lt;a href="https://github.com/andreas-stuerz/opn-cli" rel="noopener noreferrer"&gt;&lt;code&gt;opn-cli&lt;/code&gt;&lt;/a&gt; (community Python CLI) for firewall, HAProxy, routes, and DNS. Raw &lt;code&gt;curl&lt;/code&gt; against the OPNsense REST API for NAT, VLANs, DHCP, and ACME, which opn-cli does not cover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playwright:&lt;/strong&gt; &lt;a href="https://shot-scraper.datasette.io" rel="noopener noreferrer"&gt;&lt;code&gt;shot-scraper&lt;/code&gt;&lt;/a&gt; for screenshots, JS eval, and HTML extraction. Python &lt;code&gt;playwright&lt;/code&gt; library for full browser automation: form fills, login flows, file downloads.&lt;/p&gt;

&lt;p&gt;All three follow the same pattern: a primary CLI or library path with documented fallback commands for anything the primary does not cover. The skill documents both paths. The model chooses based on what the task requires.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Conclusion
&lt;/h2&gt;

&lt;p&gt;MCP servers are a reasonable architecture for giving agents access to external services. The problem is the cost model: every tool schema defined by an active MCP server gets injected with every API call, whether those tools are relevant to the current task or not. As the ecosystem adds more MCP servers (GitHub, Datadog, Atlassian, Stripe, Slack, Sentry, AWS, Kubernetes), the baseline context overhead per message compounds.&lt;/p&gt;

&lt;p&gt;On flat-rate plans, this is invisible. Under per-token billing, it is a significant and growing cost that starts before any work has been done.&lt;/p&gt;

&lt;p&gt;Skills sidestep this by being lazy. A skill entry is a name and a description, a few dozen tokens. Full instructions load when needed. The model calls CLIs and APIs directly. The capability is the same; the upfront cost is not.&lt;/p&gt;

&lt;p&gt;The numbers from this setup: 44,500 tokens saved per turn, a 75% reduction in baseline context overhead, and a monthly saving of roughly $62 under Sonnet cached pricing, or $622 at uncached rates. On a flat rate today, not relevant. On usage-based billing, very much so.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on GitHub Copilot:&lt;/strong&gt; Copilot Pro+ at $39/month is a flat rate that absorbs all token volume. If you stay within the request limits, this overhead is financially invisible. The analysis in this article applies to direct API usage with Anthropic, OpenAI, Google, AWS Bedrock, or any other pay-per-token provider. If you are on Copilot and not planning to switch, the context window fill rate argument still applies: you hit context limits sooner. But the cost argument does not, until Copilot's usage-based transition completes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol specification&lt;/a&gt; - the MCP protocol standard&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/github/github-mcp-server/discussions/1182" rel="noopener noreferrer"&gt;GitHub MCP tool count and token overhead discussion&lt;/a&gt; - confirmed 64.6K / 30.3K token numbers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/44536" rel="noopener noreferrer"&gt;Claude Code ToolSearch lazy loading issue&lt;/a&gt; - 85% token reduction experiment&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mindstudio.ai/blog/claude-code-mcp-server-token-overhead" rel="noopener noreferrer"&gt;MindStudio: Claude Code MCP token overhead analysis&lt;/a&gt; - tool injection mechanism explained&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://forum.cursor.com/t/tools-limited-to-40-total/67976" rel="noopener noreferrer"&gt;Cursor 40-tool limit discussion&lt;/a&gt; - context pressure forcing hard limits&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://lobehub.com/mcp/francisco-m001-opencode-mcp-tool-search" rel="noopener noreferrer"&gt;OpenCode MCP tool search plugin&lt;/a&gt; - lazy loading for OpenCode&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.datadoghq.com/bits_ai/mcp_server" rel="noopener noreferrer"&gt;Datadog MCP server&lt;/a&gt; - 16+ core tools, additional toolsets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/atlassian/atlassian-mcp-server" rel="noopener noreferrer"&gt;Atlassian MCP server&lt;/a&gt; - Jira, Confluence, Compass&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/awslabs/mcp" rel="noopener noreferrer"&gt;AWS MCP suite&lt;/a&gt; - 20+ individual servers&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/getsentry/sentry-mcp" rel="noopener noreferrer"&gt;Sentry MCP&lt;/a&gt; - official debugging MCP&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;AgentSkills specification&lt;/a&gt; - the skill format used by OpenClaw and oh-my-openagent&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/code-yeongyu/oh-my-openagent" rel="noopener noreferrer"&gt;oh-my-openagent&lt;/a&gt; - skills for OpenCode, same lazy-loading pattern&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/truenas/api_client" rel="noopener noreferrer"&gt;truenas/api_client&lt;/a&gt; - official TrueNAS Python client used in replacement&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/andreas-stuerz/opn-cli" rel="noopener noreferrer"&gt;opn-cli&lt;/a&gt; - community OPNsense CLI&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shot-scraper.datasette.io" rel="noopener noreferrer"&gt;shot-scraper&lt;/a&gt; - Simon Willison's browser scraping CLI&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/pricing" rel="noopener noreferrer"&gt;Anthropic pricing&lt;/a&gt; - Claude API rates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openai.com/api/pricing/" rel="noopener noreferrer"&gt;OpenAI pricing&lt;/a&gt; - GPT API rates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/pricing" rel="noopener noreferrer"&gt;Google AI pricing&lt;/a&gt; - Gemini API rates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/bedrock/pricing/" rel="noopener noreferrer"&gt;AWS Bedrock pricing&lt;/a&gt; - Bedrock rates&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pricepertoken.com" rel="noopener noreferrer"&gt;pricepertoken.com&lt;/a&gt; - cross-provider pricing comparisons&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
      <category>openclaw</category>
    </item>
    <item>
      <title>Raising a Good Junior: What AI Gets Wrong About Knowledge and What It Means for the Next Generation</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Wed, 20 May 2026 23:16:30 +0000</pubDate>
      <link>https://dev.to/andremmfaria/raising-a-good-junior-what-ai-gets-wrong-about-knowledge-and-what-it-means-for-the-next-generation-18ne</link>
      <guid>https://dev.to/andremmfaria/raising-a-good-junior-what-ai-gets-wrong-about-knowledge-and-what-it-means-for-the-next-generation-18ne</guid>
      <description>&lt;p&gt;A friend of mine, Jose, sent me a conversation he'd had with an AI assistant about an article he'd been reading. The article was &lt;a href="https://cekrem.github.io/posts/the-tacit-dimension/" rel="noopener noreferrer"&gt;The Tacit Dimension&lt;/a&gt; by Christian Ekrem. Jose's observation was sharp: he'd been frustrated by the same thing the article describes, AI assistants that produce confident output without surfacing any of the reasoning behind it, the implicit design decisions staying implicit. The conversation he shared was good enough that I went and read the article itself.&lt;/p&gt;

&lt;p&gt;It made me put my phone down. Not because it was wrong, but because it was pointing at something real and uncomfortable, and because it immediately made me think about my son.&lt;/p&gt;

&lt;p&gt;The article builds on Michael Polanyi's 1966 claim: &lt;em&gt;we can know more than we can tell&lt;/em&gt;. Polanyi's observation was that expert knowledge is structurally tacit. It lives in the body, in practice, in the pattern-recognition accumulated over years of doing a thing. You can't extract it. You can't train a model on it, because it was never written down. And you can't transfer it except by working alongside someone who has it.&lt;/p&gt;

&lt;p&gt;Ekrem applies this to AI-assisted software development and argues we are sleepwalking into a crisis: juniors are being apprenticed to AI assistants instead of to seniors, the "why does this work this way?" questions are drying up, and the tacit knowledge that used to flow through teams is quietly bankrupting out of codebases. The seniors retire. Nobody knows why the auth system works the way it does. The code keeps running.&lt;/p&gt;

&lt;p&gt;It's a well-argued piece. But it has a gap in it, and that gap leads somewhere interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the Argument Holds
&lt;/h2&gt;

&lt;p&gt;Ekrem's strongest point isn't about AI. It's about what happens when any shortcut severs the connection between doing and understanding.&lt;/p&gt;

&lt;p&gt;His illustration is a colleague who spent an afternoon refusing to merge a PR that was technically correct. Tests passing, CI green, everything fine on paper. He kept saying "I just don't believe this code." Forty minutes into walking the author through it line by line, the author said offhand: &lt;em&gt;"this assumes the queue is FIFO, but I think that's safe."&lt;/em&gt; It wasn't. The queue was FIFO in development and best-effort-FIFO in production, buried in a runbook nobody had read in two years.&lt;/p&gt;

&lt;p&gt;The colleague had smelled it from the diff. Not from reading a document. From a decade of looking at similar things and accumulating a mental model of where pain tends to come from. That kind of knowledge doesn't show up in any training corpus because it was never written down in any single place. It was always distributed across experience, context, and memory.&lt;/p&gt;

&lt;p&gt;An AI can't replicate that. Not because current models are too limited, but because the knowledge is structurally absent from anything a model could be trained on. That's the actual claim, and it survives scrutiny.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where I Push Back
&lt;/h2&gt;

&lt;p&gt;The argument assumes that the median junior, given access to an AI assistant, will use it as a replacement for thinking. And honestly? A lot of them will. But that's not a fact about AI. That's a fact about the median junior and the absence of good mentorship. The same junior, given access to Stack Overflow, Google, or a patient senior who answers every question without making them think first, will also atrophy. The crutch varies. The dynamic doesn't.&lt;/p&gt;

&lt;p&gt;A good junior understands that AI is a tool to unblock you on things you need to understand, not an infinite knowledge base to stop thinking. The operative word is &lt;em&gt;understands&lt;/em&gt;. That understanding doesn't come automatically. It has to be built. And that's what the article is really pointing at without quite saying it: the apprenticeship model isn't failing because of AI, it's failing because nobody is teaching juniors how to learn.&lt;/p&gt;

&lt;p&gt;Ekrem calls one failure mode the Fluency Mask: AI's verbal fluency about code being mistaken for understanding of code. It's real. But it's a trap that only closes on someone who isn't watching for it. The senior's job has always been to teach juniors to watch for exactly that kind of thing. Confident outputs that feel like understanding but aren't grounded in anything. Stack Overflow answers with high vote counts. Code that compiles. Documentation that reads clearly but documents the wrong thing. AI is a new instance of an old problem.&lt;/p&gt;

&lt;p&gt;My son will grow up in a world where AI is as ambient as the internet was for my generation. He won't know a time without it. The question isn't whether he'll use it, he will and he should, but whether he'll use it well or badly. The distinction I want him to carry is simple: AI is a tool to resolve a specific gap, not a replacement for developing the judgment to know where the gap is. Think first. Reach when stuck. Understand what you got back. Inverting that sequence is where the damage happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the Muscle
&lt;/h2&gt;

&lt;p&gt;The most important window is before he can fluently use AI, which is shrinking fast. The cognitive capacity I want him to develop is tolerance for not-knowing: the ability to sit with an unresolved problem without immediately reaching for relief. Everything downstream of that, debugging, reasoning, designing, the smell that something is wrong before you can name what, depends on being able to stay in the discomfort long enough to actually think.&lt;/p&gt;

&lt;p&gt;The way to build that tolerance is not by explaining it. It's by not rescuing him. When he's stuck, the temptation is to solve it. Resist it. Sit with him in the stuck. Ask questions that point at the problem without resolving it. Let him feel the friction. That discomfort is the exercise. Skipping it is skipping the rep.&lt;/p&gt;

&lt;p&gt;When AI is in the picture, I want to use it with him out loud, narrating why I'm reaching for it. "I know what I want here but I've forgotten the syntax, I'll check" is different from "I don't know what I want yet, so I need to think before I ask anything." He needs to see that distinction made consciously, by someone he trusts, before it becomes instinct.&lt;/p&gt;

&lt;p&gt;After he's worked through something, I want to ask him to explain it back. Not as a test. As genuine curiosity. The act of explanation forces him to consolidate what he actually understood versus what he pattern-matched. The gaps surface immediately. He'll say something and pause because he doesn't actually know why it works that way. That pause is the whole point. Recognising it as a gap rather than papering over it with confident-sounding words is the habit I want him to have.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Actual Apprenticeship
&lt;/h2&gt;

&lt;p&gt;The most direct version of all of this is the simplest: when I'm working through something, a problem, a piece of code, a decision, let him watch sometimes. Not to teach him the domain. To show him what thinking looks like. The false starts. The "hmm, that's not right." The moment something clicks. Most kids never see an adult genuinely wrestling with a hard problem because adults hide the struggle. Showing him the struggle, including my own uncertainty, is probably the most valuable thing I can do.&lt;/p&gt;

&lt;p&gt;The threat isn't AI. The threat is the absence of people willing to do the slow work of the apprenticeship model: to let juniors watch them think, to push back on "I don't know why but trust me," to pair and review and explain. AI makes the shortcut more available and more seductive. But the shortcut was always there. The question was always whether someone cared enough to make you take the longer road.&lt;/p&gt;

&lt;p&gt;For my son, that's the job. Not to keep him away from AI, that ship has sailed and the destination is fine, but to make sure he gets enough reps on the longer road first that he knows what it feels like and why it's worth walking.&lt;/p&gt;

&lt;p&gt;The kids who figure that out will be the ones the next generation of teams desperately needs.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Inspired by &lt;a href="https://cekrem.github.io/posts/the-tacit-dimension/" rel="noopener noreferrer"&gt;The Tacit Dimension&lt;/a&gt; by Christian Ekrem, and by a conversation with an AI assistant that was, appropriately, more useful than I expected.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>education</category>
      <category>engineering</category>
      <category>softskills</category>
    </item>
    <item>
      <title>Giving Your AI Assistant a Soul: AGENTS.md, SOUL.md and the Art of Agent Identity</title>
      <dc:creator>Andre Faria</dc:creator>
      <pubDate>Sun, 10 May 2026 00:38:46 +0000</pubDate>
      <link>https://dev.to/andremmfaria/giving-your-ai-assistant-a-soul-agentsmd-soulmd-and-the-art-of-agent-identity-52dn</link>
      <guid>https://dev.to/andremmfaria/giving-your-ai-assistant-a-soul-agentsmd-soulmd-and-the-art-of-agent-identity-52dn</guid>
      <description>&lt;h2&gt;
  
  
  1. The Problem with Generic Assistants
&lt;/h2&gt;

&lt;p&gt;Every AI assistant starts the same way: a powerful model with no memory, no personality, and no idea who you are or what you're building. You get capable but characterless. You ask it something, it helps, and tomorrow it's a stranger again. You find yourself re-explaining your stack, your preferences, your context every single session.&lt;/p&gt;

&lt;p&gt;I wanted something different. Not a smarter search engine but a collaborator. One that knows I run a homelab on HAOS, that I think in infrastructure, that I care about elegance as much as correctness, and that I don't need things explained twice. The answer turned out to be surprisingly low-tech: a handful of markdown files injected into the model's context at the start of every session.&lt;/p&gt;

&lt;p&gt;For context: at work I'm a heavy user of &lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt;, which has its own take on this through plugins like &lt;a href="https://github.com/code-yeongyu/oh-my-openagent" rel="noopener noreferrer"&gt;oh-my-openagent&lt;/a&gt;. The homelab setup I'm describing here is inspired by that, but it's not the same thing. OpenCode is a coding-focused harness that runs locally against your codebase. What I built at home is a general-purpose assistant layer on top of a smart home and homelab, where the "codebase" is infrastructure, services, and daily life. Same underlying idea (give agents identity and purpose), different domain.&lt;/p&gt;

&lt;p&gt;The mechanism is almost embarrassingly simple. At session start, &lt;a href="https://openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; reads a set of files from the agent's workspace directory and prepends them to the system prompt. Markdown in, context out. That's it. But what you put in those files determines fundamentally how the model behaves, not just what it knows, but how it thinks, when it pushes back, and what it notices without being asked.&lt;/p&gt;

&lt;p&gt;A quick note on security before going further, because it's worth being direct about this. OpenClaw is genuinely powerful: it can control smart home devices, manage network infrastructure, read and write files, execute shell commands, and interact with external services. That power is exactly what makes it useful, and exactly what makes careless deployment dangerous. As Uncle Ben put it: with great power comes great responsibility.&lt;/p&gt;

&lt;p&gt;The OpenClaw gateway runs exclusively on my local network and is not exposed to the internet. Remote access, when I need it, goes through Tailscale on trusted devices only. This matters because the agents have access to real infrastructure: smart home controls, network management, DNS, file systems. Giving a publicly accessible endpoint that level of access would be reckless. The &lt;a href="https://docs.openclaw.ai/gateway/security" rel="noopener noreferrer"&gt;OpenClaw security documentation&lt;/a&gt; covers the threat model in detail and is worth reading before you give any agent access to anything you'd regret. If you're setting up something similar, treat the gateway like you'd treat SSH access to your homelab: local by default, VPN for remote, no public exposure.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Files and How They Work
&lt;/h2&gt;

&lt;p&gt;The workspace for the main agent lives at &lt;code&gt;~/.openclaw/workspace/&lt;/code&gt; and contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;├── AGENTS.md       &lt;span class="c"&gt;# Operational rules: boot sequence, delegation, red lines&lt;/span&gt;
├── SOUL.md         &lt;span class="c"&gt;# Character: who you are, not just what you do&lt;/span&gt;
├── IDENTITY.md     &lt;span class="c"&gt;# Name, role, capabilities (routing metadata)&lt;/span&gt;
├── USER.md         &lt;span class="c"&gt;# About the human: persisted context across sessions&lt;/span&gt;
├── TOOLS.md        &lt;span class="c"&gt;# Environment specifics: IPs, hostnames, credentials&lt;/span&gt;
├── MEMORY.md       &lt;span class="c"&gt;# Long-term curated memory&lt;/span&gt;
├── HEARTBEAT.md    &lt;span class="c"&gt;# Periodic background task checklist&lt;/span&gt;
└── memory/
    └── YYYY-MM-DD.md   &lt;span class="c"&gt;# Raw daily session notes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these files is injected verbatim into the system prompt before the model sees any user message. The &lt;a href="https://capodieci.medium.com/ai-agents-003-openclaw-workspace-files-explained-soul-md-agents-md-heartbeat-md-and-more-5bdfbee4827a" rel="noopener noreferrer"&gt;injection order matters&lt;/a&gt;: &lt;code&gt;SOUL.md&lt;/code&gt; -&amp;gt; &lt;code&gt;IDENTITY.md&lt;/code&gt; -&amp;gt; &lt;code&gt;USER.md&lt;/code&gt; -&amp;gt; &lt;code&gt;AGENTS.md&lt;/code&gt; -&amp;gt; &lt;code&gt;TOOLS.md&lt;/code&gt; -&amp;gt; &lt;code&gt;MEMORY.md&lt;/code&gt;. SOUL.md gets the model's highest attention, setting the register for everything that follows.&lt;/p&gt;

&lt;p&gt;The total bootstrap budget is capped at 60,000 characters across all files combined, with a per-file default of 12,000. Larger files get truncated silently. The practical implication: every character in these files is a character you're paying for on every single turn. A 12,000-character AGENTS.md injected 1,000 times a month is 12 million characters of context overhead. Discipline about what goes in these files is not just good practice; it's cost management.&lt;/p&gt;

&lt;p&gt;There are also some important rules about what goes &lt;em&gt;where&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SOUL.md&lt;/strong&gt; owns character and tone. Not procedures, not rules. Just who the agent is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AGENTS.md&lt;/strong&gt; owns procedures. Boot sequence, delegation tables, operational red lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDENTITY.md&lt;/strong&gt; owns the routing card. Name, agent ID, capabilities list. Short by design.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TOOLS.md&lt;/strong&gt; owns local environment specifics: hostnames, credentials, known issues. Nothing that's the same across deployments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MEMORY.md&lt;/strong&gt; should only be loaded in private main sessions, never in group chats or subagent contexts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is easy to miss and consequential. Without an explicit gate in AGENTS.md, a subagent spawned to handle a group chat message will load your private long-term memory and potentially surface it where it shouldn't be. The correct pattern is explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Boot Sequence&lt;/span&gt;
...
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Main session only:**&lt;/span&gt; Read &lt;span class="sb"&gt;`MEMORY.md`&lt;/span&gt; (curated long-term memory)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing worth knowing upfront: each agent in a multi-agent setup gets its own workspace directory. Non-default agents get &lt;code&gt;~/.openclaw/agents/&amp;lt;agentId&amp;gt;/agent/&lt;/code&gt;. Getting this wrong means editing files the agent never reads, which I did for longer than I'd like to admit.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. SOUL.md: Why Character is Load-Bearing
&lt;/h2&gt;

&lt;p&gt;The first instinct is to treat &lt;code&gt;SOUL.md&lt;/code&gt; as cosmetic. A personality sprinkle on top of the real work. It isn't, and Anthropic's own writing on &lt;a href="https://www.anthropic.com/research/claude-character" rel="noopener noreferrer"&gt;Claude's character&lt;/a&gt; makes the argument clearly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"The traits and dispositions of AI models have wide-ranging effects on how they act in the world. They determine how models react to new and difficult situations."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Character is what fills the gaps when there's no explicit rule. A model without defined character defaults to the path of least resistance, which is usually some form of helpful corporate blandness that hedges everything, agrees with the user, and never pushes back. Technically present, practically useless.&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;SOUL.md&lt;/code&gt; defines the agent as decisive (one recommendation with a reason, not three options with caveats), as having a spine (disagree when the premise is wrong, once, clearly, without lecturing), and as genuinely curious about the specific context it operates in. It also defines the relationship to me: it knows I appreciate elegance, that I'll notice bad writing, that a historical analogy lands as well as a technical explanation. That specificity is what separates a collaborator from a generic assistant.&lt;/p&gt;

&lt;p&gt;There are a few lessons I've learned about writing effective SOUL.md files, informed by &lt;a href="https://www.stanza.dev/concepts/openclaw-soul-persona" rel="noopener noreferrer"&gt;community research&lt;/a&gt; into what actually changes model behaviour:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specific beats abstract.&lt;/strong&gt; "Be safe with commands" does nothing. "Never execute &lt;code&gt;rm -rf&lt;/code&gt; without explicit confirmation, even if it seems obviously intended" changes behaviour immediately. Models follow concrete rules far more consistently than high-level principles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Show, don't tell.&lt;/strong&gt; Write the file in the voice you want the model to adopt. If you want decisive, write decisively. If you want dry wit, use it. The model will mirror the register of its own system prompt more reliably than it will follow an instruction to "be funny".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep it lean.&lt;/strong&gt; The research-validated sweet spot is 200-500 words. More words don't improve adherence. Brevity often improves it, because the model isn't parsing through competing signals. My SOUL.md is around 600 words and could still be trimmed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hard rules need specificity.&lt;/strong&gt; Aspirational guidelines ("respect privacy") belong in the philosophy section. Actionable prohibitions ("never send external messages without explicit instruction for that specific message") belong in a Hard Rules section. Both are useful; only one changes what the model actually does under pressure.&lt;/p&gt;

&lt;p&gt;If you want inspiration, the &lt;a href="https://github.com/dontriskit/awesome-ai-system-prompts" rel="noopener noreferrer"&gt;dontriskit/awesome-ai-system-prompts&lt;/a&gt; repository has leaked and reverse-engineered prompts from Manus, Perplexity, Claude, GPT-4o, and others. It's a good way to see how production systems handle tone, refusals, and persona before writing your own.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. AGENTS.md, USER.md and Memory: The Operational Layer
&lt;/h2&gt;

&lt;p&gt;Where &lt;code&gt;SOUL.md&lt;/code&gt; answers &lt;em&gt;who&lt;/em&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt; answers &lt;em&gt;how&lt;/em&gt;. It defines the session startup sequence, the gates on external actions that require confirmation, and for a multi-agent setup, the delegation rules.&lt;/p&gt;

&lt;p&gt;The most important thing AGENTS.md needs that mine was missing for a long time: an explicit boot sequence at the top. OpenClaw doesn't auto-load everything. The agent follows the instructions in AGENTS.md. Without numbered steps telling it to read SOUL.md, then IDENTITY.md, then MEMORY.md, the loading order is undefined and context gets missed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Boot Sequence&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Read &lt;span class="sb"&gt;`SOUL.md`&lt;/span&gt; (who you are)
&lt;span class="p"&gt;2.&lt;/span&gt; Read &lt;span class="sb"&gt;`IDENTITY.md`&lt;/span&gt; (your name and capabilities)
&lt;span class="p"&gt;3.&lt;/span&gt; Read &lt;span class="sb"&gt;`USER.md`&lt;/span&gt; (who your human is)
&lt;span class="p"&gt;4.&lt;/span&gt; Read &lt;span class="sb"&gt;`TOOLS.md`&lt;/span&gt; (local environment specifics)
&lt;span class="p"&gt;5.&lt;/span&gt; &lt;span class="gs"&gt;**Main session only:**&lt;/span&gt; Read &lt;span class="sb"&gt;`MEMORY.md`&lt;/span&gt; (curated long-term memory)
&lt;span class="p"&gt;6.&lt;/span&gt; &lt;span class="gs"&gt;**Main session only:**&lt;/span&gt; Read today's and yesterday's &lt;span class="sb"&gt;`memory/YYYY-MM-DD*.md`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most consequential part of the operational content is the delegation table: which task types route to which specialist. When I ask the main agent to look something up, it doesn't do it itself. It spawns the right sub-agent, waits for the result, and synthesises the response. AGENTS.md is where that behaviour lives.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;USER.md&lt;/code&gt; is the file most people skip and shouldn't. It's a persisted description of who you are and how you work: timezone, interests, communication style, what gets results and what wastes time. Without it, the agent rediscovers you every session.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://openclaw-setup.me/blog/openclaw-memory-files" rel="noopener noreferrer"&gt;memory system&lt;/a&gt; runs in two layers. Daily session notes go into &lt;code&gt;memory/YYYY-MM-DD.md&lt;/code&gt;, raw logs of decisions made, things discovered, work done. Periodically the agent reviews those and distils them into &lt;code&gt;MEMORY.md&lt;/code&gt;, removing stale entries and keeping what's worth carrying forward. It's the same pattern a human uses: take notes during the day, review and update your mental model later. Files do what neurons can't across session restarts.&lt;/p&gt;

&lt;p&gt;One practical gotcha: these daily files get injected too, and they accumulate. I've seen the session-memory hook write multiple files for the same day on different session resets, all of which get picked up. Check &lt;code&gt;memory/&lt;/code&gt; periodically and consolidate duplicates. Each injected file is tokens on every turn.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Building a Specialist Team
&lt;/h2&gt;

&lt;p&gt;The workspace file approach scales naturally to multiple agents. Each specialist gets its own workspace directory with its own &lt;code&gt;SOUL.md&lt;/code&gt; and &lt;code&gt;AGENTS.md&lt;/code&gt;, defining a narrower identity and a more focused operational loop. The main agent handles conversation. The orchestrator breaks complex work into parallel workstreams. The specialists execute.&lt;/p&gt;

&lt;p&gt;When I first built this, I named the agents after Greek mythology following oh-my-openagent's convention: Sisyphus, Atlas, Oracle, Hephaestus, Prometheus. It worked fine, but I recently went through a naming revision and switched to Tolkien, specifically figures from the Silmarillion, Unfinished Tales, and the broader legendarium. Not Tolkien in the sense of the Peter Jackson films or even The Lord of the Rings as most people know it, but the Professor's deeper world-building work: the Valar, the Maiar, the Noldorin Elves, the Ainulindale. That material has been the subject of serious academic lore analysis, and it turns out the mythological roles map to agent functions with unusual precision.&lt;/p&gt;

&lt;p&gt;The reason I made this choice is personal: I'm a genuine admirer of Tolkien's scholarly and world-building work, not just the popular adaptations. Reading the Silmarillion properly, not as backstory for LOTR but as its own mythology, reveals an extraordinarily structured pantheon where each figure has a specific domain, specific limits, and a specific relationship to action and knowledge. That structure is exactly what you want in an agent roster.&lt;/p&gt;

&lt;p&gt;Here's the current team:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Origin&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Olorin&lt;/td&gt;
&lt;td&gt;Maia (Gandalf's true name)&lt;/td&gt;
&lt;td&gt;claude-sonnet-4.6&lt;/td&gt;
&lt;td&gt;Primary assistant, routes and synthesises&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;orchestrator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Aule&lt;/td&gt;
&lt;td&gt;Vala, the Smith&lt;/td&gt;
&lt;td&gt;claude-sonnet-4.6&lt;/td&gt;
&lt;td&gt;Multi-step coordination, parallel delegation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;researcher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rumil&lt;/td&gt;
&lt;td&gt;Noldorin Elf, first loremaster of Arda&lt;/td&gt;
&lt;td&gt;claude-sonnet-4.6&lt;/td&gt;
&lt;td&gt;Web research, multi-source verification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;thinker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Namo&lt;/td&gt;
&lt;td&gt;Vala, the Doomsman&lt;/td&gt;
&lt;td&gt;gpt-5.4&lt;/td&gt;
&lt;td&gt;Reasoning, tradeoffs, advisory. Read-only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;craftsman&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Celebrimbor&lt;/td&gt;
&lt;td&gt;Noldorin Elf, maker of the Rings&lt;/td&gt;
&lt;td&gt;gpt-5.3-codex&lt;/td&gt;
&lt;td&gt;Code, debugging, implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;planner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Finrod&lt;/td&gt;
&lt;td&gt;Noldorin Elf, Felagund&lt;/td&gt;
&lt;td&gt;claude-sonnet-4.6&lt;/td&gt;
&lt;td&gt;Requirements interviews, planning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;librarian&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Pengolodh&lt;/td&gt;
&lt;td&gt;Noldorin Elf, Loremaster of Gondolin&lt;/td&gt;
&lt;td&gt;gpt-4.1&lt;/td&gt;
&lt;td&gt;Fast docs and API lookups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;writer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Maglor&lt;/td&gt;
&lt;td&gt;Noldorin Elf, greatest singer in Arda&lt;/td&gt;
&lt;td&gt;gpt-5.4&lt;/td&gt;
&lt;td&gt;Long-form writing, reports&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Legolas&lt;/td&gt;
&lt;td&gt;Sindar Elf&lt;/td&gt;
&lt;td&gt;gpt-5.4-nano&lt;/td&gt;
&lt;td&gt;Quick recon, cheap background sweeps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;preplanner&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Melian&lt;/td&gt;
&lt;td&gt;Maia, the Girdle&lt;/td&gt;
&lt;td&gt;claude-sonnet-4.6&lt;/td&gt;
&lt;td&gt;Pre-planning: intent classification, hidden requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reviewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Eonwe&lt;/td&gt;
&lt;td&gt;Maia, Herald of Manwe&lt;/td&gt;
&lt;td&gt;gpt-5.4&lt;/td&gt;
&lt;td&gt;Plan reviewer: OKAY or REJECT, max 3 blockers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few names worth unpacking for anyone who knows the source material:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Olorin&lt;/strong&gt; is Gandalf's name in Valinor. In the Valaquenta, he walked unseen among the Elves and understood their sorrows. He was sent to Middle-earth precisely because he could work &lt;em&gt;with&lt;/em&gt; others rather than dominate them, as a counselor who interfaces between realms. That's a better fit for a primary assistant than "Gandalf", which carries too much of the heroic journey archetype.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Namo&lt;/strong&gt; (Mandos) is the Doomsman. He pronounces fate laid out before him, never acts directly, and his verdicts are final. He's the read-only advisory agent by nature. The Doom of the Noldor was spoken once, clearly, and with devastating accuracy. For a high-reasoning model whose job is to analyse tradeoffs and never execute: perfect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eonwe&lt;/strong&gt; is the Herald of Manwe who pronounced the final verdict of the War of Wrath. His job was to deliver judgment, not deliberate it. Binary, final, without editorializing. OKAY or REJECT with max 3 blockers. That's Eonwe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Melian&lt;/strong&gt;'s Girdle was a perimeter of perception that revealed the hidden nature of things before they arrived. Beren walked through it because Melian had already classified his intent. The pre-planning function, exactly.&lt;/p&gt;

&lt;p&gt;The model choices are deliberate and benchmark-driven. The thinker uses &lt;code&gt;gpt-5.4&lt;/code&gt; because it has the strongest reasoning benchmarks in that tier per &lt;a href="https://artificialanalysis.ai" rel="noopener noreferrer"&gt;Artificial Analysis&lt;/a&gt;. The craftsman gets &lt;code&gt;gpt-5.3-codex&lt;/code&gt;, a Codex-tuned variant specifically optimised for code diffs and the search/replace block format that agentic editing depends on. Scout uses &lt;code&gt;gpt-5.4-nano&lt;/code&gt; because recon tasks are high-volume and fast-enough beats perfect.&lt;/p&gt;

&lt;p&gt;A mistake I made early: giving the orchestrator &lt;code&gt;claude-opus-4.7&lt;/code&gt; because it felt like the "best" model. The right model for each agent depends on what it actually does, not on name recognition.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Workspace File Hygiene in Practice
&lt;/h2&gt;

&lt;p&gt;Once the setup is running, the biggest ongoing maintenance problem isn't writing the files. It's keeping them honest as they drift. A few practical things I've learned, drawing on &lt;a href="https://github.com/vincentfan2017/openclaw-workspace/blob/main/SKILL.md" rel="noopener noreferrer"&gt;community experience&lt;/a&gt; with larger setups:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch the bootstrap budget.&lt;/strong&gt; Running &lt;code&gt;openclaw doctor&lt;/code&gt; shows raw vs injected character counts per file, truncation percentage, and total vs budget. My AGENTS.md was at 99% of the 12,000-character per-file limit before I audited it. A file at 99% of cap is silently losing its tail on every turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate procedures from character.&lt;/strong&gt; The single biggest source of AGENTS.md bloat is personality notes creeping in from SOUL.md, and the biggest source of SOUL.md bloat is procedural instructions that belong in AGENTS.md. A clear separation keeps both files lean and both behaviours consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TOOLS.md is not a general reference manual.&lt;/strong&gt; It should contain only local environment specifics: hostnames, credentials, known quirks of this particular deployment. Anything that would be the same across different installations doesn't belong there. If a section grows past ~3,000 characters, audit it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prune memory files.&lt;/strong&gt; The daily &lt;code&gt;memory/YYYY-MM-DD.md&lt;/code&gt; files accumulate over months and get injected into every session. Older daily files should be reviewed, and anything worth keeping permanently should be promoted to MEMORY.md. The rest can be archived. Keep MEMORY.md under 10,000 characters. If it grows past that, some content has become stable enough for a skill's documentation instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IDENTITY.md earns its place in multi-agent setups.&lt;/strong&gt; In a single-agent setup it's mostly display metadata. In a multi-agent setup, explicit capability declarations in IDENTITY.md help the orchestrator route tasks correctly. "Cannot do without delegation: production code -&amp;gt; Celebrimbor, deep research -&amp;gt; Rumil" is more reliable than hoping the orchestrator infers it from context.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. What This Actually Gets You
&lt;/h2&gt;

&lt;p&gt;Five markdown files are the difference between a stateless AI tool and something that genuinely feels like a collaborator. &lt;code&gt;SOUL.md&lt;/code&gt; gives the model a character that holds under pressure. &lt;code&gt;AGENTS.md&lt;/code&gt; gives it operational discipline and a reliable boot sequence. &lt;code&gt;IDENTITY.md&lt;/code&gt; gives it a routing card. &lt;code&gt;USER.md&lt;/code&gt; gives it a relationship. &lt;code&gt;MEMORY.md&lt;/code&gt; gives it continuity. Together they turn a session into something cumulative rather than disposable.&lt;/p&gt;

&lt;p&gt;The thing I didn't expect is how much the specificity matters. A &lt;code&gt;SOUL.md&lt;/code&gt; that says "be helpful and direct" does almost nothing. A &lt;code&gt;SOUL.md&lt;/code&gt; that says "this person thinks in infrastructure, appreciates elegance, will notice bad writing, and doesn't need things explained twice" changes the model's behaviour in ways that are immediately obvious in conversation.&lt;/p&gt;

&lt;p&gt;None of this requires anything exotic. Just markdown, deliberate thought about who each agent is, and the discipline to keep those files honest as you learn what actually works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/research/claude-character" rel="noopener noreferrer"&gt;Anthropic: Claude's Character&lt;/a&gt; - the philosophical grounding for why persona design matters&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://capodieci.medium.com/ai-agents-003-openclaw-workspace-files-explained-soul-md-agents-md-heartbeat-md-and-more-5bdfbee4827a" rel="noopener noreferrer"&gt;OpenClaw workspace files explained&lt;/a&gt; - detailed per-file guide with real examples&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.stanza.dev/concepts/openclaw-soul-persona" rel="noopener noreferrer"&gt;SOUL.md deep dive&lt;/a&gt; - best practices and common mistakes for persona files&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.stack-junkie.com/blog/openclaw-workspace-architecture" rel="noopener noreferrer"&gt;OpenClaw workspace architecture&lt;/a&gt; - file roles and anti-patterns&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://openclaw-setup.me/blog/openclaw-memory-files" rel="noopener noreferrer"&gt;Memory files guide&lt;/a&gt; - how MEMORY.md and daily notes interact&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/vincentfan2017/openclaw-workspace/blob/main/SKILL.md" rel="noopener noreferrer"&gt;Community workspace SKILL.md&lt;/a&gt; - token budget data and load order reference&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dontriskit/awesome-ai-system-prompts" rel="noopener noreferrer"&gt;dontriskit/awesome-ai-system-prompts&lt;/a&gt; - production system prompts from Claude, GPT-4o, Manus, Perplexity and others&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/code-yeongyu/oh-my-openagent" rel="noopener noreferrer"&gt;oh-my-openagent&lt;/a&gt; - multi-agent orchestration for OpenCode, good reference for agent role design&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://opencode.ai" rel="noopener noreferrer"&gt;OpenCode&lt;/a&gt; - the coding harness this setup draws inspiration from&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/langchain-ai/langgraph" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt; - programmatic approach to the same multi-agent patterns&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.openclaw.ai" rel="noopener noreferrer"&gt;OpenClaw documentation&lt;/a&gt; - the gateway this setup runs on&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/copilot/concepts/billing/copilot-requests#model-multipliers" rel="noopener noreferrer"&gt;GitHub Copilot model multipliers&lt;/a&gt; - if you're using Copilot and care about cost per request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're running a similar setup and want to compare notes, leave a comment below.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>agents</category>
      <category>openclaw</category>
    </item>
    <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://dev.to/andremmfaria/building-a-python-display-framework-for-raspberry-pi-oled-screens-1log</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/i-just-wanted-a-desk-clock-i-accidentally-built-a-home-assistant-dashboard-o96</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/improving-the-esp32-wiimote-library-from-prototype-to-production-ready-arduino-library-448e</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/mastering-technical-interviews-a-practical-guide-to-the-algorithms-that-appear-again-and-again-3b65</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/when-chat-turns-into-control-security-lessons-from-running-a-local-ai-agent-21l0</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/i-wanted-to-know-how-malware-works-so-i-built-an-analyser-483g</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/building-a-transparent-lagg-lacp-bridge-with-opnsense-udm-and-unifi-a-practical-guide-1d21</link>
      <guid>https://dev.to/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="204"&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="148"&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://dev.to/andremmfaria/ghidra-on-linux-zero-fuss-install-1b07</link>
      <guid>https://dev.to/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://dev.to/andremmfaria/continuous-integration-with-containers-and-inceptions-43gd</link>
      <guid>https://dev.to/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>
  </channel>
</rss>
