<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Alex Rosito</title>
    <description>The latest articles on Forem by Alex Rosito (@alexrosito67).</description>
    <link>https://forem.com/alexrosito67</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%2F3903394%2F8e90cfff-2f31-4948-ad06-ae6c14594087.png</url>
      <title>Forem: Alex Rosito</title>
      <link>https://forem.com/alexrosito67</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alexrosito67"/>
    <language>en</language>
    <item>
      <title>The Constraint Is Gone. The Discipline Isn't Optional."</title>
      <dc:creator>Alex Rosito</dc:creator>
      <pubDate>Sun, 03 May 2026 04:21:50 +0000</pubDate>
      <link>https://forem.com/alexrosito67/the-constraint-is-gone-the-discipline-isnt-optional-cl9</link>
      <guid>https://forem.com/alexrosito67/the-constraint-is-gone-the-discipline-isnt-optional-cl9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is part three of a series on display consistency in embedded systems. The first two parts were technical. This one is about why the technical parts worked.&lt;/em&gt;&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;The picture: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ATtiny85 thermometer. Neural network inference. QUAD7SHIFT display. Built from datasheets.&lt;/p&gt;

&lt;p&gt;"For the skeptics:"&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;Scanning&lt;/span&gt; &lt;span class="n"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Dependency&lt;/span&gt; &lt;span class="n"&gt;Graph&lt;/span&gt;
&lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="n"&gt;nnetwork&lt;/span&gt;
&lt;span class="o"&gt;|--&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt; &lt;span class="mf"&gt;1.0.7&lt;/span&gt;
&lt;span class="n"&gt;Building&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;release&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt;
&lt;span class="n"&gt;Compiling&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pio&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;attiny85&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="n"&gt;Linking&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pio&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;attiny85&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elf&lt;/span&gt;
&lt;span class="n"&gt;Checking&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pio&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;attiny85&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elf&lt;/span&gt;
&lt;span class="n"&gt;Advanced&lt;/span&gt; &lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;Usage&lt;/span&gt; &lt;span class="n"&gt;is&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="s"&gt;"PlatformIO Home &amp;gt; Project Inspect"&lt;/span&gt;
&lt;span class="n"&gt;RAM&lt;/span&gt;&lt;span class="o"&gt;:&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="mf"&gt;10.9&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Flash&lt;/span&gt;&lt;span class="o"&gt;:&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="mf"&gt;57.6&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="mi"&gt;4718&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="mi"&gt;8192&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Configuring&lt;/span&gt; &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;AVAILABLE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;usbtiny&lt;/span&gt;
&lt;span class="n"&gt;CURRENT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;upload_protocol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;usbtiny&lt;/span&gt;
&lt;span class="n"&gt;Looking&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;Uploading&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pio&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;attiny85&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;firmware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hex&lt;/span&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Steve Wozniak Didn't Have a Framework
&lt;/h2&gt;

&lt;p&gt;He had datasheets.&lt;/p&gt;

&lt;p&gt;No Stack Overflow. No libraries to install. No AI to generate boilerplate. No tutorials that abstracted away the inconvenient parts. Just component specifications, logic, and the discipline to read until he understood.&lt;/p&gt;

&lt;p&gt;The result was an architecture that engineers still study today.&lt;/p&gt;

&lt;p&gt;That's not a coincidence. And it's not genius that can't be replicated. It's what happens when you have no choice but to understand the hardware before you write the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Gets Lost When the Abstraction Hides Too Much
&lt;/h2&gt;

&lt;p&gt;Modern embedded development has never been more accessible. Frameworks, package managers, community libraries — you can have a working prototype in an afternoon without understanding a single register.&lt;/p&gt;

&lt;p&gt;That's genuinely useful. It lowers the barrier. It lets more people build things.&lt;/p&gt;

&lt;p&gt;But it also produces a generation of firmware that works until it doesn't — and when it doesn't, nobody knows why. Because the person who wrote it never read the datasheet. They installed a library, called a function, and assumed the details were handled.&lt;/p&gt;

&lt;p&gt;Sometimes they are. Sometimes they aren't. And the difference between those two cases is invisible until something goes wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Counter That Stayed With Me
&lt;/h2&gt;

&lt;p&gt;There's a specific moment that illustrates this better than any argument.&lt;/p&gt;

&lt;p&gt;A teacher handed out breadboards, components, and a schematic. The goal: build a single-digit counter, 0 to 9, triggered by a button press.&lt;/p&gt;

&lt;p&gt;No microcontroller. No code. Just logic gates, a flip-flop, a seven-segment display, and the understanding of why each component was there.&lt;/p&gt;

