<?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: Toc am</title>
    <description>The latest articles on DEV Community by Toc am (@amtocbot).</description>
    <link>https://dev.to/amtocbot</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%2F4051140%2F0072bb72-7db2-45a7-a59b-7eef8697f644.png</url>
      <title>DEV Community: Toc am</title>
      <link>https://dev.to/amtocbot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amtocbot"/>
    <language>en</language>
    <item>
      <title>How Virtual Memory Works — Why every program thinks it owns all of RAM.</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Wed, 05 Aug 2026 02:50:07 +0000</pubDate>
      <link>https://dev.to/amtocbot/how-virtual-memory-works-why-every-program-thinks-it-owns-all-of-ram-3g64</link>
      <guid>https://dev.to/amtocbot/how-virtual-memory-works-why-every-program-thinks-it-owns-all-of-ram-3g64</guid>
      <description>&lt;p&gt;&lt;em&gt;Why every program thinks it owns all of RAM.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every process runs in its own private address space, as if it had the whole machine to itself. The OS and CPU maintain that illusion by mapping virtual addresses to physical ones, page by page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translating an address
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Virtual address.&lt;/strong&gt; Your program uses addresses that mean nothing to the hardware directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page table.&lt;/strong&gt; The OS keeps a map from virtual pages to physical frames.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLB.&lt;/strong&gt; A small cache of recent translations avoids walking the table every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page fault.&lt;/strong&gt; If a page isn't in RAM, the OS loads it from disk and retries.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What the illusion buys you
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isolation.&lt;/strong&gt; One process can't read another's memory — different maps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overcommit.&lt;/strong&gt; Programs can use more address space than physical RAM, backed by disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharing.&lt;/strong&gt; Read-only pages (like libraries) map into many processes once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line mental model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Give each program a fake, private map of memory — and the OS quietly translates it to the real thing.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;This is part of &lt;strong&gt;LearningTechBasics&lt;/strong&gt; — one tech idea a day, each with an animated diagram and a 60-second narrated video.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://amtocsoft.blogspot.com/2026/08/how-virtual-memory-works.html" rel="noopener noreferrer"&gt;Animated version with the live diagram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a class="mentioned-user" href="https://dev.to/amtocbot"&gt;@amtocbot&lt;/a&gt; · #LearningTechBasics&lt;/p&gt;

</description>
      <category>operatingsystems</category>
      <category>systems</category>
      <category>memory</category>
      <category>cs</category>
    </item>
    <item>
      <title>How Race Conditions Happen — Two threads, one variable, and a bug that only shows up sometimes.</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Fri, 31 Jul 2026 06:41:52 +0000</pubDate>
      <link>https://dev.to/amtocbot/how-race-conditions-happen-two-threads-one-variable-and-a-bug-that-only-shows-up-sometimes-3dhh</link>
      <guid>https://dev.to/amtocbot/how-race-conditions-happen-two-threads-one-variable-and-a-bug-that-only-shows-up-sometimes-3dhh</guid>
      <description>&lt;p&gt;&lt;em&gt;Two threads, one variable, and a bug that only shows up sometimes.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A race condition is a bug where the result depends on the exact timing of concurrent operations. It hides in the gap between reading a value and writing it back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The classic lost update
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Both read.&lt;/strong&gt; Thread A and Thread B both read count = 5.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both compute.&lt;/strong&gt; Each independently decides the new value is 6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both write.&lt;/strong&gt; They both store 6 — but two increments happened, so it should be 7.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-determinism.&lt;/strong&gt; Whether it breaks depends on scheduling, so it passes tests and fails in production.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to prevent them
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Locks.&lt;/strong&gt; A mutex makes read-modify-write atomic, one thread at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Atomics.&lt;/strong&gt; Hardware atomic operations do increment as a single indivisible step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid shared state.&lt;/strong&gt; Message passing and immutability sidestep the problem entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line mental model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;When correctness depends on who wins a timing race, you don't have a program — you have a coin flip.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;This is part of &lt;strong&gt;LearningTechBasics&lt;/strong&gt; — one tech idea a day, each with an animated diagram and a 60-second narrated video.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://amtocsoft.blogspot.com/2026/07/how-race-conditions-happen.html" rel="noopener noreferrer"&gt;Animated version with the live diagram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a class="mentioned-user" href="https://dev.to/amtocbot"&gt;@amtocbot&lt;/a&gt; · #LearningTechBasics&lt;/p&gt;

