<?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: Vignesh Athiappan</title>
    <description>The latest articles on DEV Community by Vignesh Athiappan (@vicky_acedia).</description>
    <link>https://dev.to/vicky_acedia</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4028899%2F1e712c2d-9953-4fd8-855f-a0bdd45cb21b.jpeg</url>
      <title>DEV Community: Vignesh Athiappan</title>
      <link>https://dev.to/vicky_acedia</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vicky_acedia"/>
    <language>en</language>
    <item>
      <title>We Cut a 190 GB Production Database to 45 GB in One Night. Here's What We Learned</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Mon, 10 Aug 2026 11:44:35 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/we-cut-a-190-gb-production-database-to-45-gb-in-one-night-heres-what-we-learned-3kog</link>
      <guid>https://dev.to/vicky_acedia/we-cut-a-190-gb-production-database-to-45-gb-in-one-night-heres-what-we-learned-3kog</guid>
      <description>&lt;h1&gt;
  
  
  We Cut a 190 GB Production Database to 45 GB in One Night. Here's What We Learned.
&lt;/h1&gt;

&lt;p&gt;Our flagship internal application had a storage problem. Twelve years of accumulated project data had pushed the production database past 190 GB, and we were about to migrate it to a managed cloud instance where every gigabyte carries a monthly bill.&lt;/p&gt;

&lt;p&gt;The mandate was simple: get it under control before the migration. The execution was less simple.&lt;/p&gt;

&lt;p&gt;Here's how it went, and the six things I'd tell anyone attempting the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 1: Your database is probably smaller than you think
&lt;/h2&gt;

&lt;p&gt;The first number everyone quoted was 190 GB. That's what the file size said, and that's what went into the planning deck.&lt;/p&gt;

&lt;p&gt;Then we actually measured it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;size&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;decimal&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;1&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;AllocatedGB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FILEPROPERTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'SpaceUsed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;decimal&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;1&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;UsedGB&lt;/span&gt;
&lt;span class="k"&gt;FROM&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;database_files&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allocated: 190.6 GB. &lt;strong&gt;Actually used: 106.7 GB.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Eighty-four gigabytes of the file was empty space — pages that had been allocated during past growth spikes and never released. The database had been quietly lying to us for years.&lt;/p&gt;

&lt;p&gt;This changed the entire plan. We didn't need to delete 150 GB of data. We needed to delete about 65 GB and then reclaim the empty space that was already there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; allocated size and used size are different numbers, and stakeholders almost always quote the wrong one. Measure both before you scope anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 2: Size doesn't correlate with row count
&lt;/h2&gt;

&lt;p&gt;We built a per-table inventory expecting the biggest tables to be the ones with the most rows. Mostly true — but the outliers were where the real wins were.&lt;/p&gt;

&lt;p&gt;One configuration table held &lt;strong&gt;13 GB across 21,000 rows&lt;/strong&gt;. That's roughly 640 KB per row. It was storing serialized blobs, and nobody had ever cleaned it up because it "only had twenty thousand rows in it."&lt;/p&gt;

&lt;p&gt;An attachment metadata table: &lt;strong&gt;3.7 GB across 8,700 rows.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Meanwhile a history table with 30 million rows took 2.2 GB, because it was six narrow integer columns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; rank tables by megabytes, not row count. The blob-heavy tables hiding behind small row counts are often the fastest wins available — and they're invisible if you only look at &lt;code&gt;sys.partitions.rows&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Related trap: if you join &lt;code&gt;sys.partitions&lt;/code&gt; to &lt;code&gt;sys.allocation_units&lt;/code&gt; to get sizes, your row counts get multiplied by 2–3× for any table with LOB or overflow allocation units. Trust the MB column from that query; get row counts from a separate `COUNT(&lt;/em&gt;)`.)*&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 3: The transaction log will kill you before the data file does
&lt;/h2&gt;

&lt;p&gt;This is the one that nearly ended the night early.&lt;/p&gt;