&lt;p&gt;When it worked — when pressing the button changed the digit — something became clear that no textbook had made clear before: the hardware responds to principles, not to instructions. If you understand the principles, the hardware does what you expect. If you don't, you're guessing.&lt;/p&gt;

&lt;p&gt;That lesson is older than Arduino. It's older than C. It applies today exactly as it did then.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Has to Do With QUAD7SHIFT
&lt;/h2&gt;

&lt;p&gt;QUAD7SHIFT is a small Arduino library. It drives a four-digit seven-segment display through two cascaded 74HC595 shift registers. It has zero stars on GitHub from the day it was published until a Chinese technical site cited it as a reference implementation for eliminating display flicker — without being asked, without knowing the author, because they were looking for something that worked correctly and found it.&lt;/p&gt;

&lt;p&gt;It works correctly because before writing a single line of code, the 74HC595 datasheet was read. The latch mechanism was understood. The difference between the shift register and the storage register was clear. The decision to transfer 16 bits atomically and pulse the latch once was a direct consequence of that understanding — not a clever optimization, not a trick. Just the obvious thing to do when you know what the hardware does.&lt;/p&gt;

&lt;p&gt;The flicker that plagues most 74HC595 display drivers has been showing up in Arduino forums since 2016. Thousands of views. Dozens of threads. The symptom described repeatedly, the root cause never identified — because the root cause is in the code, and the code was written without reading the datasheet.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Principle Doesn't Age
&lt;/h2&gt;

&lt;p&gt;Processors change. Frameworks come and go. The 74HC595 is a 40-year-old chip that still ships in millions of units per year because the underlying logic — shift data in, latch it out atomically — is correct and has always been correct.&lt;/p&gt;

&lt;p&gt;The engineers who understand that logic write drivers that work. The engineers who skip it write drivers that mostly work, until they don't.&lt;/p&gt;

&lt;p&gt;This is not an argument against modern tools. Use frameworks. Use libraries. Use AI assistants. They save real time and they solve real problems.&lt;/p&gt;

&lt;p&gt;But know what's underneath. Read the datasheet at least once. Understand what the latch does before you decide when to pulse it. Know why &lt;code&gt;shiftOut()&lt;/code&gt; is bit-banging and what that means for interrupt safety.&lt;/p&gt;

&lt;p&gt;The tools change. The hardware doesn't lie.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old School Isn't Obsolete. It's the Foundation.
&lt;/h2&gt;

&lt;p&gt;Wozniak had datasheets. The engineers who built CP/M had datasheets. The people who designed the TI-99/4A had datasheets. They understood their hardware completely because they had no alternative.&lt;/p&gt;

&lt;p&gt;That constraint produced discipline. That discipline produced systems that worked correctly for reasons their authors could explain.&lt;/p&gt;

&lt;p&gt;The constraint is gone. The discipline is optional now.&lt;/p&gt;

&lt;p&gt;But the hardware still responds to principles. And the principles haven't changed.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alex Rosito — self-taught electronics engineer. ATtiny85 · ESP32 · KiCad · C++&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;GitHub: &lt;a href="https://github.com/AlexRosito67" rel="noopener noreferrer"&gt;AlexRosito67&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>embeddedsystems</category>
      <category>arduino</category>
      <category>electronics</category>
      <category>hardware</category>
    </item>
    <item>
      <title>Frame Consistency in Embedded LED Systems **Why hardware drivers like MAX7219 don’t solve the real problem**</title>
      <dc:creator>Alex Rosito</dc:creator>
      <pubDate>Thu, 30 Apr 2026 17:48:45 +0000</pubDate>
      <link>https://forem.com/alexrosito67/frame-consistency-in-embedded-led-systems-why-hardware-drivers-like-max7219-dont-solve-the-real-10lj</link>
      <guid>https://forem.com/alexrosito67/frame-consistency-in-embedded-led-systems-why-hardware-drivers-like-max7219-dont-solve-the-real-10lj</guid>
      <description>&lt;p&gt;&lt;em&gt;After the 74HC595 flicker article, someone will inevitably ask: "Why not just use a MAX7219? It handles everything in hardware." It's a fair question. The answer is more nuanced than most tutorials admit.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The MAX7219 Is Not a Solution to a Software Problem
&lt;/h2&gt;

&lt;p&gt;The MAX7219 is an excellent chip. It handles multiplexing internally, provides constant current regulation, has its own digit buffer, and communicates over SPI. Compared to two cascaded 74HC595s and hand-rolled multiplexing, it looks like a massive upgrade.&lt;/p&gt;