</description>
      <category>concurrency</category>
      <category>programming</category>
      <category>threads</category>
      <category>bugs</category>
    </item>
    <item>
      <title>I spent day two cutting a production cost that was already effectively zero</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Thu, 30 Jul 2026 12:06:42 +0000</pubDate>
      <link>https://dev.to/amtocbot/i-spent-day-two-cutting-a-production-cost-that-was-already-effectively-zero-4ibe</link>
      <guid>https://dev.to/amtocbot/i-spent-day-two-cutting-a-production-cost-that-was-already-effectively-zero-4ibe</guid>
      <description>&lt;p&gt;Sixteen days into this project the channel has 20 YouTube subscribers, one follower on two of the five platforms it publishes to, zero email subscribers and zero comments.&lt;/p&gt;

&lt;p&gt;I spent day two of the project working out how to make an episode cost less to produce.&lt;/p&gt;

&lt;p&gt;Both of those sentences are true at the same time, and that is the story this series is about. Here is the setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I assumed
&lt;/h2&gt;

&lt;p&gt;On 13 July I decided to build a faceless short-form channel full time, and to treat it as a compounding asset rather than a job of work with an invoice at the end.&lt;/p&gt;

&lt;p&gt;The format is "Senior vs Junior". One coding problem, solved twice: the naive way and the veteran way. Vertical 9:16, split screen, panicked junior under red light on the left, calm senior in cool blue on the right. Tagline: "the difference between code that runs and code that ships."&lt;/p&gt;

&lt;p&gt;The bet was technical accuracy. I have shipped production code in C, C++, C#, Java, JavaScript, TypeScript and VB. Anyone can generate a channel that looks like this one in an afternoon; what they cannot generate is a correct explanation of why the junior version falls over. Developer audiences sit at the top of the CPM tables, so accuracy in a top-CPM niche looked like the one defensible position available to a solo operator competing with automated slop.&lt;/p&gt;

&lt;p&gt;The second assumption was quieter and it shaped everything that followed: I assumed the binding constraint would be credits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;The pipeline was proven on 14 July, one day after the decision. Four steps.&lt;/p&gt;

&lt;p&gt;Generate a still frame for each block. Animate each still into an 8-second clip. Record one voiceover take per beat. Assemble the whole thing locally, syncing each block's audio to its own block and burning the captions in.&lt;/p&gt;

&lt;p&gt;An episode is four blocks of roughly 10 seconds: cold open, junior code, senior code, the point. About 40 seconds end to end.&lt;/p&gt;

&lt;p&gt;The same day, the funnel went live: a one-page site with email capture backed by a SQLite/D1 table, offering one senior tip a week. Then I built episodes 1 to 5 as a launch buffer before publishing anything at all, and Episode 1 went out on 17 July, starting on YouTube with the cross-posts following.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I found out
&lt;/h2&gt;

&lt;p&gt;I itemised the cost by stage rather than by episode: the still frames, the animation of each beat, the voiceover takes.&lt;/p&gt;

&lt;p&gt;One stage dominates everything. Animating four beats at eight seconds each is the overwhelming majority of an episode's credit cost. The frames and the voice takes together are a rounding error beside it, which means the only lever worth pulling is the number of beats you animate.&lt;/p&gt;

&lt;p&gt;The plan the account runs on carries roughly 3,000 credits a cycle, and puts an episode somewhere between 30 and 110 credits depending on how many beats get generated. Real builds sit at the top of that range, because frames come back wrong and get regenerated and animations come back unusable.&lt;/p&gt;

&lt;p&gt;Now the part I read correctly and then ignored. The subscription renews whether I use it or not, and the plan's own reference does the arithmetic for you: at that per-episode cost, roughly 3,000 credits a cycle is about a hundred episodes before it runs dry. I was producing five. The marginal cost of one more episode was, in any sense that matters, zero.&lt;/p&gt;

&lt;p&gt;So the number I spent day two optimising was already zero. I optimised it anyway, because on day two it was the only number I had that I could count. There were no views, no watch time, no signups and nothing in the D1 table to measure instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;Two decisions, both made on day two, both to cut credits.&lt;/p&gt;

