C# Loops — From `for` and `foreach` to CPU Pipelines and LLM‑Ready Code

Published: (December 17, 2025 at 07:11 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Most developers use loops every day.
Very few truly understand what happens below the syntax.

  • Why does one for loop fly while another crawls?
  • Why can foreach be free… or secretly expensive?
  • Why does the same loop get faster after it runs for a while?
  • How can understanding loops help you write LLM‑friendly, performance‑predictable code?

This article is a mental‑model upgrade — from beginner syntax to processor‑level reality, modern .NET JIT behavior, and how to reason about loops like a scientist.

If you can write for (int i = 0; i Memory beats syntax every time.

3. Roslyn vs JIT — Who Does the Work?

StageWhat it does
Roslyn (C# compiler)Emits IL, lowers foreach, inserts branches
RyuJIT (runtime)Generates machine code, removes bounds checks, hoists invariants, specializes hot loops, uses tiered compilation + PGO

The same loop may be re‑compiled after warming up, which is why micro‑benchmarks need a warm‑up phase.

4. for, while, do/while — Real Differences

Loop typeKey difference
whileCondition evaluated first
do/whileBody executes at least once
forSame machine shape as while, but intent is clearer

Performance differences are usually noise. Choose based on correctness and readability.

5. foreach Under the Hood

Arrays

foreach (var x in array)
{
    // …
}
  • Lowered to a for loop
  • Bounds checks often eliminated
  • Very fast

List

  • Uses a struct enumerator
  • No allocation
  • Still very fast

IEnumerable

⚠️ Potential performance cliff:

  • Interface dispatch
  • Possible allocation
  • No bounds‑check elimination

Avoid IEnumerable in hot loops.

6. Bounds‑Check Elimination (BCE)

for (int i = 0; i < arr.Length; i++)
{
    // …
}

Rule of thumb

  • Linear access
  • Single index variable
  • Cached length (int len = arr.Length;)

Weird indexing patterns can break BCE.

7. Branch Prediction: Data Beats Code

Two loops with identical code but different data:

Data patternPredictabilityEffect
99 % predictableFastBranch predictor learns the pattern
50/50 randomSlowerFrequent mispredictions

Sometimes sorting data yields bigger gains than rewriting the loop.

8. Span: Zero‑Allocation Iteration

Span<int> slice = array.AsSpan(1, 3);
foreach (ref var x in slice)
    x++;
  • No allocations (stack‑only)
  • Cache‑friendly
  • Safe

Span is one of the most important performance tools in modern .NET.

9. Vectorized Loops (SIMD Taste)

Vector<float> v1, v2;
acc += v1 * v2;
  • Uses SIMD when available
  • Processes multiple elements per instruction
  • Great for numeric workloads

Data layout matters more than loop shape for SIMD.

10. yield return: The Hidden State Machine

IEnumerable<int> Evens()
{
    yield return 2;
}

The compiler generates a state machine:

  • Usually a heap allocation
  • Extra indirections

Great for clarity, but avoid in ultra‑hot paths.

11. World‑Class Loop Heuristics

  • ✅ Prefer contiguous memory
  • ✅ Avoid allocations inside loops
  • ✅ Avoid interface dispatch in hot paths
  • ✅ Let the JIT eliminate bounds checks
  • ✅ Measure with BenchmarkDotNet
  • ✅ Optimize memory before branches

Most performance bugs are memory bugs.

12. Why This Matters for LLM‑Assisted Code

LLMs:

  • Generate correct syntax
  • Do not understand cache lines, branch misprediction, or GC pressure

When you (or an LLM) write loops, keep the hardware realities in mind. Write code that is:

  1. Memory‑friendly – contiguous, cache‑aware
  2. Predictable – avoid random‑access patterns that hurt branch predictors
  3. Allocation‑light – especially in hot paths

By doing so, you’ll get code that not only compiles but also runs efficiently—something no LLM can infer on its own.

All model is the safety net.

If you understand loops at this level, you can:

  • Guide LLMs
  • Review generated code intelligently
  • Predict performance before profiling
  • Write code that scales under real load

Final Thought

A loop is not a construct.
It is a contract between your data, the JIT, and the processor.

Once you understand that, you stop guessing — and start engineering.

Happy looping.

Back to Blog

Related posts

Read more »

C# Minimal API: Response Caching

Response Caching Response caching reduces the number of requests a client or proxy makes to a web server. It also reduces the amount of work the web server per...