&lt;p&gt;And for hardware reliability, it is.&lt;/p&gt;

&lt;p&gt;But here's what nobody tells you: the MAX7219 doesn't know what a valid display state looks like. That's still your job.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the MAX7219 Actually Takes Off Your Plate
&lt;/h2&gt;

&lt;p&gt;To be fair, let's be specific about what it solves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiplexing timing&lt;/strong&gt; — handled internally, no refresh loop needed in firmware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ghosting from latch timing&lt;/strong&gt; — not a problem because the chip manages its own output stage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Brightness control&lt;/strong&gt; — built-in PWM, adjustable via SPI command&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constant current&lt;/strong&gt; — no current-limiting resistors per segment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's real. That's valuable. If you're moving from bit-banged 74HC595 code to MAX7219, you will see immediate improvement.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Doesn't Solve
&lt;/h2&gt;

&lt;p&gt;Here's the part that gets skipped.&lt;/p&gt;

&lt;p&gt;Most firmware that drives a multi-digit display still does something like this:&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="c1"&gt;// Update the display with new sensor reading&lt;/span&gt;
&lt;span class="n"&gt;max7219&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDigit&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;units&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;max7219&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDigit&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;tens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;max7219&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDigit&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="n"&gt;hundreds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;max7219&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thousands&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four separate SPI transactions. Four separate moments where the display is in a partially updated state.&lt;/p&gt;

&lt;p&gt;If an interrupt fires between digit 1 and digit 2 — maybe a timer ISR, maybe a sensor read, maybe a UART receive — the display briefly shows a mix of old and new data. The hardware is stable. The frame is not.&lt;/p&gt;

&lt;p&gt;The MAX7219 didn't create this problem. It just didn't eliminate it either.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Problem: No Concept of a Frame
&lt;/h2&gt;

&lt;p&gt;The issue is architectural. Most embedded display code has no explicit notion of a complete frame — a moment where all digits are updated atomically and the display shows a coherent state.&lt;/p&gt;

&lt;p&gt;Instead, it updates digits one at a time, sequentially, and assumes the whole thing happens fast enough that nobody notices. Usually that's true. Sometimes it isn't — and when it isn't, you get artifacts that look like hardware problems but aren't.&lt;/p&gt;

&lt;p&gt;This is the same class of problem as the 74HC595 latch issue, operating at a higher level.&lt;/p&gt;

&lt;p&gt;With 74HC595: the problem is at the shift register boundary — partial hardware state during transfer.&lt;br&gt;
With MAX7219: the problem is at the firmware boundary — partial logical state during multi-register update.&lt;/p&gt;

&lt;p&gt;Different layer, same root cause.&lt;/p&gt;




&lt;h2&gt;
  
  
  What QUAD7SHIFT Does That Generalizes
&lt;/h2&gt;

&lt;p&gt;When I built QUAD7SHIFT, I didn't think about any of this explicitly. I just transferred all 16 bits — segments and digit select — in one atomic SPI transaction, then pulsed the latch once. The display never saw a partial state because the design never created one.&lt;/p&gt;

&lt;p&gt;That rule — never expose a partial frame to the output stage — turns out to be hardware-independent.&lt;/p&gt;

&lt;p&gt;It applies to 74HC595. It applies to MAX7219. It applies to LED matrices driven by MAX7219 chains. It applies anywhere you have a display with more than one register to update.&lt;/p&gt;

&lt;p&gt;The implementation changes. The principle doesn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Practical Mental Model
&lt;/h2&gt;

&lt;p&gt;Think of it in two layers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it guarantees&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hardware driver (MAX7219)&lt;/td&gt;
&lt;td&gt;stable, flicker-free scanning of whatever is in its buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Firmware&lt;/td&gt;
&lt;td&gt;that what's in the buffer is always a coherent state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The MAX7219 is excellent at the first layer. It does nothing about the second.&lt;/p&gt;

&lt;p&gt;If your firmware never writes partial states — if every update is atomic — then the hardware driver has nothing to hide and everything works cleanly. If your firmware writes partial states, the hardware driver will display them faithfully, partial states and all.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Next Article
&lt;/h2&gt;