&lt;p&gt;First, reuse. Block 1 — the split-screen hook clip — is generated once and reused on every episode. So is block 4: the payoff card and the payoff voiceover that goes over it. Only the hook narration and the two code beats change from episode to episode. The stated rationale was brand recognition, a recurring open and sign-off that makes a catalogue feel like a channel. The real rationale was that it removed two of the four animated beats, which is roughly half the credit cost of an episode.&lt;/p&gt;

&lt;p&gt;Second, one voice. A single preset voice — "Sterling" — reads every line of every episode. The junior, the senior, the hook, the payoff. One voice is one fewer variable in a pipeline I had just built, and consistency across a catalogue reads as a channel rather than a pile of clips.&lt;/p&gt;

&lt;p&gt;Both decisions were cheap to make and both are load-bearing across all five launch episodes. Remember them.&lt;/p&gt;

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

&lt;p&gt;Do the measurement, then check what the measurement actually said before you act on it. I got the credit accounting right, correctly concluded that marginal cost per episode was effectively zero, and then spent a day halving zero — because credits were countable on day two and the things that mattered were not. If the only number you can measure is the one that does not constrain you, the honest move is to publish sooner and let the platform hand you a number that does, rather than to optimise the one on your own dashboard because it is sitting there.&lt;/p&gt;




&lt;p&gt;This is part of an honest log of building a faceless coding channel from zero.&lt;br&gt;
The channel: &lt;a href="https://www.youtube.com/@amtocbot" rel="noopener noreferrer"&gt;https://www.youtube.com/@amtocbot&lt;/a&gt;&lt;br&gt;
One senior tip a week, by email: &lt;a href="https://seniorvsjunior.higgsfield.app" rel="noopener noreferrer"&gt;https://seniorvsjunior.higgsfield.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>devjournal</category>
      <category>video</category>
      <category>ai</category>
    </item>
    <item>
      <title>How CDNs Work — Why a site loads fast whether you’re in Tokyo or Toronto.</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Thu, 30 Jul 2026 08:22:00 +0000</pubDate>
      <link>https://dev.to/amtocbot/how-cdns-work-why-a-site-loads-fast-whether-youre-in-tokyo-or-toronto-3k5h</link>
      <guid>https://dev.to/amtocbot/how-cdns-work-why-a-site-loads-fast-whether-youre-in-tokyo-or-toronto-3k5h</guid>
      <description>&lt;p&gt;&lt;em&gt;Why a site loads fast whether you're in Tokyo or Toronto.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A Content Delivery Network puts copies of your content in hundreds of locations worldwide, so users are served from a nearby edge instead of your single origin server far away.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a request is served
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Anycast routing.&lt;/strong&gt; The user's request goes to the nearest edge point of presence automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache hit.&lt;/strong&gt; If the edge already has the file, it returns it immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache miss.&lt;/strong&gt; Otherwise the edge fetches from origin, stores it, and serves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL &amp;amp; purge.&lt;/strong&gt; Cached copies expire on a TTL or can be purged when content changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Beyond speed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Offload.&lt;/strong&gt; The origin handles a fraction of traffic, saving cost and load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resilience.&lt;/strong&gt; Edges absorb spikes and shield the origin from DDoS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dynamic too.&lt;/strong&gt; Modern CDNs run code at the edge, not just cache static files.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line mental model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Move the content close to the user, and distance stops being the bottleneck.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;This is part of &lt;strong&gt;LearningTechBasics&lt;/strong&gt; — one tech idea a day, each with an animated diagram and a 60-second narrated video.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://amtocsoft.blogspot.com/2026/07/how-cdns-work-learningtechbasics.html" rel="noopener noreferrer"&gt;Animated version with the live diagram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a class="mentioned-user" href="https://dev.to/amtocbot"&gt;@amtocbot&lt;/a&gt; · #LearningTechBasics&lt;/p&gt;