&lt;p&gt;We had 40 GB free on the data volume. The plan was to delete ~65 GB of rows. In full recovery mode, with no log backups running (we'd stopped the scheduler for the maintenance window), every one of those deletes would accumulate in the transaction log until the volume filled and the whole operation rolled back — a rollback that would itself need log space it didn't have.&lt;/p&gt;

&lt;p&gt;Delete operations log the full row image plus every index entry removed. Log volume typically &lt;strong&gt;meets or exceeds&lt;/strong&gt; the data volume being deleted. We were looking at 65+ GB of log growth into 40 GB of free space.&lt;/p&gt;

&lt;p&gt;The fix was one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;MyDatabase&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;RECOVERY&lt;/span&gt; &lt;span class="k"&gt;SIMPLE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In simple recovery, the log truncates at every checkpoint. Ours stayed at 6.5 GB for the entire three-hour run — it never grew a single megabyte.&lt;/p&gt;

&lt;p&gt;This is safe &lt;em&gt;specifically because&lt;/em&gt; of the conditions we'd established: applications were offline, no users were writing, and we had a verified full backup as our rollback. We weren't giving up anything we needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical follow-up:&lt;/strong&gt; switching back to full recovery afterward is not enough on its own.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;MyDatabase&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;RECOVERY&lt;/span&gt; &lt;span class="k"&gt;FULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;BACKUP&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;MyDatabase&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;DISK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;COMPRESSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Until that backup runs, the database sits in what's often called "pseudo-simple" mode — the recovery chain is broken and log backups will fail. This step gets skipped constantly. Don't skip it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 4: Gates and abort flags, not rollbacks
&lt;/h2&gt;

&lt;p&gt;The purge script was structured in six waves, each handling a dependency layer — child records first, then parents, then orphan sweeps.&lt;/p&gt;

&lt;p&gt;Between every wave sat a gate: a verification query that confirmed zero remnants before the next wave was allowed to start. And before every destructive batch, the script checked a control table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Purge_RunState&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Aborted&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;BEGIN&lt;/span&gt;
    &lt;span class="n"&gt;PRINT&lt;/span&gt; &lt;span class="s1"&gt;'SKIPPED (aborted)'&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="k"&gt;END&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That abort flag meant a second session could halt the run at the next batch boundary without triggering a multi-hour rollback of work already committed. During UAT it saved us more than once.&lt;/p&gt;

&lt;p&gt;The gates also caught something we would otherwise have missed. Wave A was planned to delete 6,435,445 rows. It deleted 8,824,935 — a difference of exactly 2,389,490. Those were orphaned parent headers whose children had just been removed, and the script logged them explicitly. Because the number reconciled to the row, we knew it was correct behaviour rather than a bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; design for &lt;em&gt;stopping&lt;/em&gt;, not for undoing. On a dataset this size, rollback is a worse outcome than a controlled halt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 5: Prove what you didn't delete
&lt;/h2&gt;

&lt;p&gt;The single most valuable thing in the script wasn't a delete — it was a set of before/after assertions on the data we promised not to touch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active projects   before=2154     after=2154        (must be equal)
Timesheet records before=4391749  after=4391749     (must be equal)
Task records      before=3536910  after=3536910     (expected drop=0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every stakeholder conversation about a purge eventually arrives at the same question: &lt;em&gt;"How do you know you didn't delete something important?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;"We were careful" is not an answer. "Here are the exact counts of your financially-relevant records before and after, and they are identical" ends the conversation.&lt;/p&gt;

&lt;p&gt;Take a snapshot of the protected data &lt;em&gt;before&lt;/em&gt; you start. Verify against it &lt;em&gt;after&lt;/em&gt;. Put both numbers in the communication.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lesson 6: Deleting rows doesn't shrink the file
&lt;/h2&gt;

&lt;p&gt;We finished the purge at 43 GB of actual data. The file was still 190.6 GB.&lt;/p&gt;

&lt;p&gt;Deleting rows frees space &lt;em&gt;inside&lt;/em&gt; the file; it doesn't return it to the operating system. That requires an explicit shrink — and shrinking 190 GB down to 45 GB means physically relocating well over 100 GB of pages.&lt;/p&gt;

&lt;p&gt;Don't do that in one statement. A single massive &lt;code&gt;SHRINKFILE&lt;/code&gt; can run for many hours, and cancelling it mid-flight loses everything it accomplished. Step it down instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHRINKFILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;150000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHRINKFILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;110000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHRINKFILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;85000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHRINKFILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;65000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;DBCC&lt;/span&gt; &lt;span class="n"&gt;SHRINKFILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;46000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step completes in a manageable window, and if you have to stop, you keep the ground you've gained.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Be aware of the cost:&lt;/strong&gt; shrinking works by moving pages from the end of the file to the front, which destroys index ordering. Expect heavy fragmentation afterward and plan an index rebuild — which will grow the file back somewhat. Budget for that before you report a final number.&lt;/p&gt;




&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File size&lt;/td&gt;
&lt;td&gt;190.6 GB&lt;/td&gt;
&lt;td&gt;44.9 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Actual data&lt;/td&gt;
&lt;td&gt;106.7 GB&lt;/td&gt;
&lt;td&gt;43.0 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transaction log&lt;/td&gt;
&lt;td&gt;6.5 GB&lt;/td&gt;
&lt;td&gt;6.5 GB (never grew)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;3h 02m&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed gates&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active records lost&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Roughly 146 GB returned to the operating system, and a database that will cost meaningfully less to run in the cloud.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Measure earlier.&lt;/strong&gt; Two weeks of planning were built on a number that turned out to be 44% empty space. One query on day one would have reframed the entire project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inventory everything, not just the suspects.&lt;/strong&gt; Our purge script targeted 22 tables. A full-database inventory run late in the process surfaced two more totalling 5.7 GB that nobody had thought about — one of them the parent of a table we &lt;em&gt;were&lt;/em&gt; purging, which meant we were about to create orphans.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decide the retention policy before the maintenance window.&lt;/strong&gt; We finished the night 3 GB above our stretch target, blocked entirely on one table that needed a business decision about how much history to keep. That decision could have been made a week earlier over email. Instead it became a follow-up item, and follow-up items have a way of becoming next quarter's items.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Take the backup twice.&lt;/strong&gt; Once before you start. Once after you finish. The second one is the one people skip because they're tired and it's 4 AM, and it's the one that matters if something surfaces on Monday.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Every environment is different. Test on staging first — every wave and every retention rule in this operation was validated against a full production restore before it touched anything real. That discipline is the reason there's a blog post about a successful night instead of a post-mortem about a bad one.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>database</category>
    </item>
    <item>
      <title>Designing a humane involuntary exit workflow: what I learned building one</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:40:31 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/designing-a-humane-involuntary-exit-workflow-what-i-learned-building-one-25bl</link>
      <guid>https://dev.to/vicky_acedia/designing-a-humane-involuntary-exit-workflow-what-i-learned-building-one-25bl</guid>
      <description>&lt;p&gt;Involuntary exits are the workflow nobody wants to talk about, which is exactly why most companies handle them badly — over email threads, spreadsheets, and hallway conversations. I recently spent a few weeks designing an end-to-end system for bench-driven involuntary separations for US full-time employees, and it taught me more about process design than any "happy path" workflow ever has. Here's the shape of it, with everything company-specific stripped away.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trigger: let the system start it, never finish it
&lt;/h2&gt;

&lt;p&gt;The flow begins when someone on the bench approaches the end of their allocation window with no new project confirmed. Instead of relying on a resource manager to remember to start the process, the system auto-initiates the workflow a few business days before the bench end date and notifies the resource management group (RMG).&lt;/p&gt;

&lt;p&gt;That word &lt;em&gt;initiates&lt;/em&gt; is doing careful work. The system opens the case; a human must verify it. RMG reviews the employee's situation, attaches a justification document, adds comments, and only then does the workflow actually move. If there's even a remote chance of a position, they can hold the case (by moving the allocation end date, which re-arms the trigger) or cancel it outright. Automation for reliability, humans for judgment.&lt;/p&gt;

&lt;p&gt;And because people don't always act on time, the verification step carries a 24-hour SLA with daily reminders, escalating first to HR for visibility and eventually to a senior leader if it sits untouched for days. The lesson: an SLA without an escalation path is just a suggestion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approvals: parallel where possible, sequential where it matters
&lt;/h2&gt;

&lt;p&gt;Once verified, two things happen at the same time. The employee's reporting manager gets a pure FYI — not an approval, just the chance to flag "wait, I have an opportunity for this person, expected to start on this date." Making this a comment instead of a gate was deliberate: the manager closest to the work often knows about openings first, but they shouldn't be able to stall the process by simply not responding.&lt;/p&gt;

&lt;p&gt;In parallel, the vertical head — the actual decision-maker — gets an approval request framed as a staffing question rather than paperwork: &lt;em&gt;"I have a position"&lt;/em&gt; (which rejects the exit and keeps the person) or &lt;em&gt;"go with the exit, I don't have a position."&lt;/em&gt; Any one approver from a defined list is enough. If nobody acts within one business day, the case auto-approves and everyone gets notified that it did. Silence can slow a person's exit; it can never leave them in limbo forever.&lt;/p&gt;

&lt;p&gt;Then comes the one branch in the flow: tenure. For long-tenured employees (past a defined threshold), an additional business-unit-head approval kicks in — mandatory, with no auto-approval, escalating to a configured senior contact if it sits for 24 hours. The more someone has invested in the company, the more senior the human who must personally sign off. I think that's the right instinct encoded as a rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  The last mile: the employee hears it from a person, not a system
&lt;/h2&gt;

&lt;p&gt;This was the most important decision in the whole design. The final step is deliberately &lt;em&gt;not&lt;/em&gt; integrated with the HRIS. When all approvals are done, HR gets notified and must do three things in strict order: talk to the employee face to face, close the case in the workflow tool, and only then manually raise the exit in the HR system.&lt;/p&gt;

&lt;p&gt;Why manual? Because the moment an exit is created in most HR platforms, automated emails fire — to the employee, to stakeholders, to IT. Nobody should learn they're being separated from a system notification. The workflow physically cannot reach the HRIS until a human confirms the conversation has happened. We gave up integration elegance to guarantee dignity, and I'd make that trade every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation is a feature, not an edge case
&lt;/h2&gt;

&lt;p&gt;At every single stage — including &lt;em&gt;after&lt;/em&gt; the exit has been initiated — the process can be cancelled with a mandatory reason, and whoever is currently holding the case gets notified. Opportunities appear late. Clients extend. Someone finds a role at the last minute. A separation process that can't gracefully reverse itself will either be bypassed or will hurt someone. Until the very last step, the door back stays open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everything is configuration, everything is audited
&lt;/h2&gt;

&lt;p&gt;Two engineering choices rounded it out. First, nothing is hardcoded: triggers, stage sequences, SLAs, and escalation contacts all live in configuration tables, so a different country, a different employee type, or a different trigger (performance-based, contract end) becomes a set of rows rather than a rewrite. Second, an append-only audit log — insert-only at the database permission level — records every transition, every decision with its comments, every auto-approval, every escalation, and every email sent. When the question is "who approved this person's exit, and was it a real decision or an SLA timeout," the answer must be one query away, forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The system's job in an involuntary exit isn't to make the process faster. It's to make sure nothing is forgotten, nobody is skipped, silence never traps a case, and the human moments stay human. Automate the memory, escalate the accountability, and keep a person between the decision and the email — that's the whole design in one sentence.&lt;/p&gt;

</description>
      <category>hr</category>
      <category>process</category>
    </item>
    <item>
      <title>Azure Managed Redis: A Field-by-Field Walkthrough of the Create Blade</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:49:10 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/azure-managed-redis-a-field-by-field-walkthrough-of-the-create-blade-2i8j</link>
      <guid>https://dev.to/vicky_acedia/azure-managed-redis-a-field-by-field-walkthrough-of-the-create-blade-2i8j</guid>
      <description>&lt;p&gt;I recently spun up my first Azure Managed Redis (AMR) instance. The create wizard looks simple — five tabs, a handful of checkboxes — but several of those checkboxes are &lt;strong&gt;one-way doors&lt;/strong&gt;, and a couple of them will silently break your applications if you get them wrong.&lt;/p&gt;

&lt;p&gt;This is the walkthrough I wish I'd had. Every field, what it does, and what I'd actually pick.&lt;/p&gt;

&lt;p&gt;First, a clarification that matters: &lt;strong&gt;Azure Managed Redis is not the same product as Azure Cache for Redis.&lt;/strong&gt; AMR is the newer service replacing the Enterprise tiers. A lot of the blog posts and Stack Overflow answers you'll find describe the old Premium tier behaviour, and some of it is flat-out wrong for AMR. Persistence is the big one — more on that below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Advanced tab (where all the interesting decisions live)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Modules
&lt;/h3&gt;

&lt;p&gt;Optional Redis capabilities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;What it gives you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RedisJSON&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Store and query JSON documents natively&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RedisTimeSeries&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Time-series data with downsampling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RedisBloom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bloom filters, count-min sketch, top-k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RediSearch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Secondary indexes + &lt;strong&gt;vector search&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;RediSearch&lt;/code&gt; is the one to think hardest about — it's what turns Redis into a vector store for RAG workloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch: modules can only be set at creation time.&lt;/strong&gt; You cannot add one later without rebuilding the instance. But they also consume memory and CPU, so don't enable all four "just in case."&lt;/p&gt;

&lt;p&gt;My pick: only what I'll use in the next quarter. If vector search is anywhere on the roadmap, tick &lt;code&gt;RediSearch&lt;/code&gt; now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-TLS access only
&lt;/h3&gt;

&lt;p&gt;Leave it &lt;strong&gt;unchecked&lt;/strong&gt;. Checking it opens plaintext port 6379. There is no good reason to do this in production, and Entra authentication won't work without TLS anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eviction Policy
&lt;/h3&gt;

&lt;p&gt;What Redis does when memory fills up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Eviction&lt;/strong&gt; — writes start failing with OOM errors once full&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;volatile-lru&lt;/strong&gt; — evicts least-recently-used keys &lt;em&gt;that have a TTL&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;allkeys-lru&lt;/strong&gt; — evicts least-recently-used keys, TTL or not&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The portal defaults to &lt;code&gt;No Eviction&lt;/code&gt;, which is the right default for a data store and the wrong one for a cache. If you're building a cache, you want it to shed cold data gracefully rather than start throwing errors at 3am.&lt;/p&gt;

&lt;p&gt;My pick: &lt;code&gt;volatile-lru&lt;/code&gt; for a cache. &lt;code&gt;No Eviction&lt;/code&gt; only if losing keys is genuinely unacceptable.&lt;/p&gt;

&lt;h3&gt;
  
  
  High availability
&lt;/h3&gt;

&lt;p&gt;Adds a replica in a second availability zone with automatic failover.&lt;/p&gt;

&lt;p&gt;Disabling it roughly halves your cost. It also means a single node failure is total data loss with no failover. For anything production-facing, keep it &lt;strong&gt;Enabled&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clustering Policy
&lt;/h3&gt;

&lt;p&gt;How clients talk to the instance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OSS&lt;/strong&gt; — the client discovers shards and talks to each node directly. Best throughput and latency, but needs a cluster-aware client, and multi-key operations must land in the same hash slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise&lt;/strong&gt; — a single proxy endpoint that behaves like one Redis server. Slightly higher latency, far less friction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-clustered&lt;/strong&gt; — smaller SKUs only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;StackExchange.Redis&lt;/code&gt; handles OSS fine. But if you're pointing legacy code, low-code connectors, or anything you don't fully control at this instance, &lt;code&gt;Enterprise&lt;/code&gt; will save you debugging time.&lt;/p&gt;

&lt;p&gt;My pick: &lt;code&gt;Enterprise&lt;/code&gt; unless I've explicitly tested OSS with every client.&lt;/p&gt;




&lt;h2&gt;
  
  
  Data Persistence — read this part carefully
&lt;/h2&gt;

&lt;p&gt;Three options: &lt;strong&gt;No Persistence&lt;/strong&gt;, &lt;strong&gt;RDB&lt;/strong&gt;, &lt;strong&gt;AOF&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RDB&lt;/strong&gt; takes a point-in-time snapshot of the whole dataset on a configurable interval. Cheap in steady state. If your interval is one hour and you lose the instance at minute 59, you lose 59 minutes of writes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AOF&lt;/strong&gt; appends every write operation to a log, flushed once per second. Tiny loss window, but a real throughput cost. Note that with HA enabled, AOF runs on the &lt;strong&gt;replica&lt;/strong&gt; shards only — primaries have appendonly disabled to avoid the write overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the part that surprises everyone:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In AMR, persisted data is written to a managed disk attached to the instance. The location is neither configurable nor accessible to you.&lt;/strong&gt; There is no blob to browse. No &lt;code&gt;.rdb&lt;/code&gt; file to download. No restore-point picker.&lt;/p&gt;

&lt;p&gt;Persistence exists so that if a catastrophic event takes out &lt;em&gt;both&lt;/em&gt; the primary and the replica, the instance rebuilds itself automatically. It is a self-healing mechanism, not a backup product.&lt;/p&gt;

&lt;p&gt;Two things follow from this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Persistence is not a backup and not point-in-time recovery.&lt;/strong&gt; If a buggy job writes corrupted data into Redis, the corruption gets faithfully persisted too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persisted files cannot be imported into another instance.&lt;/strong&gt; To move data between caches — or to get an actual file you can hold — you use the separate &lt;strong&gt;Import/Export&lt;/strong&gt; feature, which writes RDB files to a blob container you specify. If you want real backups, script periodic exports via CLI or PowerShell on a schedule.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The old Premium tier of Azure Cache for Redis &lt;em&gt;did&lt;/em&gt; write RDB into your own storage account. AMR does not. That's the single most common piece of stale information out there.&lt;/p&gt;

&lt;p&gt;My pick: &lt;code&gt;No Persistence&lt;/code&gt; for a pure cache — the data is regenerable and I'd rather have the throughput. &lt;code&gt;RDB&lt;/code&gt; if a cold start would hammer the backing database hard enough to matter, or if I'm holding sessions and don't want to force everyone to re-authenticate after an outage.&lt;/p&gt;

&lt;p&gt;Persistence &lt;strong&gt;is&lt;/strong&gt; changeable after creation. Not a one-way door.&lt;/p&gt;




&lt;h2&gt;
  
  
  Customer-managed key encryption at rest
&lt;/h2&gt;

&lt;p&gt;Leaving this unchecked does &lt;strong&gt;not&lt;/strong&gt; mean "unencrypted."&lt;/p&gt;

&lt;p&gt;AMR encrypts persistence data and OS disks with Microsoft-managed keys by default. The CMK option just wraps those keys with one from your own Key Vault. The difference isn't &lt;em&gt;whether&lt;/em&gt; data at rest is encrypted — it's &lt;em&gt;who controls the key material&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;CMK gives you rotation control, access auditing, and the ability to revoke (which makes the instance inaccessible — that's the point for some threat models). The cost is operational: it requires a user-assigned managed identity plus a Key Vault with both purge protection and soft delete enabled. Delete that key or break the identity's access, and your instance breaks.&lt;/p&gt;

&lt;p&gt;Also worth noting: if you chose &lt;code&gt;No Persistence&lt;/code&gt;, there's very little at rest to encrypt in the first place.&lt;/p&gt;

&lt;p&gt;My pick: unchecked, unless a client contract or auditor explicitly requires customer-controlled keys. It's addable later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defer automatic major Redis version updates (preview)
&lt;/h2&gt;

&lt;p&gt;Lets you postpone major engine upgrades so they don't land in the middle of a release window. Useful if you need change control. You still have to take them eventually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access Keys Authentication — the one that will break your apps
&lt;/h2&gt;

&lt;p&gt;This is the field most likely to cause a bad afternoon.&lt;/p&gt;

&lt;p&gt;Unchecked means &lt;strong&gt;Entra ID authentication only&lt;/strong&gt;. No access keys, no &lt;code&gt;password=&lt;/code&gt; in a connection string. This is now AMR's default — managed identity is enabled when you create a new instance, and Microsoft is deliberately steering people off shared secrets.&lt;/p&gt;

&lt;p&gt;If you go Entra-only, here's what's involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client acquires an Entra token (scope &lt;code&gt;https://redis.azure.com/.default&lt;/code&gt;) and uses it in place of the password. Username is the object ID of the principal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens expire.&lt;/strong&gt; The client must refresh before expiry and re-send &lt;code&gt;AUTH&lt;/code&gt;, or connections drop. For .NET, use the &lt;code&gt;Microsoft.Azure.StackExchangeRedis&lt;/code&gt; package — don't hand-roll this.&lt;/li&gt;
&lt;li&gt;Entra auth is &lt;strong&gt;TLS only&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A valid token alone isn't enough. You must separately authorize the identity on the Redis side: &lt;strong&gt;Data Access Configuration → new Redis User → Data Owner / Data Contributor / Data Reader.&lt;/strong&gt; No grant, no access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entra groups aren't supported.&lt;/strong&gt; You add individual users or service principals. For a team of any size, that's a real operational annoyance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use a &lt;strong&gt;managed identity&lt;/strong&gt; wherever the caller runs inside Azure. A service principal / app registration is only needed for callers outside Azure. And note that the authorization is &lt;em&gt;not&lt;/em&gt; API permissions on the app registration — there's nothing to configure on that side.&lt;/p&gt;

&lt;p&gt;Two gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toggling this setting &lt;strong&gt;terminates all existing client connections&lt;/strong&gt;, regardless of which auth method they were using. Do it in a window, with retry logic in place.&lt;/li&gt;
&lt;li&gt;On geo-replicated instances you must unlink → disable keys → relink.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My pick: &lt;strong&gt;tick it at creation.&lt;/strong&gt; Get everything connected and working, migrate app-by-app to Entra, then disable keys. It's changeable post-creation — unlike modules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick reference: what's a one-way door?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Changeable later?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Modules&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;No — creation only&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clustering policy&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistence&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eviction policy&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High availability&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMK&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access keys&lt;/td&gt;
&lt;td&gt;✅ Yes (but kills live connections)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SKU / size&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  After it's created: the things I wish I'd known on day one
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Redis is a flat key → value store.&lt;/strong&gt; No tables, no joins, no queries. You must know the key to get the value. Your key naming convention &lt;em&gt;is&lt;/em&gt; your schema — something like &lt;code&gt;app:entity:id:field&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TTL is the whole game.&lt;/strong&gt; Every cache key gets an expiry, or you leak memory until eviction kicks in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's single-threaded.&lt;/strong&gt; One slow command blocks every other client. Which is why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never run &lt;code&gt;KEYS *&lt;/code&gt; in production. Use &lt;code&gt;SCAN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Never store multi-megabyte values.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FLUSHALL&lt;/code&gt; has no undo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Redis is not the source of truth.&lt;/strong&gt; Your code must work correctly when the cache is completely empty. Test that path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Watch for cache stampede.&lt;/strong&gt; A thousand requests miss simultaneously and all hammer the database at once. Add jitter to your TTLs.&lt;/p&gt;

&lt;p&gt;On the .NET side: &lt;code&gt;ConnectionMultiplexer&lt;/code&gt; is a &lt;strong&gt;singleton&lt;/strong&gt;. Register it once in DI. Creating one per request is the single most common Redis mistake and it will exhaust your connections. If you want the easy path, &lt;code&gt;AddStackExchangeRedisCache()&lt;/code&gt; gives you &lt;code&gt;IDistributedCache&lt;/code&gt; with straightforward get/set/TTL semantics.&lt;/p&gt;

&lt;p&gt;The pattern you'll use 90% of the time is &lt;strong&gt;cache-aside&lt;/strong&gt;: read from Redis, return on hit; on miss, read from the database, write to Redis with a TTL, return. Write that helper once and reuse it everywhere.&lt;/p&gt;

&lt;p&gt;Finally, monitor these four things and alert on them: &lt;strong&gt;memory used %&lt;/strong&gt;, &lt;strong&gt;evicted keys&lt;/strong&gt;, &lt;strong&gt;cache hit ratio&lt;/strong&gt;, and &lt;strong&gt;server load&lt;/strong&gt;. Memory above 80% or evictions climbing above zero means it's time to scale or shorten your TTLs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Don't overthink the sizing — that's changeable. Do think hard about &lt;strong&gt;modules&lt;/strong&gt; and &lt;strong&gt;clustering policy&lt;/strong&gt;, because those aren't. Leave access keys on until your clients are ready for Entra. And remember that persistence in AMR is self-healing, not a backup.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>microsoft</category>
      <category>azure</category>
    </item>
    <item>
      <title>The Moving Bike on Your Food App: How Live Order Tracking Actually Works (in Azure)</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Tue, 04 Aug 2026 05:48:33 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/the-moving-bike-on-your-food-app-how-live-order-tracking-actually-works-in-azure-378j</link>
      <guid>https://dev.to/vicky_acedia/the-moving-bike-on-your-food-app-how-live-order-tracking-actually-works-in-azure-378j</guid>
      <description>&lt;p&gt;You order biryani on Swiggy. You keep refreshing. A little bike icon crawls across the map toward your house. It feels like magic.&lt;/p&gt;

&lt;p&gt;It isn't. It's four tools, each doing exactly one job. This post breaks the whole thing down using that one everyday moment — no heavy theory, just the flow.&lt;/p&gt;

&lt;p&gt;Swiggy runs on AWS; I've mapped everything to its &lt;strong&gt;Azure&lt;/strong&gt; equivalent so you can build the same thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cast: 4 tools, 4 jobs
&lt;/h2&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;Its one job&lt;/th&gt;
&lt;th&gt;Real-life analogy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Service Bus&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Carry the &lt;strong&gt;order&lt;/strong&gt; — must never be lost&lt;/td&gt;
&lt;td&gt;Registered post&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event Hubs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Carry the &lt;strong&gt;GPS firehose&lt;/strong&gt; — millions of pings&lt;/td&gt;
&lt;td&gt;CCTV recording&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hold &lt;strong&gt;"where's the bike right now"&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Sticky note&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cosmos DB / SQL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keep the &lt;strong&gt;order forever&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Filing cabinet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The whole trick is: &lt;strong&gt;never make one tool do two jobs.&lt;/strong&gt; Each is great at exactly one thing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The journey, step by step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. You tap "Place Order"&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tap → Service Bus (1 order message) → Order Service → saved to Cosmos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Service Bus = reliable, so the order can't vanish or double. Cosmos = the permanent record.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Restaurant gets notified&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service → Service Bus → Restaurant App: "New order!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A discrete command that must arrive → Service Bus again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Rider assigned — and GPS starts immediately&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rider assigned → phone GPS starts streaming → Event Hubs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;em&gt;why&lt;/em&gt; you can watch the bike drive &lt;strong&gt;to&lt;/strong&gt; the restaurant first, then to you. GPS begins at "assigned," not at "picked up."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The firehose hits the pipe&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bike GPS (every ~4 sec) → Event Hubs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One bike is tiny. 500,000 bikes = ~125,000 pings/sec. Event Hubs swallows it without blinking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. A processor turns the firehose into one value&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event Hubs → Stream Processor → Redis: "bike_4471 = 12.97, 77.59"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each new ping &lt;strong&gt;overwrites&lt;/strong&gt; the last one in Redis. 15 pings a minute collapse into 1 sticky note.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. You refresh the map&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your app → reads Redis (1 value, &amp;lt;1ms) → 🛵 dot moves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You read &lt;strong&gt;Redis, not Event Hubs.&lt;/strong&gt; It's instant because it's a single lookup, not a firehose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Delivered — keep only what matters&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order Service → status "Delivered" → Cosmos
Processor → sampled trail → Lakehouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw 4-second pings are thrown away. Kept: the final order (Cosmos) + a thinned route trail (Lakehouse).&lt;/p&gt;




&lt;h2&gt;
  
  
  The one idea that makes it all click: TWO LANES
&lt;/h2&gt;

&lt;p&gt;Beginners picture one straight line. It's actually &lt;strong&gt;two independent lanes that meet only at Redis.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WRITE lane (always on, background):   GPS → Event Hubs → Processor → Redis
READ lane  (only when you refresh):   You → Redis → 🛵
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The processor is &lt;strong&gt;always&lt;/strong&gt; filling Redis in the background.&lt;/li&gt;
&lt;li&gt;Your refresh &lt;strong&gt;only&lt;/strong&gt; reads Redis.&lt;/li&gt;
&lt;li&gt;You never touch Event Hubs or the processor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Analogy: a chef (processor) keeps refilling the buffet tray (Redis). You (refresh) grab from the tray. You never wait for the chef.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 4 confusions everyone hits (and the fix)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Isn't the cached position outdated?"&lt;/strong&gt;&lt;br&gt;
It can be — by design. You only cache things where "a few seconds old is fine" (bike position). You never cache the wallet balance. Redis holds a fresh value because the processor overwrites it every few seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Is Event Hubs just a database?"&lt;/strong&gt;&lt;br&gt;
No. A database lets you ask &lt;em&gt;"give me bike 4471's location."&lt;/em&gt; Event Hubs can't — it only lets you read the stream front-to-back, like a ticker. It's a &lt;strong&gt;log that forgets&lt;/strong&gt; (data expires after 1–7 days). Filing cabinet vs conveyor belt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Event Hubs overwrites, so no history?"&lt;/strong&gt;&lt;br&gt;
Swapped tools. &lt;strong&gt;Event Hubs KEEPS history&lt;/strong&gt; (every ping, in order, for days). &lt;strong&gt;Redis&lt;/strong&gt; is the one that overwrites. History survives in Event Hubs short-term and Lakehouse long-term.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Won't the database get the same 125k/sec firehose anyway?"&lt;/strong&gt;&lt;br&gt;
No — the consumer is a &lt;strong&gt;filter + funnel&lt;/strong&gt;, not a pass-through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reduce&lt;/strong&gt; — Redis overwrites; only a sampled trail is kept (drop ~87%)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch&lt;/strong&gt; — 1,000 events → 1 bulk write&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth&lt;/strong&gt; — the buffer absorbs the dinner-time spike so the DB sees a calm, flat rate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the time data reaches the DB, it's a trickle — sampled, batched, and calm.&lt;/p&gt;


&lt;h2&gt;
  
  
  Bonus: how does it say "25 mins" before the food is even made?
&lt;/h2&gt;

&lt;p&gt;It's a &lt;strong&gt;prediction&lt;/strong&gt;, not a measurement. An ML model adds three guesses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ETA = prep time + rider-to-restaurant + restaurant-to-you
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Guessed from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prep time&lt;/td&gt;
&lt;td&gt;This restaurant's history for this dish + kitchen load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pickup travel&lt;/td&gt;
&lt;td&gt;Rider GPS distance + live traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery travel&lt;/td&gt;
&lt;td&gt;Route distance + live traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's why your ETA sometimes jumps 25 → 35 mid-order: the model re-adjusts as reality changes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not just use one big tool?
&lt;/h2&gt;

&lt;p&gt;Because every tool is great at one thing and bad at the others:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you forced Event Hubs to also be the database&lt;/th&gt;
&lt;th&gt;If you forced Redis to keep history&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Can't look up by key&lt;/td&gt;
&lt;td&gt;Runs out of memory fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data expires and vanishes&lt;/td&gt;
&lt;td&gt;Loses "current" clarity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Splitting the jobs is the same trade-off principle behind all system design: &lt;strong&gt;pick the right tool for each job, and accept that no single tool wins at everything.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The whole thing in 3 lines
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Order&lt;/strong&gt; → Service Bus → Cosmos (keep forever)&lt;br&gt;
&lt;strong&gt;GPS&lt;/strong&gt; → Event Hubs → Redis (keep "now") → your map&lt;br&gt;
&lt;strong&gt;The database never sees the firehose — only the trickle that matters.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Next time you watch that bike crawl toward your door, you'll know: it's not magic. It's a write lane quietly filling a sticky note, and a read lane grabbing it every time you refresh.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>distributedsystems</category>
    </item>
    <item>
      <title>System Design Foundations, Explained with Swiggy</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Mon, 03 Aug 2026 06:38:27 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/system-design-foundations-explained-with-swiggy-37p1</link>
      <guid>https://dev.to/vicky_acedia/system-design-foundations-explained-with-swiggy-37p1</guid>
      <description>&lt;p&gt;Every system design decision comes down to trade-offs between a handful of properties. This post walks through the foundational ones — quality attributes, the CAP theorem, PACELC, and the consistency spectrum — using one running example: you open Swiggy on a Friday night and order biryani.&lt;/p&gt;

&lt;p&gt;Uber works almost identically (rider + driver instead of eater + delivery partner), so the same lens applies there too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1 — Quality Attributes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario: Friday 8:30 PM. You order biryani on Swiggy.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;One-line meaning&lt;/th&gt;
&lt;th&gt;Swiggy moment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scalability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Handle more load&lt;/td&gt;
&lt;td&gt;10k users at 3PM → 5M at dinner. Add servers.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Speed of &lt;em&gt;your one&lt;/em&gt; request&lt;/td&gt;
&lt;td&gt;Tap search → results in &amp;lt;1 sec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Throughput&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Total volume for &lt;em&gt;everyone&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;2M searches/sec across India&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Availability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is it &lt;em&gt;up&lt;/em&gt;?&lt;/td&gt;
&lt;td&gt;App must not go down at peak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reliability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Is the answer &lt;em&gt;correct&lt;/em&gt;?&lt;/td&gt;
&lt;td&gt;Don't show "delivered" when food's still cooking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Durability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data &lt;em&gt;survives&lt;/em&gt; crashes&lt;/td&gt;
&lt;td&gt;Your paid order never gets lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Everyone sees &lt;em&gt;same latest&lt;/em&gt; data&lt;/td&gt;
&lt;td&gt;"Sold out" shows to all instantly (or not)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fault tolerance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keeps working when a part &lt;em&gt;dies&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;1 of 500 servers crashes → you notice nothing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Easy to &lt;em&gt;change&lt;/em&gt; code&lt;/td&gt;
&lt;td&gt;Ship features weekly, safely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can &lt;em&gt;see inside&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;Order stuck? Trace exactly where&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The ONE big idea
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;You can't max all of them. They fight. Pick 2–3 that matter, sacrifice the rest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Proof — same Swiggy, opposite choices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;💳 &lt;strong&gt;Payment&lt;/strong&gt; → wins: consistency, durability, reliability. sacrifices: latency (2-sec wait is fine)&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Search/browse&lt;/strong&gt; → wins: latency, availability. sacrifices: consistency (stale for 5 sec is fine)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same company. Opposite rankings. &lt;strong&gt;That ranking = the architecture decision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One quick distinction that trips people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reliability / Consistency / Availability&lt;/strong&gt; = what the &lt;em&gt;user&lt;/em&gt; feels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability / Maintainability&lt;/strong&gt; = what the &lt;em&gt;engineer&lt;/em&gt; needs behind the scenes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't mix the two camps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2 — CAP Theorem
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Setup:&lt;/strong&gt; Swiggy runs on many servers in different cities. Sometimes the network &lt;em&gt;between&lt;/em&gt; them breaks — a server in Delhi can't talk to a server in Bengaluru. That break is called a &lt;strong&gt;partition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Three things you want:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Letter&lt;/th&gt;
&lt;th&gt;Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;C&lt;/strong&gt; — Consistency&lt;/td&gt;
&lt;td&gt;Everyone sees the same latest data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;A&lt;/strong&gt; — Availability&lt;/td&gt;
&lt;td&gt;Every request gets an answer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;P&lt;/strong&gt; — Partition tolerance&lt;/td&gt;
&lt;td&gt;System survives the network break&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; During a partition, you can only keep &lt;strong&gt;2 of 3&lt;/strong&gt;. And since network breaks &lt;em&gt;will&lt;/em&gt; happen, &lt;strong&gt;P is not optional&lt;/strong&gt;. So the real choice is just: &lt;strong&gt;C or A?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The choice, on Swiggy
&lt;/h3&gt;

&lt;p&gt;Delhi and Bengaluru servers can't talk. You place an order. What does Swiggy do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pick C (refuse):&lt;/strong&gt; "Sorry, try again later." → Correct, but you got blocked. This is a &lt;strong&gt;CP&lt;/strong&gt; system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick A (accept):&lt;/strong&gt; "Order placed!" → but Delhi doesn't know yet, data is briefly out of sync. This is an &lt;strong&gt;AP&lt;/strong&gt; system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can't have both &lt;em&gt;during the break&lt;/em&gt;. Correctness OR uptime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which does Swiggy pick? Depends on the feature
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;💳 &lt;strong&gt;Payment&lt;/strong&gt; → &lt;strong&gt;CP&lt;/strong&gt;. Better to block than double-charge. Refuse and stay correct.&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Browse restaurants&lt;/strong&gt; → &lt;strong&gt;AP&lt;/strong&gt;. Better to show slightly stale list than an error page. Stay up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The classic trap: "CA system"
&lt;/h3&gt;

&lt;p&gt;A CA system means "no partition tolerance" — that only works on a &lt;em&gt;single machine&lt;/em&gt;. Any real distributed system &lt;em&gt;will&lt;/em&gt; face network breaks, so &lt;strong&gt;P is forced on you&lt;/strong&gt;. You're always really choosing &lt;strong&gt;C or A&lt;/strong&gt;. "CA distributed" is a contradiction.&lt;/p&gt;

&lt;p&gt;The clean way to hold it: &lt;strong&gt;P is forced, C-or-A is the actual choice.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3 — PACELC
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The gap in CAP:&lt;/strong&gt; CAP only tells you what happens &lt;em&gt;during&lt;/em&gt; a network break. But breaks are rare. What about normal times, when everything's healthy? CAP says nothing. PACELC fixes that.&lt;/p&gt;

&lt;p&gt;Read it like a sentence:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;P&lt;/strong&gt;artition? → choose &lt;strong&gt;A&lt;/strong&gt; or &lt;strong&gt;C&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;E&lt;/strong&gt;lse (normal times)? → choose &lt;strong&gt;L&lt;/strong&gt; or &lt;strong&gt;C&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(&lt;strong&gt;L&lt;/strong&gt; = Latency)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;The choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;P&lt;/strong&gt; — network broken&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;A&lt;/strong&gt;vailability or &lt;strong&gt;C&lt;/strong&gt;onsistency &lt;em&gt;(this is just CAP)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;E&lt;/strong&gt; — normal, healthy&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;L&lt;/strong&gt;atency or &lt;strong&gt;C&lt;/strong&gt;onsistency &lt;em&gt;(this is the NEW part)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Why is there even a choice when the network is fine?
&lt;/h3&gt;

&lt;p&gt;Because to be &lt;em&gt;strongly consistent&lt;/em&gt;, servers must &lt;strong&gt;coordinate&lt;/strong&gt; — "wait, let me check with Delhi and Mumbai before I answer." That checking takes &lt;strong&gt;time = latency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So even with a perfect network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want everyone to see the exact same data? → coordinate → &lt;strong&gt;slower&lt;/strong&gt; (pay latency)&lt;/li&gt;
&lt;li&gt;OK with slightly stale? → skip coordination → &lt;strong&gt;faster&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This trade-off is happening &lt;strong&gt;all the time&lt;/strong&gt;, not just during failures. That's PACELC's whole point.&lt;/p&gt;

&lt;h3&gt;
  
  
  On Swiggy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;💳 &lt;strong&gt;Payment&lt;/strong&gt; → &lt;strong&gt;PC / EC&lt;/strong&gt; → consistent always, even if it's a bit slow. Both cases pick C.&lt;/li&gt;
&lt;li&gt;🔍 &lt;strong&gt;Browse&lt;/strong&gt; → &lt;strong&gt;PA / EL&lt;/strong&gt; → during break: stay available. Normal times: stay fast. Both cases avoid the cost of C.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read it as two letters: &lt;code&gt;PA/EL&lt;/code&gt; = "in Partition pick A, Else pick L." Speed and uptime over correctness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4 — The Consistency Spectrum (the dial)
&lt;/h2&gt;

&lt;p&gt;"Consistent vs eventual" isn't a switch — it's a &lt;strong&gt;dial&lt;/strong&gt;. Here it is, strongest (slowest) → weakest (fastest):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Plain meaning&lt;/th&gt;
&lt;th&gt;Swiggy example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Strong&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every read = newest write, always&lt;/td&gt;
&lt;td&gt;Wallet balance — never wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bounded staleness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can lag, but only by X seconds max&lt;/td&gt;
&lt;td&gt;Order status, max 5 sec behind&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Session&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;You&lt;/em&gt; always see &lt;em&gt;your own&lt;/em&gt; writes in order&lt;/td&gt;
&lt;td&gt;Your cart — you added it, you see it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistent prefix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Never see things out of order, just maybe old&lt;/td&gt;
&lt;td&gt;Order timeline: placed→cooking→out, never scrambled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Eventual&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Catches up &lt;em&gt;eventually&lt;/em&gt;, no promise on order&lt;/td&gt;
&lt;td&gt;"Trending near you" list&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Top = safest + slowest. Bottom = fastest + loosest.&lt;/strong&gt; You turn the dial &lt;em&gt;per feature&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Azure tie-in
&lt;/h3&gt;

&lt;p&gt;Cosmos DB literally exposes these as &lt;strong&gt;5 settable levels&lt;/strong&gt; — Strong, Bounded Staleness, Session, Consistent Prefix, Eventual. &lt;strong&gt;Session is the default.&lt;/strong&gt; This isn't theory — it's a dropdown you configure. Wallet → Strong. User cart → Session. Trending list → Eventual.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole of foundations comes down to one habit: name the 2–3 attributes that matter for the feature in front of you, then choose what to sacrifice — during failures (CAP), during normal operation (PACELC), and at the exact level of consistency you actually need (the spectrum). Same system, different features, opposite choices. That ranking act &lt;em&gt;is&lt;/em&gt; the architecture.&lt;/p&gt;

</description>
      <category>design</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Chain of Responsibility: The Support Escalation Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 06:44:21 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/chain-of-responsibility-the-support-escalation-pattern-10jp</link>
      <guid>https://dev.to/vicky_acedia/chain-of-responsibility-the-support-escalation-pattern-10jp</guid>
      <description>&lt;p&gt;You call customer support with a problem. Level 1 tries to help. If they can't solve it, they escalate to Level 2. If Level 2 is stuck, it goes to Level 3. The request travels down a line until &lt;em&gt;someone&lt;/em&gt; handles it — and you, the caller, never needed to know which level would end up solving it.&lt;/p&gt;

&lt;p&gt;That's the Chain of Responsibility pattern: &lt;strong&gt;pass a request along a chain of handlers until one of them handles it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Imagine approving an expense, where the approver depends on the amount:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Approve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&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;amount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Team lead approves"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&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;amount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Manager approves"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&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;amount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Director approves"&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;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"CFO approves"&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;It works, but every new approval level means editing this one method, and the sender is hard-wired to know the entire hierarchy. Chain of Responsibility turns each level into its own handler and links them together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chain way
&lt;/h2&gt;

&lt;p&gt;Each handler knows one thing to do and who to pass to if it can't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Approver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="n"&gt;Approver&lt;/span&gt;&lt;span class="p"&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;public&lt;/span&gt; &lt;span class="n"&gt;Approver&lt;/span&gt; &lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Approver&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="n"&gt;_next&lt;/span&gt; &lt;span class="p"&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;return&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// return next so we can chain the setup fluently&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&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 concrete handler either handles the request or forwards it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TeamLead&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Approver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&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;amount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Team lead approves"&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;_next&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// can't handle — pass it on&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Manager&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Approver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&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;amount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Manager approves"&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;_next&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Director&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Approver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;amount&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;amount&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Director approves"&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;_next&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;Link the chain, then send requests to the front of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;teamLead&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TeamLead&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Manager&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;director&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Director&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;teamLead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;SetNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// build the chain&lt;/span&gt;

&lt;span class="n"&gt;teamLead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// Team lead approves&lt;/span&gt;
&lt;span class="n"&gt;teamLead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// Manager approves&lt;/span&gt;
&lt;span class="n"&gt;teamLead&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// Director approves&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller always talks to the front of the chain and never worries about who ultimately handles the request. Add a CFO level? Write one handler and link it on the end — nothing else changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key idea
&lt;/h2&gt;

&lt;p&gt;Each handler makes one small decision: &lt;em&gt;can I deal with this? If yes, handle it and stop. If no, pass it to the next one.&lt;/em&gt; No single place holds the whole branching logic — it's distributed across small, independent handlers you can reorder, insert, or remove freely.&lt;/p&gt;

&lt;p&gt;You can even design chains where &lt;em&gt;every&lt;/em&gt; handler runs (each doing its bit and always passing along), rather than stopping at the first match — useful for pipelines where several steps should all get a turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you've seen it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ASP.NET Core middleware&lt;/strong&gt; — the single clearest example. Each middleware inspects the request, does its bit (auth, logging, compression), and calls &lt;code&gt;next()&lt;/code&gt; to pass control down the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging frameworks&lt;/strong&gt; — a message passes through handlers filtered by level (Debug, Info, Warning, Error).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event bubbling in UIs&lt;/strong&gt; — a click travels up through parent elements until something handles it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation pipelines&lt;/strong&gt; — each rule checks the input and passes it on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Chain of Responsibility vs Decorator
&lt;/h2&gt;

&lt;p&gt;Both build a chain of wrapped objects, so they look related. The difference is intent. A &lt;strong&gt;Decorator&lt;/strong&gt; always calls the next one and its whole point is to &lt;em&gt;add&lt;/em&gt; behavior around the result. A &lt;strong&gt;Chain of Responsibility&lt;/strong&gt; handler may &lt;em&gt;stop&lt;/em&gt; the chain — the point is to find the &lt;em&gt;one&lt;/em&gt; handler that should deal with the request. Decorator enhances every layer; Chain looks for a place to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Chain of Responsibility = support escalation. A request travels down a line of handlers until one deals with it — and the sender never needs to know which one.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 15 — the finale — of a series on must-know design patterns in C#, explained the simple way. Across the series we covered all fifteen: Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, Observer, Strategy, Command, Iterator, State, Template Method, and Chain of Responsibility. If you followed along from part one, you now have a working mental model for every pattern on the list — each tied to something from everyday life. Thanks for reading.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pattern</category>
    </item>
    <item>
      <title>Template Method: The Recipe-With-Blanks Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 06:43:39 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/template-method-the-recipe-with-blanks-pattern-2gnd</link>
      <guid>https://dev.to/vicky_acedia/template-method-the-recipe-with-blanks-pattern-2gnd</guid>
      <description>&lt;p&gt;Think about how a recipe works. Making tea and making coffee follow the &lt;em&gt;same&lt;/em&gt; skeleton:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Boil water&lt;/li&gt;
&lt;li&gt;Add the main ingredient (tea leaves / coffee powder)&lt;/li&gt;
&lt;li&gt;Add extras (milk, sugar)&lt;/li&gt;
&lt;li&gt;Pour into cup&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The steps and their order never change. Only &lt;em&gt;one or two steps&lt;/em&gt; differ between tea and coffee. You don't rewrite the whole procedure for each drink — you keep the skeleton and swap the parts that vary.&lt;/p&gt;

&lt;p&gt;That's the Template Method pattern: &lt;strong&gt;a parent class defines the fixed skeleton of an algorithm, and subclasses fill in specific steps without changing the overall order.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Say you process two report types — PDF and Excel. Both follow the same flow: fetch data, format it, export, then email it. Without Template Method, you copy-paste that flow twice and each copy drifts out of sync over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;GeneratePdfReport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// same&lt;/span&gt;
    &lt;span class="c1"&gt;// ...format as PDF...       // different&lt;/span&gt;
    &lt;span class="c1"&gt;// ...export...              // different&lt;/span&gt;
    &lt;span class="nf"&gt;SendEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                 &lt;span class="c1"&gt;// same&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;GenerateExcelReport&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;// same — duplicated&lt;/span&gt;
    &lt;span class="c1"&gt;// ...format as Excel...     // different&lt;/span&gt;
    &lt;span class="c1"&gt;// ...export...              // different&lt;/span&gt;
    &lt;span class="nf"&gt;SendEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                 &lt;span class="c1"&gt;// same — duplicated&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shared steps are duplicated, and if the flow changes, you have to fix it in every copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Template Method way
&lt;/h2&gt;

&lt;p&gt;The parent class owns the skeleton in one place. The steps that vary become abstract methods for subclasses to implement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReportGenerator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The template method — defines the fixed skeleton. Note: it's NOT overridable.&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// shared step&lt;/span&gt;
        &lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;               &lt;span class="c1"&gt;// varies — subclass decides&lt;/span&gt;
        &lt;span class="nf"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                   &lt;span class="c1"&gt;// varies — subclass decides&lt;/span&gt;
        &lt;span class="nf"&gt;SendEmail&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                &lt;span class="c1"&gt;// shared step&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;FetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fetching data"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SendEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Emailing report"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The blanks — subclasses must fill these in&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&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;protected&lt;/span&gt; &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Export&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 subclass fills only the blanks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PdfReport&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReportGenerator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Formatting as PDF"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Exporting PDF"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExcelReport&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ReportGenerator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Formatting as Excel"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;            &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Exporting Excel"&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;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;ReportGenerator&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PdfReport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Fetching data&lt;/span&gt;
&lt;span class="c1"&gt;// Formatting as PDF&lt;/span&gt;
&lt;span class="c1"&gt;// Exporting PDF&lt;/span&gt;
&lt;span class="c1"&gt;// Emailing report&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Generate()&lt;/code&gt; flow lives in exactly one place. Subclasses can't reorder or skip steps — they only fill the blanks. Add a CSV report? Write one subclass with two methods; the skeleton is untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The key idea: "don't call us, we'll call you"
&lt;/h2&gt;

&lt;p&gt;Notice the control flow. You don't call &lt;code&gt;Format()&lt;/code&gt; and &lt;code&gt;Export()&lt;/code&gt; yourself — the &lt;em&gt;parent's&lt;/em&gt; &lt;code&gt;Generate()&lt;/code&gt; calls them for you, at the right moment. The subclass just provides the pieces; the parent drives the sequence. This inversion is sometimes called the Hollywood Principle: "don't call us, we'll call you."&lt;/p&gt;

&lt;h2&gt;
  
  
  Template Method vs Strategy
&lt;/h2&gt;

&lt;p&gt;They solve a similar problem — varying part of a behavior — but differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template Method&lt;/strong&gt; uses &lt;em&gt;inheritance&lt;/em&gt;. The skeleton is fixed in a base class; subclasses override steps. The variation is chosen at compile time by which subclass you instantiate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy&lt;/strong&gt; uses &lt;em&gt;composition&lt;/em&gt;. You inject a whole algorithm object and can swap it at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule of thumb: fixed skeleton with a few pluggable steps → Template Method. Whole interchangeable algorithm, swappable on the fly → Strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you've seen it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ASP.NET Core middleware and framework base classes&lt;/strong&gt; — you override specific methods (&lt;code&gt;OnActionExecuting&lt;/code&gt;, &lt;code&gt;ConfigureServices&lt;/code&gt;) while the framework runs the overall pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit test lifecycles&lt;/strong&gt; — &lt;code&gt;Setup()&lt;/code&gt;, the test body, &lt;code&gt;Teardown()&lt;/code&gt; run in a fixed order; you fill in the parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data import pipelines&lt;/strong&gt; — validate, transform, save always run in that order; each importer customizes transform.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Template Method = a recipe with blanks. The parent locks the steps and their order; subclasses fill in only the parts that differ.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 14 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton through State. Next up: Chain of Responsibility — passing a request down a line of handlers.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pattern</category>
    </item>
    <item>
      <title>State: The Traffic Light Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 06:41:04 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/state-the-traffic-light-pattern-1ghl</link>
      <guid>https://dev.to/vicky_acedia/state-the-traffic-light-pattern-1ghl</guid>
      <description>&lt;p&gt;A traffic light is always in exactly one state, and its behavior depends entirely on which one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Red&lt;/strong&gt; — cars stop. After a timer, it becomes Green.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green&lt;/strong&gt; — cars go. After a timer, it becomes Yellow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yellow&lt;/strong&gt; — cars slow. After a timer, it becomes Red.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same object, but what it does — and what it becomes next — changes completely based on its current state. And crucially, the light switches &lt;em&gt;itself&lt;/em&gt;. Red decides when to become Green. Nobody from outside picks the next state.&lt;/p&gt;

&lt;p&gt;That's the State pattern: &lt;strong&gt;an object changes its own behavior when its internal state changes — as if it became a different class.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Without it, you get a mess of flags and &lt;code&gt;if&lt;/code&gt;s scattered across every method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Play&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;_state&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"playing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Already playing"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&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;_state&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"paused"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"playing"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Resumed"&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"playing"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Pause&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;_state&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"playing"&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="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&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;_state&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"paused"&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="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// the same tangle repeated in every method&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every method re-checks the state. Add a new state and you edit every method. This is the classic "state machine as spaghetti."&lt;/p&gt;

&lt;h2&gt;
  
  
  The State way
&lt;/h2&gt;

&lt;p&gt;Each state becomes its own class that knows two things: how to behave, and which state to move to next.&lt;/p&gt;

&lt;p&gt;The state interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IPlayerState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&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 context holds the current state and delegates to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MediaPlayer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IPlayerState&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MediaPlayer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StoppedState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// start here&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// delegate to current state&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&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 state is a class — and it decides the next state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StoppedState&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPlayerState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Starting playback"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlayingState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// the state switches itself&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Can't pause — already stopped"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlayingState&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPlayerState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Already playing"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Pausing"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PausedState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PausedState&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPlayerState&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Resuming"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlayingState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MediaPlayer&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Already paused"&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;Watch it flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MediaPlayer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// Stopped&lt;/span&gt;
&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// "Starting playback"  → Playing&lt;/span&gt;
&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pause&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// "Pausing"            → Paused&lt;/span&gt;
&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// "Resuming"           → Playing&lt;/span&gt;
&lt;span class="n"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// "Already playing"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;if (_state == ...)&lt;/code&gt; anywhere. Each state class handles its own behavior and points to the next. Adding a new state — say a buffering state — means writing one new class; no existing method gets touched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy vs State — the confusion, settled
&lt;/h2&gt;

&lt;p&gt;The code looks almost identical. Both plug in an object and delegate to it. The difference is &lt;em&gt;who decides&lt;/em&gt;, and &lt;em&gt;whether it changes itself&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Who picks the plugged-in object?&lt;/strong&gt; Strategy: you, from outside. State: the object switches itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it change during use?&lt;/strong&gt; Strategy: no — pick once, it runs. State: yes — it flips as things happen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do the objects know each other?&lt;/strong&gt; Strategy: no, they're independent algorithms. State: yes, each state knows the next.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analogy.&lt;/strong&gt; Strategy is Google Maps — &lt;em&gt;you&lt;/em&gt; pick car vs walking. State is a traffic light — red &lt;em&gt;becomes&lt;/em&gt; green on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tell: in Strategy, the context never reassigns its own strategy — you do, from outside. In State, &lt;code&gt;PlayingState&lt;/code&gt; sets &lt;code&gt;player.State = new PausedState()&lt;/code&gt; — the states rewrite the context from inside. Self-transitioning means State. Externally-chosen means Strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you've seen it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Order lifecycle&lt;/strong&gt; — Pending, Paid, Shipped, Delivered, each stage allowing different actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workflow and approval engines&lt;/strong&gt; — Draft, Submitted, Approved, Published.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Game character states&lt;/strong&gt; — Idle, Running, Jumping, Attacking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connection states&lt;/strong&gt; — Disconnected, Connecting, Connected, Reconnecting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI wizards&lt;/strong&gt; — each step enables or disables different buttons.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;State = traffic light. The object holds one state at a time, behaves according to it, and switches itself to the next — no giant if-else tangle.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 13 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton through Iterator. Next up: Template Method — defining a skeleton and letting subclasses fill in the steps.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pattern</category>
    </item>
    <item>
      <title>Iterator: The "Next" Button Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 05:41:00 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/iterator-the-next-button-pattern-590m</link>
      <guid>https://dev.to/vicky_acedia/iterator-the-next-button-pattern-590m</guid>
      <description>&lt;h1&gt;
  
  
  Iterator: The "Next" Button Pattern
&lt;/h1&gt;

&lt;p&gt;You flip through TV channels with a remote. Press "next" — channel 1. Next — channel 2. Next — channel 3.&lt;/p&gt;

&lt;p&gt;You don't know or care how the TV stores channels internally. Array? Linked list? Database? Doesn't matter. You press next and get the next one. The remote gives you one simple way to walk through everything, one at a time, without exposing the internals.&lt;/p&gt;

&lt;p&gt;That's the Iterator pattern: &lt;strong&gt;walk through a collection one item at a time, without knowing how it's stored inside.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Different collections store data completely differently. An array uses an index, a linked list uses node pointers, a tree walks branches. Without Iterator, your loop has to know each collection's internal structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// array — must know it's index-based&lt;/span&gt;
&lt;span class="k"&gt;for&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;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&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;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&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="p"&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;// linked list — completely different traversal&lt;/span&gt;
&lt;span class="kt"&gt;var&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;list&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="k"&gt;while&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="k"&gt;null&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="n"&gt;node&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;Next&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;Every collection type forces a different loop. Change the storage and you rewrite every loop. Iterator hides all that behind one uniform "give me the next item."&lt;/p&gt;

&lt;h2&gt;
  
  
  The C# reality: you already have this
&lt;/h2&gt;

&lt;p&gt;In C#, Iterator is the &lt;code&gt;foreach&lt;/code&gt; loop, and it works on arrays, lists, dictionaries, sets — anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;anyCollection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one line works everywhere because every collection provides an iterator under the hood. You never see the difference between an array and a linked list — &lt;code&gt;foreach&lt;/code&gt; just asks each one for the next item until it's done. You've used this pattern thousands of times without naming it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your own class iterable
&lt;/h2&gt;

&lt;p&gt;Here's the useful part. Say you build a custom &lt;code&gt;Playlist&lt;/code&gt; class and want &lt;code&gt;foreach&lt;/code&gt; to work on it. The keyword &lt;code&gt;yield return&lt;/code&gt; makes that effortless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Playlist&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_songs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_songs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetEnumerator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_songs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// hand out one at a time&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;Now your own class walks like any built-in collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;playlist&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Playlist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Song A"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Song B"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;song&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;// works — because we gave it an iterator&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;song&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;yield return&lt;/code&gt;: the magic word
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;yield return&lt;/code&gt; is genuinely powerful. It pauses the method, hands back one item, and remembers where it left off for the next request. That makes it &lt;strong&gt;lazy&lt;/strong&gt; — items are produced only when asked for, not all at once.&lt;/p&gt;

&lt;p&gt;Which unlocks something wild: infinite sequences.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Naturals&lt;/span&gt;&lt;span class="p"&gt;()&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;n&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&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="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;// infinite loop, but safe&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;  &lt;span class="c1"&gt;// produces one number only when asked&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;Naturals&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// 1 2 3 4 5 — the rest never get computed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An infinite loop that doesn't hang, because &lt;code&gt;yield&lt;/code&gt; only computes the next value when you actually ask for it. Try that with a normal &lt;code&gt;List&lt;/code&gt; and it runs out of memory instantly. This laziness is also why LINQ can chain &lt;code&gt;.Where().Select().Take()&lt;/code&gt; efficiently — it's all iterators, computed on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two interfaces
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IEnumerable&lt;/code&gt;&lt;/strong&gt; means "I can be iterated" — the collection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IEnumerator&lt;/code&gt;&lt;/strong&gt; means "I do the actual walking" — the cursor, with &lt;code&gt;MoveNext()&lt;/code&gt; and &lt;code&gt;Current&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;foreach&lt;/code&gt; calls &lt;code&gt;GetEnumerator()&lt;/code&gt; to get a cursor, then calls &lt;code&gt;MoveNext()&lt;/code&gt; until it's done. You almost never write these by hand — &lt;code&gt;yield return&lt;/code&gt; generates them for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's everywhere
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Every &lt;code&gt;foreach&lt;/code&gt; loop you've ever written.&lt;/li&gt;
&lt;li&gt;All of LINQ — &lt;code&gt;.Where()&lt;/code&gt;, &lt;code&gt;.Select()&lt;/code&gt;, &lt;code&gt;.Take()&lt;/code&gt; return lazy iterators.&lt;/li&gt;
&lt;li&gt;Streaming large files or database results row by row via &lt;code&gt;yield&lt;/code&gt;, instead of loading everything into memory.&lt;/li&gt;
&lt;li&gt;Paginated APIs that yield results page by page.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Iterator = the "next" button. Walk a collection one item at a time without caring how it's stored — and in C#, &lt;code&gt;foreach&lt;/code&gt; + &lt;code&gt;yield return&lt;/code&gt; give it to you for free.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 12 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, Observer, Strategy, and Command. Next up: State — when an object changes its behavior as its state changes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>computerscience</category>
      <category>design</category>
    </item>
    <item>
      <title>Command: The TV Remote Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 05:36:14 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/command-the-tv-remote-pattern-3ajf</link>
      <guid>https://dev.to/vicky_acedia/command-the-tv-remote-pattern-3ajf</guid>
      <description>&lt;p&gt;Look at a TV remote. Each button is a wrapped-up request. Power means "turn the TV on." Volume+ means "increase volume." Channel means "change channel."&lt;/p&gt;

&lt;p&gt;The remote has no idea how the TV actually turns on internally. The button just holds the instruction. Press it, and the instruction runs on the TV.&lt;/p&gt;

&lt;p&gt;Here's the magic: because each action is now an object — a button — you can do things you couldn't before. Record a sequence of presses, replay them, or undo the last one.&lt;/p&gt;

&lt;p&gt;That's the Command pattern: &lt;strong&gt;wrap a request as an object&lt;/strong&gt;, so you can pass it around, queue it, log it, or undo it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Normally you call a method directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TurnOn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// happens immediately, then vanishes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The action is a fleeting event. You can't store it, queue it, undo it, or ask "what did the user just do?" Once it runs, it's gone. Command turns that verb into a noun — an object you can hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three players
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Receiver&lt;/strong&gt; — the thing that does the actual work (the TV, the light).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command&lt;/strong&gt; — an object wrapping "call &lt;em&gt;this&lt;/em&gt; method on the receiver."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoker&lt;/strong&gt; — triggers commands but knows nothing about what they do (the remote).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;Every command can execute, and optionally undo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ICommand&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Undo&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 receiver does the real work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Light&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;On&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Light ON"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Off&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Light OFF"&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 concrete command wraps one action on the receiver — and knows its own reverse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurnOnCommand&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Light&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TurnOnCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Light&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;On&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Undo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Off&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// the reverse action&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TurnOffCommand&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICommand&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Light&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TurnOffCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Light&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Off&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Undo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;    &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_light&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;On&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 invoker presses buttons and keeps a history — but knows nothing about lights:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RemoteControl&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_history&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PressButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ICommand&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// remember it, so we can undo&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PressUndo&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;_history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;_history&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;Undo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// undo the last thing&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;Use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;light&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Light&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RemoteControl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PressButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TurnOnCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// Light ON&lt;/span&gt;
&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PressButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TurnOffCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;light&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// Light OFF&lt;/span&gt;
&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PressUndo&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                             &lt;span class="c1"&gt;// Light ON  — undo!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remote replayed and undid actions — because each action was an &lt;em&gt;object&lt;/em&gt; it could store on a stack. You cannot do this with plain &lt;code&gt;light.On()&lt;/code&gt; method calls. That's the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "action as object" unlocks
&lt;/h2&gt;

&lt;p&gt;Once a request is an object, you get superpowers for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Undo / Redo&lt;/strong&gt; — keep a history stack and call &lt;code&gt;.Undo()&lt;/code&gt;. Every text editor works this way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queuing&lt;/strong&gt; — put commands in a queue and run them later. This is how background job systems work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling&lt;/strong&gt; — "run this command at 2 AM."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging and audit&lt;/strong&gt; — store executed commands to replay or review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Macros&lt;/strong&gt; — bundle several commands into one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are possible when the action is just a method call that runs and disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you've seen it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Undo/redo&lt;/strong&gt; in any editor — Word, Photoshop, VS Code — is a command history stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background job queues&lt;/strong&gt; (Hangfire, message queues) — each job is essentially a command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ICommand&lt;/code&gt; in WPF/MVVM&lt;/strong&gt; — button bindings are literally this pattern by name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The CQRS command side&lt;/strong&gt; and MediatR's &lt;code&gt;IRequest&lt;/code&gt; — command objects sent to handlers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Command vs Strategy
&lt;/h2&gt;

&lt;p&gt;Both wrap behavior in an object, but the intent differs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strategy&lt;/strong&gt; is about &lt;em&gt;how&lt;/em&gt; to do one thing, swappable — which shipping algorithm to run. It usually runs immediately, no history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command&lt;/strong&gt; is about &lt;em&gt;what&lt;/em&gt; to do, captured as an object so it can be queued, undone, or logged. The action itself becomes storable data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Strategy chooses a method. Command captures a request.&lt;/p&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Command = TV remote button. Wrap an action as an object so you can store it, queue it, replay it, or undo it — things a plain method call can never do.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 11 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, Observer, and Strategy. Next up: Iterator — walking a collection without exposing its internals.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>csharp</category>
      <category>design</category>
      <category>software</category>
    </item>
    <item>
      <title>Strategy: The Google Maps Travel-Mode Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 05:24:03 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/strategy-the-google-maps-travel-mode-pattern-3dbj</link>
      <guid>https://dev.to/vicky_acedia/strategy-the-google-maps-travel-mode-pattern-3dbj</guid>
      <description>&lt;h1&gt;
  
  
  Strategy: The Google Maps Travel-Mode Pattern
&lt;/h1&gt;

&lt;p&gt;You open Google Maps to go somewhere, and it asks: how do you want to travel? By car, walking, public transport, or cycling.&lt;/p&gt;

&lt;p&gt;Same goal — get from A to B — but each option runs a completely different algorithm to work out the route. You pick one, and Maps swaps in that method. Tomorrow you pick another; same app, different strategy, chosen at runtime.&lt;/p&gt;

&lt;p&gt;That's the Strategy pattern: &lt;strong&gt;a family of interchangeable algorithms, picked and swapped at runtime.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;Say you calculate shipping cost, and it depends on the method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateShipping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&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;method&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"standard"&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;weight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&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;method&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"express"&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;weight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;else&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;method&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"overnight"&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;weight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// add "drone"? edit this method again...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every new method means editing this one function. It becomes a dumping ground of &lt;code&gt;if&lt;/code&gt;s — hard to test each branch alone, and it keeps forcing you to modify code that already works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strategy way
&lt;/h2&gt;

&lt;p&gt;Define the shape all algorithms share:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IShippingStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&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 algorithm becomes its own small class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StandardShipping&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IShippingStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExpressShipping&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IShippingStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OvernightShipping&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IShippingStrategy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;50&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 context holds a strategy and delegates to it, without knowing which one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShippingCalculator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;IShippingStrategy&lt;/span&gt; &lt;span class="n"&gt;_strategy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ShippingCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IShippingStrategy&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_strategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;SetStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IShippingStrategy&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_strategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_strategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight&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;Swap strategies at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ShippingCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StandardShipping&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// 50&lt;/span&gt;

&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ExpressShipping&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  &lt;span class="c1"&gt;// swapped the algorithm, same object&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// 120&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add drone delivery? Write one &lt;code&gt;DroneShipping&lt;/code&gt; class. The calculator never changes. You add a new class instead of modifying working code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea behind it
&lt;/h2&gt;

&lt;p&gt;Strategy replaces a big conditional — an &lt;code&gt;if/else&lt;/code&gt; or &lt;code&gt;switch&lt;/code&gt; picking behavior — with a set of small classes plus composition. Instead of the code &lt;em&gt;deciding&lt;/em&gt; what to do inside one method, you &lt;em&gt;hand it&lt;/em&gt; the behavior you want.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't bury the "how" inside an if-else. Inject the way to do it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The C# shortcut
&lt;/h2&gt;

&lt;p&gt;For simple strategies you don't even need classes — just pass a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShippingCalculator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_strategy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ShippingCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_strategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;Calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;_strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;standard&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ShippingCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;express&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ShippingCalculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;Func&amp;lt;&amp;gt;&lt;/code&gt; or lambda &lt;em&gt;is&lt;/em&gt; a strategy. Every time you pass a comparison to &lt;code&gt;list.Sort(...)&lt;/code&gt; or a predicate to &lt;code&gt;.Where(...)&lt;/code&gt;, you're using Strategy. LINQ is soaked in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategy vs Factory
&lt;/h2&gt;

&lt;p&gt;These get confused, but the jobs are different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Factory&lt;/strong&gt; decides &lt;em&gt;which object to create&lt;/em&gt; — its job is creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy&lt;/strong&gt; decides &lt;em&gt;which algorithm to run&lt;/em&gt; — its job is behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They often team up: a factory &lt;em&gt;creates&lt;/em&gt; the right strategy, then the context &lt;em&gt;runs&lt;/em&gt; it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you've seen it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LINQ&lt;/strong&gt; — &lt;code&gt;.Where(predicate)&lt;/code&gt;, &lt;code&gt;.OrderBy(selector)&lt;/code&gt;; predicates and selectors are strategies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;List.Sort(IComparer)&lt;/code&gt;&lt;/strong&gt; — the comparer is a pluggable sorting strategy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Payment processing&lt;/strong&gt; — pick Card, UPI, or Wallet strategy at checkout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression, encryption, pricing rules, discount engines&lt;/strong&gt; — anywhere "same task, multiple methods."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Strategy = Google Maps travel mode. Same goal, a family of swappable algorithms, pick one at runtime — adding a new one never touches existing code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 10 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, and Observer. Next up: Command — wrapping a request as an object.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pattern</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Observer: The YouTube Subscribe Pattern</title>
      <dc:creator>Vignesh Athiappan</dc:creator>
      <pubDate>Sun, 02 Aug 2026 05:20:34 +0000</pubDate>
      <link>https://dev.to/vicky_acedia/observer-the-youtube-subscribe-pattern-npd</link>
      <guid>https://dev.to/vicky_acedia/observer-the-youtube-subscribe-pattern-npd</guid>
      <description>&lt;p&gt;You subscribe to a YouTube channel and hit the bell. Now, whenever the creator uploads, you get notified automatically. You don't sit there refreshing every five minutes asking "new video yet?" The channel &lt;em&gt;pushes&lt;/em&gt; the news to everyone subscribed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;channel&lt;/strong&gt; is the thing being watched — the Subject.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You and the other subscribers&lt;/strong&gt; are the Observers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new upload&lt;/strong&gt; is the event that triggers notifications to everyone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And subscribers come and go freely. You can unsubscribe anytime, and the channel doesn't care who's on the list — it just notifies whoever currently is.&lt;/p&gt;

&lt;p&gt;That's the Observer pattern: &lt;strong&gt;when one object changes, all its dependents get notified automatically.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem it solves
&lt;/h2&gt;

&lt;p&gt;An order gets placed, and several things must happen: send an email, update inventory, log analytics, notify the warehouse. The naive way glues them all onto the order class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...place order...&lt;/span&gt;
    &lt;span class="n"&gt;_emailService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Send&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
    &lt;span class="n"&gt;_inventoryService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
    &lt;span class="n"&gt;_analyticsService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Track&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;
    &lt;span class="n"&gt;_warehouseService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Notify&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;Every time you add a new reaction — "also send an SMS" — you edit the order class. It knows about everyone and is tightly coupled to all of them. Observer breaks exactly this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Observer way
&lt;/h2&gt;

&lt;p&gt;First, an interface describing what a subscriber looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IOrderObserver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnOrderPlaced&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;orderId&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 subject keeps a list of subscribers and notifies them — without knowing who they are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IOrderObserver&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_observers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOrderObserver&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Unsubscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IOrderObserver&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&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;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...place the order...&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_observers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// notify everyone on the list&lt;/span&gt;
            &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnOrderPlaced&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// no idea who they actually are&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 observer reacts in its own way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailNotifier&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderObserver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnOrderPlaced&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Email sent for #&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryUpdater&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IOrderObserver&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnOrderPlaced&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Inventory updated for #&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EmailNotifier&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InventoryUpdater&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;101&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Email sent for #101&lt;/span&gt;
&lt;span class="c1"&gt;// Inventory updated for #101&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add SMS later? Write &lt;code&gt;SmsNotifier&lt;/code&gt;, call &lt;code&gt;Subscribe(new SmsNotifier())&lt;/code&gt;. &lt;code&gt;OrderService&lt;/code&gt; never changes — it has no idea SMS exists, it just loops over whoever subscribed. The subject and its reactions are fully decoupled. That's the payoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The C# native way
&lt;/h2&gt;

&lt;p&gt;C# has Observer baked into the language via events, so you rarely write the observer list by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;OrderPlaced&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// the "channel"&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrder&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;orderId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...place order...&lt;/span&gt;
        &lt;span class="n"&gt;OrderPlaced&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// notify all subscribers&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderPlaced&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Email sent for #&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderPlaced&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Inventory updated for #&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PlaceOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;101&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// both fire automatically&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;+=&lt;/code&gt; on an event, every button &lt;code&gt;Click&lt;/code&gt; handler, every &lt;code&gt;PropertyChanged&lt;/code&gt; in MVVM — that's Observer. You've been using it since day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's everywhere
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI events&lt;/strong&gt; — &lt;code&gt;button.Click += ...&lt;/code&gt;, every DOM &lt;code&gt;addEventListener&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MVVM and data binding&lt;/strong&gt; — &lt;code&gt;INotifyPropertyChanged&lt;/code&gt; &lt;em&gt;is&lt;/em&gt; Observer; the UI observes your model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event buses and domain events&lt;/strong&gt; — publish once, many handlers react.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SignalR, webhooks, pub/sub&lt;/strong&gt; — the same idea at network scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;IObservable&amp;lt;T&amp;gt;&lt;/code&gt; and Reactive Extensions&lt;/strong&gt; — Observer turned into full data streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One line to remember
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Observer = YouTube subscribe. When the subject changes, everyone on the list gets notified automatically — and the subject doesn't know or care who's listening.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Part 9 of a series on must-know design patterns in C#, explained the simple way. Earlier parts covered Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, and Composite. Next up: Strategy — the choose-your-algorithm pattern.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pattern</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