&lt;p&gt;This is part two of a series. The third part steps back from the hardware entirely — and asks why these principles work in the first place.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alex Rosito — self-taught electronics engineer. ATtiny85 · ESP32 · KiCad · C++&lt;/em&gt;&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>embeddedsystems</category>
      <category>cpp</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Built a Useless Data Structure in C++17 and Learned More Than Any Tutorial Taught Me</title>
      <dc:creator>Alex Rosito</dc:creator>
      <pubDate>Wed, 29 Apr 2026 05:39:57 +0000</pubDate>
      <link>https://forem.com/alexrosito67/i-built-a-useless-data-structure-in-c17-and-learned-more-than-any-tutorial-taught-me-e0i</link>
      <guid>https://forem.com/alexrosito67/i-built-a-useless-data-structure-in-c17-and-learned-more-than-any-tutorial-taught-me-e0i</guid>
      <description>&lt;p&gt;&lt;em&gt;I built this because I was bored. No real use case, no production target — just a C++17 exercise to shake off the rust and explore techniques I knew existed but had never actually used. What I didn't expect was how much the implementation taught me about things I thought I already understood.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Homogeneous Lists
&lt;/h2&gt;

&lt;p&gt;Every linked list tutorial shows you the same thing: a list of integers, or a list of strings. The node holds one type, the list holds nodes of that type, done.&lt;/p&gt;

&lt;p&gt;That works — until you want a single list to hold an integer, a float, a string, and a custom struct at the same time. Then the standard approach falls apart immediately, because the type is baked into the node at compile time.&lt;/p&gt;

&lt;p&gt;The question becomes: how do you build a node that doesn't care what it holds?&lt;/p&gt;




&lt;h2&gt;
  
  
  The Polymorphic Base — The "Wow" Moment
&lt;/h2&gt;

&lt;p&gt;The answer is a polymorphic base class. Instead of a templated node that holds &lt;code&gt;T&lt;/code&gt; directly, you separate the concerns:&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="c1"&gt;// Base class — type-erased, polymorphic&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&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="k"&gt;virtual&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&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;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Derived class — holds the actual value&lt;/span&gt;
&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="nl"&gt;public:&lt;/span&gt;
    &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;explicit&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;T&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&lt;/span&gt; &lt;span class="n"&gt;otherNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;dynamic_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&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;otherNode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;otherNode&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Object&lt;/code&gt; knows nothing about the type it holds — it only defines the interface. &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; knows the type, stores the value, and implements the interface.&lt;/p&gt;

&lt;p&gt;The list holds &lt;code&gt;Object*&lt;/code&gt; pointers. From the list's perspective, every node is an &lt;code&gt;Object&lt;/code&gt;. The actual type is invisible at that level — which is exactly what allows the list to be heterogeneous.&lt;/p&gt;

&lt;p&gt;When I saw this working for the first time — a single list printing an integer, then a float, then a string, then a struct, all through the same &lt;code&gt;listPrint()&lt;/code&gt; call — it was genuinely surprising. Not because it's magic, but because the abstraction is so clean.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;code&gt;dynamic_cast&lt;/code&gt; — Type-Safe Downcasting at Runtime
&lt;/h2&gt;

&lt;p&gt;This is the part that was completely new to me, and worth documenting carefully.&lt;/p&gt;

&lt;p&gt;Once you have a list of &lt;code&gt;Object*&lt;/code&gt; pointers, you lose type information at the list level. That's fine for printing — &lt;code&gt;print()&lt;/code&gt; is virtual, it dispatches correctly. But what about searching? If I want to find the integer &lt;code&gt;42&lt;/code&gt; in a mixed list, I need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Skip nodes that aren't integers&lt;/li&gt;
&lt;li&gt;Compare only against nodes that actually hold &lt;code&gt;int&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is exactly what &lt;code&gt;dynamic_cast&lt;/code&gt; does:&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="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;searchNodeInList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&lt;/span&gt; &lt;span class="n"&gt;typedNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;dynamic_cast&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typedNode&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;typedNode&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;value&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;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&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="nb"&gt;nullptr&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;&lt;code&gt;dynamic_cast&amp;lt;Node&amp;lt;T&amp;gt;*&amp;gt;(current)&lt;/code&gt; attempts to cast the &lt;code&gt;Object*&lt;/code&gt; pointer to a &lt;code&gt;Node&amp;lt;T&amp;gt;*&lt;/code&gt;. If the actual runtime type of the object is &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; — it succeeds and returns a valid pointer. If the actual type is something else — &lt;code&gt;Node&amp;lt;float&amp;gt;&lt;/code&gt;, &lt;code&gt;Node&amp;lt;std::string&amp;gt;&lt;/code&gt; — it returns &lt;code&gt;nullptr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That &lt;code&gt;nullptr&lt;/code&gt; check is the type filter. No exceptions, no undefined behavior — just a null pointer when the types don't match.&lt;/p&gt;