</description>
      <category>cdn</category>
      <category>performance</category>
      <category>webdev</category>
      <category>networking</category>
    </item>
    <item>
      <title>How Compilers Work — From text you wrote to instructions a CPU runs.</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Wed, 29 Jul 2026 11:13:04 +0000</pubDate>
      <link>https://dev.to/amtocbot/how-compilers-work-from-text-you-wrote-to-instructions-a-cpu-runs-4bea</link>
      <guid>https://dev.to/amtocbot/how-compilers-work-from-text-you-wrote-to-instructions-a-cpu-runs-4bea</guid>
      <description>&lt;p&gt;&lt;em&gt;From text you wrote to instructions a CPU runs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A compiler is a translator with several passes. It reads your source, checks it makes sense, and lowers it step by step into machine code — optimizing along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  The classic phases
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lexing.&lt;/strong&gt; Break the source into tokens: keywords, identifiers, literals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsing.&lt;/strong&gt; Assemble tokens into an abstract syntax tree following the grammar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic analysis.&lt;/strong&gt; Type-check, resolve names, catch misuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IR.&lt;/strong&gt; Lower the tree into a simpler intermediate representation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize.&lt;/strong&gt; Fold constants, inline, remove dead code on the IR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codegen.&lt;/strong&gt; Emit machine instructions for the target CPU.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why the middle exists
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Separation.&lt;/strong&gt; A shared IR lets one backend serve many languages and one language target many CPUs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimization surface.&lt;/strong&gt; The IR is where most speedups happen, independent of syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JIT vs AOT.&lt;/strong&gt; Some compile ahead of time; others compile hot paths while the program runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line mental model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Compiling is a staircase: each pass lowers your code to something simpler until only machine instructions remain.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;This is part of &lt;strong&gt;LearningTechBasics&lt;/strong&gt; — one tech idea a day, each with an animated diagram and a 60-second narrated video.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://amtocsoft.blogspot.com/2026/07/how-compilers-work-learningtechbasics.html" rel="noopener noreferrer"&gt;Animated version with the live diagram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a class="mentioned-user" href="https://dev.to/amtocbot"&gt;@amtocbot&lt;/a&gt; · #LearningTechBasics&lt;/p&gt;

</description>
      <category>compilers</category>
      <category>languages</category>
    </item>
    <item>
      <title>How OAuth Works — hand out a token, never the password</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Wed, 29 Jul 2026 00:08:39 +0000</pubDate>
      <link>https://dev.to/amtocbot/how-oauth-works-hand-out-a-token-never-the-password-520h</link>
      <guid>https://dev.to/amtocbot/how-oauth-works-hand-out-a-token-never-the-password-520h</guid>
      <description>&lt;p&gt;&lt;em&gt;"Log in with Google" — without Google ever seeing the other site's password.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;OAuth lets one app act on your behalf at another service without ever handling your password. Instead of credentials, apps get a scoped, revocable token.&lt;/p&gt;

&lt;h2&gt;
  
  
  The authorization-code flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Redirect.&lt;/strong&gt; The app sends you to the provider with the scopes it wants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent.&lt;/strong&gt; You authenticate with the provider and approve (or deny) those scopes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code.&lt;/strong&gt; The provider redirects back to the app with a short-lived authorization code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token exchange.&lt;/strong&gt; The app's server swaps the code (plus its secret) for an access token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &amp;amp; refresh.&lt;/strong&gt; The app calls APIs with the token, refreshing it as needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why it's safer than sharing a password
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scoped.&lt;/strong&gt; A token grants only the permissions you approved, not full account access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Revocable.&lt;/strong&gt; You can revoke one app without changing your password.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PKCE.&lt;/strong&gt; Public clients add a proof step so an intercepted code alone is useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line mental model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Hand out a narrow, revocable token — never the password itself.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;This is part of &lt;strong&gt;LearningTechBasics&lt;/strong&gt; — one tech idea a day, each with an animated diagram and a 60-second narrated video.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://amtocsoft.blogspot.com/2026/07/how-oauth-works-learningtechbasics.html" rel="noopener noreferrer"&gt;Animated version with the live diagram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a class="mentioned-user" href="https://dev.to/amtocbot"&gt;@amtocbot&lt;/a&gt; · #LearningTechBasics&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>api</category>
    </item>
    <item>
      <title>Retry Storms in C#: Exponential Backoff and Jitter with HttpClient</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Tue, 28 Jul 2026 13:20:34 +0000</pubDate>
      <link>https://dev.to/amtocbot/retry-storms-in-c-exponential-backoff-and-jitter-with-httpclient-lah</link>
      <guid>https://dev.to/amtocbot/retry-storms-in-c-exponential-backoff-and-jitter-with-httpclient-lah</guid>
      <description>&lt;p&gt;The database blipped for four seconds. The incident lasted forty minutes.&lt;/p&gt;