&lt;p&gt;This is the critical distinction from &lt;code&gt;static_cast&lt;/code&gt;: &lt;code&gt;static_cast&lt;/code&gt; trusts you at compile time. &lt;code&gt;dynamic_cast&lt;/code&gt; verifies at runtime. In a heterogeneous container where you genuinely don't know the runtime type of a node, &lt;code&gt;dynamic_cast&lt;/code&gt; is the correct tool — &lt;code&gt;static_cast&lt;/code&gt; would be undefined behavior waiting to happen.&lt;/p&gt;

&lt;p&gt;One requirement: the base class must have at least one virtual function. Without it, the compiler doesn't generate the runtime type information (RTTI) needed for &lt;code&gt;dynamic_cast&lt;/code&gt; to work. The virtual destructor in &lt;code&gt;Object&lt;/code&gt; satisfies this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operator Overloading for Custom Types — Still Somewhat Esoteric
&lt;/h2&gt;

&lt;p&gt;For built-in types like &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, and &lt;code&gt;std::string&lt;/code&gt;, everything works out of the box — &lt;code&gt;operator&amp;lt;&amp;lt;&lt;/code&gt; and &lt;code&gt;operator==&lt;/code&gt; are already defined. But for a custom struct, you have to teach the compiler how to handle it.&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="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Teach std::ostream how to print myStruct&lt;/span&gt;
&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&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;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&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;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;" "&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&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;z&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;os&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Teach the compiler how to compare two myStructs&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;myStruct&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;y&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;z&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;operator&amp;lt;&amp;lt;&lt;/code&gt; overload is what allows &lt;code&gt;Node&amp;lt;myStruct&amp;gt;::print()&lt;/code&gt; to call &lt;code&gt;os &amp;lt;&amp;lt; data&lt;/code&gt; without knowing anything about &lt;code&gt;myStruct&lt;/code&gt; at compile time — as long as that overload exists, the template resolves correctly.&lt;/p&gt;

&lt;p&gt;The stream-based syntax (&lt;code&gt;os &amp;lt;&amp;lt; s.x &amp;lt;&amp;lt; " " &amp;lt;&amp;lt; s.y&lt;/code&gt;) still feels somewhat counter-intuitive — you're chaining calls on an object that represents an output stream, returning a reference to itself at each step so the chain can continue. It works, it's idiomatic C++, but it takes some time before it stops feeling like incantation.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;operator==&lt;/code&gt; overload is what allows &lt;code&gt;dynamic_cast&lt;/code&gt; plus value comparison to work in &lt;code&gt;searchNodeInList&lt;/code&gt;. Without it, &lt;code&gt;typedNode-&amp;gt;data == value&lt;/code&gt; wouldn't compile for custom types.&lt;/p&gt;

&lt;p&gt;The requirement is clear: any type you want to store in &lt;code&gt;dllist&lt;/code&gt; must implement both. This is enforced at compile time — if you try to store a type that doesn't have them, the compiler tells you exactly which operator is missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Memory Management — Clearer Than Its Reputation
&lt;/h2&gt;

&lt;p&gt;Manual memory management in C++ has a reputation for being treacherous. In practice, for a data structure with a clear ownership model, it's straightforward — as long as you follow one rule: whoever allocates, deallocates.&lt;/p&gt;

&lt;p&gt;Insertion:&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="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typename&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;listAppend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;*&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// heap allocation&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;;&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;newNode&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deletion of a single node:&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;deleteNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Object&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// heap deallocation&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Destructor — cleans up everything:&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;dlList&lt;/span&gt;&lt;span class="o"&gt;::~&lt;/span&gt;&lt;span class="n"&gt;dlList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Object&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail in the destructor: save &lt;code&gt;current-&amp;gt;next&lt;/code&gt; before deleting &lt;code&gt;current&lt;/code&gt;. After &lt;code&gt;delete current&lt;/code&gt;, that memory is gone — accessing &lt;code&gt;current-&amp;gt;next&lt;/code&gt; afterward is undefined behavior. One line of discipline prevents the entire class of use-after-free errors.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;delete&lt;/code&gt; on an &lt;code&gt;Object*&lt;/code&gt; that points to a &lt;code&gt;Node&amp;lt;T&amp;gt;&lt;/code&gt; calls the correct destructor because &lt;code&gt;~Object()&lt;/code&gt; is virtual. Without the virtual destructor, &lt;code&gt;delete&lt;/code&gt; on a base pointer would only call the base destructor — leaking whatever the derived class allocated. This is another reason the virtual destructor in &lt;code&gt;Object&lt;/code&gt; is not optional.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Exercise Actually Taught Me
&lt;/h2&gt;

&lt;p&gt;Building something redundant — &lt;code&gt;std::list&lt;/code&gt; and &lt;code&gt;std::variant&lt;/code&gt; already cover this problem more efficiently — turned out to be more instructive than using the standard library version would have been.&lt;/p&gt;

&lt;p&gt;The standard library hides the machinery. Building it yourself forces you to confront exactly why each piece exists:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The polymorphic base exists because templates alone can't provide runtime type erasure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dynamic_cast&lt;/code&gt; exists because downcasting without runtime verification is unsafe by definition&lt;/li&gt;
&lt;li&gt;The virtual destructor exists because &lt;code&gt;delete&lt;/code&gt; on a base pointer without it is undefined behavior&lt;/li&gt;
&lt;li&gt;Operator overloading exists because templates need the compiler to resolve operations on &lt;code&gt;T&lt;/code&gt; — and for custom types, you have to tell it how&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is new knowledge in the sense that it's in every C++ book. But there's a difference between reading about it and building something where removing any one of these pieces breaks the entire structure in a specific and instructive way.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/AlexRosito67/heterogeneous-double-linked-list" rel="noopener noreferrer"&gt;https://github.com/AlexRosito67/heterogeneous-double-linked-list&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pure C++17, no dependencies, CMake build&lt;/li&gt;
&lt;li&gt;Intended as a learning reference, not a production library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this was useful, there's a &lt;a href="https://buymeacoffee.com/AlexRosito67" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; link on the GitHub page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alex Rosito — self-taught electronics engineer and C++ developer. ATtiny85 · ESP32 · KiCad · C++&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>programming</category>
      <category>computerscience</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why Most 74HC595 Display Drivers Flicker (And How QUAD7SHIFT Avoids It Without Trying)</title>
      <dc:creator>Alex Rosito</dc:creator>
      <pubDate>Wed, 29 Apr 2026 02:51:26 +0000</pubDate>
      <link>https://forem.com/alexrosito67/why-most-74hc595-display-drivers-flicker-and-how-quad7shift-avoids-it-without-trying-50eb</link>
      <guid>https://forem.com/alexrosito67/why-most-74hc595-display-drivers-flicker-and-how-quad7shift-avoids-it-without-trying-50eb</guid>
      <description>&lt;p&gt;&lt;em&gt;A Chinese technical site cited QUAD7SHIFT as a flicker-free reference implementation. I didn't design it to solve flicker. I just built it correctly.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Search for "74HC595 7-segment display Arduino" and you'll find dozens of tutorials. Most of them work — in the sense that the display shows numbers. But watch closely under fluorescent lighting, or point a camera at it, and you'll see it: a faint but persistent flicker, sometimes a ghost of the previous digit bleeding into the next.&lt;/p&gt;

&lt;p&gt;This isn't a power supply issue. It isn't a loose connection. It's a structural problem in how most drivers handle the latch.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the Naive Pattern Works
&lt;/h2&gt;

&lt;p&gt;The typical approach looks something like this:&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="c1"&gt;// Naive pattern — found in most tutorials&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;showDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;digitSelect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCH_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;shiftOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATA_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLOCK_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MSBFIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// first transfer&lt;/span&gt;
    &lt;span class="n"&gt;shiftOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DATA_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CLOCK_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MSBFIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;digitSelect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// second transfer&lt;/span&gt;
    &lt;span class="n"&gt;digitalWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCH_PIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&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;Two separate &lt;code&gt;shiftOut()&lt;/code&gt; calls, then a single latch pulse. Looks fine. The problem is what happens &lt;em&gt;between&lt;/em&gt; those two calls.&lt;/p&gt;

&lt;p&gt;When the first &lt;code&gt;shiftOut()&lt;/code&gt; completes, the shift register holds the new segment data — but the latch hasn't fired yet. At that exact moment, the &lt;em&gt;previous&lt;/em&gt; digit select is still active in the storage register. If anything delays the second &lt;code&gt;shiftOut()&lt;/code&gt; — an interrupt, a timer ISR, a millisecond of jitter — the display briefly shows the new segment pattern on the wrong digit.&lt;/p&gt;

&lt;p&gt;That's ghosting. And even without interrupts, the asymmetry in timing between two sequential software calls is enough to produce uneven brightness across digits.&lt;/p&gt;

&lt;p&gt;There's a deeper issue underneath this: &lt;code&gt;shiftOut()&lt;/code&gt; is pure software bit-banging. &lt;br&gt;
It drives the clock and data pins manually, one bit at a time, in a tight loop — with no hardware assistance and no atomicity guarantees. On any AVR running with interrupts enabled (which is the default — &lt;code&gt;millis()&lt;/code&gt; depends on it), a timer ISR can fire between any two clock pulses. The transfer is not protected. This makes the window between the two &lt;code&gt;shiftOut()&lt;/code&gt; calls not just a timing inconvenience but a structural vulnerability: the longer and more interrupt-prone your environment, the more likely you are to see artifacts.&lt;/p&gt;