&lt;p&gt;Nothing in the timeline explains the gap except the retry code your own clients are running.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&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;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://api.internal/orders"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;catch&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// transient. it'll come back.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Client log during the incident:&lt;/span&gt;
&lt;span class="c1"&gt;// 14:02:11.418  attempt failed&lt;/span&gt;
&lt;span class="c1"&gt;// 14:02:11.419  attempt failed&lt;/span&gt;
&lt;span class="c1"&gt;// 14:02:11.421  attempt failed&lt;/span&gt;
&lt;span class="c1"&gt;// ...about one attempt per millisecond, per client, across 5,000 clients.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why it breaks
&lt;/h2&gt;

&lt;p&gt;Start with the part nobody expects: &lt;strong&gt;failure is fast&lt;/strong&gt;. A successful call does DNS, a TLS handshake, a query, and serialisation — tens of milliseconds. A refused connection comes back as a TCP RST in under a millisecond. A 503 from the load balancer never reaches the application at all. So the loop's period is set by the &lt;em&gt;fastest&lt;/em&gt; path through the system, and the fastest path is the broken one. The moment the service degrades, every client starts calling it harder than it ever did while healthy.&lt;/p&gt;

&lt;p&gt;Now multiply. Normal load is one request per user action. Under failure it is a continuous stream per client, and 5,000 clients pointed at a service sized for a few thousand requests per second will offer it millions. Those attempts are not free just because they fail: each one still costs a socket accept, a TLS handshake, a thread-pool work item, and a slot in the request queue on the server side. The capacity needed to drain the backlog is exactly the capacity being spent rejecting new arrivals.&lt;/p&gt;

&lt;p&gt;That is the whole failure mode. At 14:02:15 the original trigger is gone — the failover finished, the slow query completed — and the system stays down anyway, because the retry traffic is now the load. The service has two stable states, and your clients are pinning it in the wrong one. Recovery requires the offered load to drop, which is the one thing a retry loop will never do.&lt;/p&gt;

&lt;h3&gt;
  
  
  A detour worth taking: a fixed delay is not backoff
&lt;/h3&gt;

&lt;p&gt;This is the fix everyone reaches for first.&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;catch&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// there. no more spinning.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does stop the spin, and it drops each client from a thousand attempts per second to one. It also blocks a thread-pool thread inside async code, which is its own problem. But the herd survives.&lt;/p&gt;

&lt;p&gt;The clients were &lt;em&gt;synchronised by the outage itself&lt;/em&gt;. They all failed at 14:02:11.418, so they all wake at 14:02:12.418, and again at 14:02:13.418. The failure event acted as a clock-sync pulse. What the server sees is not a steady 5,000 requests per second — it is all 5,000 arriving at the same instant, once a second, with dead air in between. Peak concurrency is what topples a connection pool, and peak concurrency is unchanged. You have made the graph prettier without moving the number that matters.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpResponseMessage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetWithRetryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;client&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;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&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;baseDelay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&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;maxDelay&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&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;for&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;attempt&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="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&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;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&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="nf"&gt;IsTransient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&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;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&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;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TaskCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;
                                            &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&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;// Exponential ceiling, then pick uniformly *below* it. That second&lt;/span&gt;
        &lt;span class="c1"&gt;// step is the jitter, and it is the part that scatters the herd.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ceiling&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxDelay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                               &lt;span class="n"&gt;baseDelay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&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;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NextDouble&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&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;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsTransient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt; &lt;span class="n"&gt;code&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;code&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestTimeout&lt;/span&gt;       &lt;span class="c1"&gt;// 408&lt;/span&gt;
    &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TooManyRequests&lt;/span&gt;   &lt;span class="c1"&gt;// 429&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="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exponential growth thins the traffic over time. The uniform draw spreads each wave across the whole interval, so two clients that failed in the same millisecond come back at two random points inside a 200 ms window instead of together — and that window doubles on every attempt after, to a 20-second ceiling. &lt;code&gt;Random.Shared&lt;/code&gt; is thread-safe, so you do not need one &lt;code&gt;Random&lt;/code&gt; per caller.&lt;/p&gt;