&lt;p&gt;Hardware SPI — used by QUAD7SHIFT via &lt;code&gt;SPI.transfer16()&lt;/code&gt; — is handled by a dedicated peripheral that runs independently of the CPU. It cannot be interrupted mid-transfer by software. The 16 bits go out clean, every time.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Timing Problem Visualized
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Naive approach — two 74HC595 chips, sequential transfers:

Time →

LATCH:   ___LOW_________________________HIGH___
SRCLK:        ↑↑↑↑↑↑↑↑  ↑↑↑↑↑↑↑↑
DATA:    [segments byte ] [digit select byte ]
                         ↑
                    HERE: shift reg 1 has new segments.
                    Storage reg still holds OLD digit select.
                    Any interrupt here = ghost on wrong digit.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUAD7SHIFT — single 16-bit atomic transfer:

Time →

LATCH:   ___LOW___________________HIGH___
SRCLK:        ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
DATA:    [segments byte | digit select byte]
                                 ↑
                    Both bytes shift as one unit.
                    Latch fires once. No intermediate state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What QUAD7SHIFT Does Instead
&lt;/h2&gt;

&lt;p&gt;The hardware is two 74HC595s in cascade — one controls the segment lines (a–g, dp), the other controls which digit is active (digit select bits). The key is that both chips share a single latch line (RCLK).&lt;/p&gt;

&lt;p&gt;The entire transfer is packed into a single &lt;code&gt;uint16_t&lt;/code&gt;:&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;transferDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cp"&gt;#if defined(__AVR_ATmega328P__) || (__AVR_ATmega168__)
&lt;/span&gt;        &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beginTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SPISettings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LSBFIRST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SPI_MODE0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transfer16&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="c1"&gt;// both bytes in one SPI transaction&lt;/span&gt;
        &lt;span class="n"&gt;SPI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="cp"&gt;#endif
&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// single latch pulse — both registers update simultaneously&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SPI.transfer16()&lt;/code&gt; shifts all 16 bits in one continuous hardware transaction. The latch fires exactly once. There is no intermediate state where one register has new data and the other doesn't.&lt;/p&gt;

&lt;p&gt;For ATtiny85, which lacks hardware SPI and uses the USI (Universal Serial Interface), the same principle applies — two &lt;code&gt;usiTransferByte()&lt;/code&gt; calls back to back with the latch held LOW throughout:&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;transferDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOW&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;usiTransferByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverseBits&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;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;         &lt;span class="c1"&gt;// segments&lt;/span&gt;
    &lt;span class="n"&gt;usiTransferByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reverseBits&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;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// digit select&lt;/span&gt;
    &lt;span class="n"&gt;writePin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LATCHPIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HIGH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The latch is LOW for the entire duration of both transfers. The storage registers don't see anything until the latch goes HIGH — at which point both update atomically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Works: The 74HC595 Latch Mechanism
&lt;/h2&gt;

&lt;p&gt;The 74HC595 has two internal registers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shift register&lt;/strong&gt; — receives serial data on each SRCLK rising edge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage register&lt;/strong&gt; — holds the parallel output, only updates on RCLK rising edge&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When LATCH (RCLK) is LOW, data shifting into the shift register has zero effect on the outputs. The outputs are frozen. You can shift as much data as you want — through one chip, through ten chips in cascade — and nothing changes on the output pins until you pulse the latch HIGH.&lt;/p&gt;

&lt;p&gt;This is the mechanism. The naive pattern doesn't violate it, but it &lt;em&gt;does&lt;/em&gt; use it sloppily — latching between two logical operations instead of after both are complete.&lt;/p&gt;

&lt;p&gt;QUAD7SHIFT latches once, after all 16 bits are in place.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Multiplexing Loop
&lt;/h2&gt;

&lt;p&gt;Eliminating ghosting on each individual transfer is necessary but not sufficient. You also need a stable refresh cycle.&lt;/p&gt;

&lt;p&gt;The naive approach typically calls the display function from &lt;code&gt;loop()&lt;/code&gt;, which means refresh rate is coupled to whatever else &lt;code&gt;loop()&lt;/code&gt; is doing. If you add a sensor read or a serial print, the display dims or flickers because some digits get fewer refresh cycles per second.&lt;/p&gt;