&lt;p&gt;Siblings worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Polly v8: &lt;code&gt;new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions { BackoffType = DelayBackoffType.Exponential, UseJitter = true })&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AddStandardResilienceHandler()&lt;/code&gt; from &lt;code&gt;Microsoft.Extensions.Http.Resilience&lt;/code&gt;, which wires retry, jitter, a circuit breaker and a total timeout into a named &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the server sends &lt;code&gt;Retry-After&lt;/code&gt; (&lt;code&gt;response.Headers.RetryAfter&lt;/code&gt;), honour it — then add jitter on top anyway, or every client obeying it wakes in lockstep again.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When backoff isn't enough
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Retry is only safe on idempotent operations.&lt;/strong&gt; A timeout does not tell you the call failed; it tells you the &lt;em&gt;response&lt;/em&gt; was lost. The charge may already be posted. &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; are idempotent by contract; &lt;code&gt;POST&lt;/code&gt; is not. Retrying one needs an idempotency key the server dedupes against, not a smarter delay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Classify before you retry.&lt;/strong&gt; A 400, 401 or 404 will fail identically on attempt five. Retrying it burns the caller's latency budget for a guaranteed failure. That is what &lt;code&gt;IsTransient&lt;/code&gt; above is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cap elapsed time, not attempt count.&lt;/strong&gt; Five attempts with a 30-second HTTP timeout each, plus backoff, is over two minutes — long after the browser gave up. Wrap the whole loop in one &lt;code&gt;CancellationTokenSource(TimeSpan.FromSeconds(10))&lt;/code&gt; and let it cut the retries short.&lt;/p&gt;

&lt;p&gt;And when the dependency is genuinely down rather than flaky, backoff still has every client politely probing a corpse. That is a circuit breaker's job: stop calling at all, and check back with one request instead of all of them.&lt;/p&gt;

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

&lt;p&gt;Backoff spreads out &lt;em&gt;your&lt;/em&gt; retries. Jitter spreads out &lt;em&gt;everyone else's&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Without the second one, you haven't built resilience — you've scheduled a DDoS against yourself, once per second, right on time.&lt;/p&gt;

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




&lt;p&gt;I post one of these every day — the same problem solved the way that works and the way that lasts.&lt;br&gt;
One senior tip a week, by email: &lt;a href="https://seniorvsjunior.higgsfield.app" rel="noopener noreferrer"&gt;https://seniorvsjunior.higgsfield.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why File.ReadAllLines Throws OutOfMemoryException on a 10 GB File in C#</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:31:15 +0000</pubDate>
      <link>https://dev.to/amtocbot/why-filereadalllines-throws-outofmemoryexception-on-a-10-gb-file-in-c-1d28</link>
      <guid>https://dev.to/amtocbot/why-filereadalllines-throws-outofmemoryexception-on-a-10-gb-file-in-c-1d28</guid>
      <description>&lt;p&gt;The job runs fine against the 200 MB sample. It dies in production against the real 10 GB log, before a single line is processed.&lt;/p&gt;

&lt;p&gt;Here is the version almost everyone writes first.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\data\events.log"&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;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&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;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ERROR"&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;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Unhandled exception. System.OutOfMemoryException: Insufficient memory&lt;/span&gt;
&lt;span class="c1"&gt;// to continue the execution of the program.&lt;/span&gt;
&lt;span class="c1"&gt;//    at System.Collections.Generic.List`1.AddWithResize(String)&lt;/span&gt;
&lt;span class="c1"&gt;//    at System.IO.File.ReadAllLines(String path)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing was printed. The process never reached the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it breaks
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;File.ReadAllLines&lt;/code&gt; is not a reading primitive, it is a materialising one. Internally it opens a &lt;code&gt;StreamReader&lt;/code&gt;, appends every &lt;code&gt;ReadLine()&lt;/code&gt; result to a &lt;code&gt;List&amp;lt;string&amp;gt;&lt;/code&gt;, and returns &lt;code&gt;list.ToArray()&lt;/code&gt;. The method only returns once the last line of the file is in memory.&lt;/p&gt;

&lt;p&gt;Three things multiply the cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UTF-16.&lt;/strong&gt; .NET strings hold two bytes per character. Your 10 GB of ASCII on disk becomes ~20 GB of character data on the heap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-object overhead.&lt;/strong&gt; Each line is a separate object: 8 bytes of header, 8 for the method table pointer, 4 for the length, a null terminator, padded to an 8-byte boundary — call it ~24 bytes per line — plus 8 bytes for its slot in the array. At 100-byte lines that's ~107 million objects and roughly 23 GB of heap for a 10 GB file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The doubling.&lt;/strong&gt; The backing array of the &lt;code&gt;List&amp;lt;string&amp;gt;&lt;/code&gt; grows by repeated doubling, and every array above 85,000 bytes lands on the Large Object Heap, which is not compacted by default. You allocate, abandon and fragment your way up through 100 MB, 200 MB, 400 MB of reference arrays, then &lt;code&gt;ToArray()&lt;/code&gt; allocates one more full-size copy alongside the last one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The GC cannot help. Every one of those strings is reachable from the array you asked for, so nothing is garbage. The allocation that finally fails is arbitrary — the exception surfaces wherever the heap happened to run out.&lt;/p&gt;

&lt;h3&gt;
  
  
  A detour worth taking: &lt;code&gt;foreach&lt;/code&gt; does not make it lazy
&lt;/h3&gt;

&lt;p&gt;This is the most common wrong fix:&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;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// still OOMs&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&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;A &lt;code&gt;foreach&lt;/code&gt; evaluates its collection expression &lt;em&gt;once&lt;/em&gt;, in full, before the first iteration. &lt;code&gt;File.ReadAllLines(path)&lt;/code&gt; runs to completion and hands back a finished &lt;code&gt;string[]&lt;/code&gt;; only then does enumeration start. The loop shape tells you nothing. The return type does: &lt;code&gt;string[]&lt;/code&gt; is a result, &lt;code&gt;IEnumerable&amp;lt;string&amp;gt;&lt;/code&gt; is a plan.&lt;/p&gt;

&lt;p&gt;The other reach is &lt;code&gt;File.ReadAllText(path).Split('\n')&lt;/code&gt;, which is strictly worse. A &lt;code&gt;string&lt;/code&gt; is hard-capped at 1,073,741,791 characters — roughly 1.07 billion, no matter how much RAM the box has — so the 10 GB read fails on the string itself, before &lt;code&gt;Split&lt;/code&gt; runs. If it did run, &lt;code&gt;Split&lt;/code&gt; allocates every substring &lt;em&gt;on top of&lt;/em&gt; the original, and on a CRLF file it leaves a stray &lt;code&gt;\r&lt;/code&gt; on the end of every line.&lt;/p&gt;

&lt;p&gt;And "just give the box more RAM" is not a fix, it is a subscription. It makes your memory ceiling a function of your input size, so you pay more every quarter and still fall over the day someone hands you a bigger file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The senior version
&lt;/h2&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;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"C:\data\events.log"&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;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ERROR"&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;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;File.ReadLines&lt;/code&gt; returns a lazy iterator over a &lt;code&gt;StreamReader&lt;/code&gt;. Each &lt;code&gt;MoveNext()&lt;/code&gt; decodes exactly one line and yields it; the &lt;code&gt;foreach&lt;/code&gt; disposes the enumerator — and with it the reader and the file handle — when the loop ends. Peak memory is one line plus the reader's buffers, whether the file is 10 MB or 10 TB. The 10 GB case now runs in a few megabytes.&lt;/p&gt;

&lt;p&gt;Because it is a real &lt;code&gt;IEnumerable&amp;lt;string&amp;gt;&lt;/code&gt;, LINQ composes over it without buffering:&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;firstTen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ERROR"&lt;/span&gt;&lt;span class="p"&gt;))&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;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Siblings worth knowing:&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;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// explicit encoding&lt;/span&gt;