&lt;p&gt;QUAD7SHIFT decouples refresh rate from &lt;code&gt;loop()&lt;/code&gt; timing:&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="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;QUAD7SHIFT&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;printNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint16_t&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;decimalPointPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... digit extraction ...&lt;/span&gt;

    &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;endtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;millis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;_refreshRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;uint8_t&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;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;millis&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;endtime&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;printDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;digitsToPrint&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;i&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;decimalPointPosition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="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="n"&gt;numberOfDigitsToDisplay&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each call to &lt;code&gt;print()&lt;/code&gt; runs the multiplexing loop for exactly &lt;code&gt;_refreshRate&lt;/code&gt; milliseconds, cycling through all digits at a consistent rate regardless of what happened before or after. The display gets a guaranteed time slice.&lt;/p&gt;




&lt;h2&gt;
  
  
  See It Running
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/2jVDQSVcXQ0"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Comparison Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Naive pattern&lt;/th&gt;
&lt;th&gt;QUAD7SHIFT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latch pulses per digit update&lt;/td&gt;
&lt;td&gt;1 (but between two transfers)&lt;/td&gt;
&lt;td&gt;1 (after both transfers complete)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intermediate state where ghost can appear&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refresh rate coupled to &lt;code&gt;loop()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No — fixed time slice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works on ATtiny85&lt;/td&gt;
&lt;td&gt;Depends on implementation&lt;/td&gt;
&lt;td&gt;Yes (USI, bit-reversal handled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hardware SPI on AVR&lt;/td&gt;
&lt;td&gt;Sometimes&lt;/td&gt;
&lt;td&gt;Yes (&lt;code&gt;SPI.transfer16&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  I Didn't Design This to Solve Flicker
&lt;/h2&gt;

&lt;p&gt;Worth being explicit about this: I didn't build QUAD7SHIFT by identifying the ghost problem and engineering a solution. I built it by reading the 74HC595 datasheet, understanding that the latch is what separates "data in transit" from "data on outputs," and constructing the transfer accordingly.&lt;/p&gt;

&lt;p&gt;The flicker never appeared because the design never created the conditions for it.&lt;/p&gt;

&lt;p&gt;A Chinese technical site (CSDN, April 2026) independently analyzed cascaded 74HC595 display drivers and cited QUAD7SHIFT as a reference implementation for eliminating flicker — specifically noting its "dynamic scan algorithm." They found the pattern by looking for the problem. I found it by not creating the problem in the first place.&lt;/p&gt;

&lt;p&gt;Both paths lead to the same place. But I think the second one is more instructive: &lt;strong&gt;correct hardware abstractions tend to be correct in ways you didn't plan for.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Coverage
&lt;/h2&gt;

&lt;p&gt;A technical analysis in Japanese covering the latch boundary mechanism, multiplexing stability, and platform comparisons (ESP32, Raspberry Pi, RP2040) was published on &lt;a href="https://lilting.ch/articles/74hc595-display-flicker-quad7shift" rel="noopener noreferrer"&gt;lilting channel&lt;/a&gt; (April 29, 2026), citing QUAD7SHIFT as a reference implementation.&lt;/p&gt;

&lt;p&gt;This has been showing up in Arduino forums for years — search any combination of "74HC595 flicker", "shiftOut ghosting", or "7-segment brightness uneven" and you'll find threads like &lt;a href="https://forum.arduino.cc/t/74hc595-with-delay/400322/4" rel="noopener noreferrer"&gt;this one&lt;/a&gt; where the symptom is described but the root cause is never identified. The answers usually suggest capacitors, resistor values, or power supply issues. None of those fix it because the problem is in the code, not the hardware.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Library
&lt;/h2&gt;

&lt;p&gt;QUAD7SHIFT is available on GitHub and in the Arduino Library Manager.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/AlexRosito67/QUAD7SHIFT" rel="noopener noreferrer"&gt;https://github.com/AlexRosito67/QUAD7SHIFT&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Supports Arduino Uno, Nano, and ATtiny85&lt;/li&gt;
&lt;li&gt;Common anode and common cathode displays&lt;/li&gt;
&lt;li&gt;Configurable refresh rate&lt;/li&gt;
&lt;li&gt;String display, float, and integer overloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this was useful, there's a &lt;a href="https://buymeacoffee.com/AlexRosito67" rel="noopener noreferrer"&gt;Buy Me a Coffee&lt;/a&gt; link on the GitHub page.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Alex Rosito — self-taught electronics engineer. ATtiny85 · ESP32 · KiCad · C++&lt;/em&gt;&lt;/p&gt;

</description>
      <category>arduino</category>
      <category>embeddedsystems</category>
      <category>cpp</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