&lt;span class="k"&gt;await&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;string&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLinesAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;// .NET 7+&lt;/span&gt;
    &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Full control: bigger buffer, sequential-scan hint to the OS.&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;reader&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;StreamReader&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;FileStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileAccess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileShare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="n"&gt;bufferSize&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;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FileOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SequentialScan&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;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When streaming isn't enough
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;File.ReadLines&lt;/code&gt; opens the file when you call it, not on the first &lt;code&gt;MoveNext()&lt;/code&gt; — the handle is live before the loop starts. So don't return &lt;code&gt;File.ReadLines(...)&lt;/code&gt; out of a method that owns the path and expect the handle to be closed: it stays open until someone finishes enumerating or disposes the enumerator, a sequence nobody ever enumerates holds it until the finalizer runs, and with the default &lt;code&gt;FileShare.Read&lt;/code&gt; it blocks writers on Windows for the whole run.&lt;/p&gt;

&lt;p&gt;Enumerating twice reads the file twice. That is 10 GB of I/O per pass, and if the file changed in between, the two passes disagree. And the moment you call &lt;code&gt;.OrderBy(...)&lt;/code&gt;, &lt;code&gt;.ToList()&lt;/code&gt; or &lt;code&gt;.GroupBy(...)&lt;/code&gt; on the sequence, you have re-materialised the whole thing and bought the original bug back. &lt;code&gt;.Count()&lt;/code&gt; is the one that looks like those and isn't: it walks the sequence and keeps nothing, so it costs you a full 10 GB pass in time while memory stays flat.&lt;/p&gt;

&lt;p&gt;If you genuinely need random access or several passes, streaming alone won't carry you: build an index of line offsets on one pass, sort externally, or load only the projection you need. Streaming buys you constant memory over a single forward pass — that is the whole contract.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;ReadAllLines&lt;/code&gt; gives you an array; &lt;code&gt;ReadLines&lt;/code&gt; gives you a promise. Only one of them fits in RAM.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Sfy6ry1-ZvU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;p&gt;I post one of these every day — the same problem solved the way that works and the way that lasts.&lt;br&gt;
One senior tip a week, by email: &lt;a href="https://seniorvsjunior.higgsfield.app" rel="noopener noreferrer"&gt;https://seniorvsjunior.higgsfield.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>performance</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How LLMs Generate Text — one token at a time</title>
      <dc:creator>Toc am</dc:creator>
      <pubDate>Tue, 28 Jul 2026 11:00:38 +0000</pubDate>
      <link>https://dev.to/amtocbot/how-llms-generate-text-one-token-at-a-time-57j2</link>
      <guid>https://dev.to/amtocbot/how-llms-generate-text-one-token-at-a-time-57j2</guid>
      <description>&lt;p&gt;&lt;em&gt;One token at a time — a very well-read autocomplete.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A large language model doesn't plan a whole answer up front. It predicts the next token from everything so far, appends it, and repeats — with attention letting it weigh which earlier words matter most.&lt;/p&gt;

&lt;h2&gt;
  
  
  How each token appears
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Tokenize.&lt;/strong&gt; Text is split into subword tokens and mapped to numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embed.&lt;/strong&gt; Each token becomes a vector encoding meaning and position.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attention.&lt;/strong&gt; Every token looks at the others and decides what to focus on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predict.&lt;/strong&gt; The model outputs a probability for every possible next token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample.&lt;/strong&gt; Temperature and top-p pick one; append it and feed the whole thing back in.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why it feels coherent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Context window.&lt;/strong&gt; The model sees thousands of prior tokens at once, keeping track of the thread.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale of training.&lt;/strong&gt; Patterns from vast text let it continue in-style and on-topic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's still prediction.&lt;/strong&gt; No lookup of facts — which is why it can sound confident yet be wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line mental model
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Generation is autocomplete with attention: predict the next token, append, repeat.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;This is part of &lt;strong&gt;LearningTechBasics&lt;/strong&gt; — one tech idea a day, each with an animated diagram and a 60-second narrated video.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;&lt;a href="https://amtocsoft.blogspot.com/2026/07/how-llms-generate-text.html" rel="noopener noreferrer"&gt;Animated version with the live diagram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow &lt;a class="mentioned-user" href="https://dev.to/amtocbot"&gt;@amtocbot&lt;/a&gt; · #LearningTechBasics&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
