Showing posts with label Iterator Design Pattern. Show all posts
Showing posts with label Iterator Design Pattern. Show all posts

Wednesday, July 29, 2026

Iterator Design Pattern

Mastering Design Patterns in C# and ASP.NET Core

Part 4.4 – Iterator Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Behavioral Design Pattern
Difficulty: ⭐⭐⭐☆☆ Intermediate
Prerequisites: C#, OOP, Interfaces, Collections, Generics, LINQ, IEnumerable<T>, IEnumerator<T>


Table of Contents

  1. Introduction

  2. What is the Iterator Design Pattern?

  3. Why Do We Need the Iterator Pattern?

  4. Iterator and Aggregate Concepts

  5. Iterator Pattern Structure

  6. UML Class Diagram

  7. Components of the Iterator Pattern

  8. IEnumerable<T> and IEnumerator<T>

  9. How foreach Works Internally

  10. Complete C# Console Application

  11. Custom Iterator Implementation

  12. Understanding yield return

  13. Iterator vs yield return

  14. ASP.NET Core Implementation

  15. Database and Repository Example

  16. Lazy Iteration

  17. Streaming Large Datasets

  18. Pagination

  19. Real-World Enterprise Examples

  20. Advantages

  21. Disadvantages

  22. Best Practices

  23. Common Mistakes

  24. Iterator vs IEnumerable<T>

  25. Iterator vs yield return

  26. Iterator vs foreach

  27. Interview Questions

  28. Summary

  29. Coming Up Next


1. Introduction

In almost every C# application, we work with collections:

List<Customer>
List<Order>
List<Product>
Dictionary<int, Employee>

A common requirement is to traverse these objects one by one.

For example:

foreach (var customer in customers)
{
    Console.WriteLine(customer.Name);
}

The interesting question is:

What actually happens internally when we use foreach?

The C# language and .NET collection infrastructure use the concepts of:

  • IEnumerable<T>

  • IEnumerator<T>

  • GetEnumerator()

  • MoveNext()

  • Current

  • yield return

These concepts are closely related to the Iterator Design Pattern.

The Iterator Pattern provides a standardized way to traverse a collection without exposing its internal representation.


2. What is the Iterator Design Pattern?

Definition

The Iterator Design Pattern is a behavioral design pattern that provides a way to sequentially access elements of a collection without exposing the collection's underlying representation.

In simple terms:

Iterator separates the process of traversing a collection from the collection itself.

For example:

Collection
    ↓
Iterator
    ↓
Item 1
    ↓
Item 2
    ↓
Item 3
    ↓
Item 4

The client does not need to know whether the data is stored in:

  • Array

  • List

  • Linked List

  • Tree

  • Database result

  • File

  • Network stream

It only needs to know how to iterate through the data.


3. Why Do We Need the Iterator Pattern?

Consider a collection:

List<Product> products;

We could expose the internal collection directly.

But this creates unnecessary coupling.

The client might become dependent on:

List
Array
Dictionary
LinkedList

Instead, we can expose an abstraction:

IEnumerable<Product>

The client simply says:

foreach (var product in products)
{
    // Process product
}

The client doesn't care how the collection is implemented.

This provides:

  • Encapsulation

  • Flexibility

  • Reusability

  • Separation of responsibilities

  • Consistent traversal


4. Iterator and Aggregate Concepts

Two important terms are:

Aggregate

An Aggregate is the collection or object containing multiple elements.

For example:

CustomerCollection
OrderCollection
ProductCollection
EmployeeCollection

Iterator

The Iterator is responsible for moving through the collection.

For example:

Iterator
   |
   +-- MoveNext()
   |
   +-- Current
   |
   +-- Reset()

Conceptually:

+-------------------+
|     Aggregate     |
+-------------------+
| CreateIterator()  |
+---------+---------+
          |
          ↓
+-------------------+
|     Iterator      |
+-------------------+
| Current           |
| MoveNext()        |
| Reset()           |
+-------------------+

5. Iterator Pattern Structure

The basic structure looks like:

                  +----------------+
                  |     Client     |
                  +-------+--------+
                          |
                          ↓
                  +---------------+
                  |   Iterator    |
                  +---------------+
                  | MoveNext()    |
                  | Current       |
                  +-------+-------+
                          |
                          ↓
                  +---------------+
                  |   Aggregate   |
                  +---------------+
                  | Items         |
                  | CreateIterator|
                  +---------------+

The client uses the iterator instead of directly depending on the internal data structure.


6. UML Class Diagram

A traditional Iterator Pattern UML diagram looks like this:

                 +----------------------+
                 |       Client         |
                 +----------+-----------+
                            |
                            ↓
                 +----------------------+
                 |    <<interface>>     |
                 |      Iterator<T>     |
                 +----------------------+
                 | + Current : T        |
                 | + MoveNext() : bool  |
                 | + Reset() : void     |
                 +----------^-----------+
                            |
                            |
                 +----------------------+
                 | ConcreteIterator<T>  |
                 +----------------------+
                 | - currentIndex       |
                 | - collection         |
                 +----------------------+
                 | + Current            |
                 | + MoveNext()         |
                 | + Reset()            |
                 +----------------------+

                 +----------------------+
                 |    <<interface>>     |
                 |      Aggregate<T>     |
                 +----------------------+
                 | + CreateIterator()   |
                 +----------^-----------+
                            |
                            |
                 +----------------------+
                 | ConcreteAggregate<T>  |
                 +----------------------+
                 | - items              |
                 +----------------------+
                 | + CreateIterator()   |
                 +----------------------+

7. Components of the Iterator Pattern

1. Iterator

Defines how to traverse elements.

Typical operations:

MoveNext()
Current
Reset()

2. Concrete Iterator

Implements the iterator behavior.

It maintains the current position.

For example:

Index = 0
Index = 1
Index = 2

3. Aggregate

Represents the collection.

It provides a mechanism for creating an iterator.


4. Concrete Aggregate

Contains the actual collection.

For example:

List<Product>

5. Client

Uses the iterator to traverse the collection.


8. IEnumerable<T> and IEnumerator<T>

This is one of the most important concepts for a .NET developer.

C# provides:

IEnumerable<T>

and:

IEnumerator<T>

They play different roles.

IEnumerable<T>

IEnumerable<T> represents something that can provide an enumerator.

Conceptually:

public interface IEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();
}

IEnumerator<T>

The enumerator is responsible for actually moving through the collection.

Conceptually:

public interface IEnumerator<out T>
{
    T Current { get; }

    bool MoveNext();

    void Reset();
}

It also implements IDisposable.


9. How foreach Works Internally

Consider:

var numbers = new List<int>
{
    10,
    20,
    30
};

foreach (var number in numbers)
{
    Console.WriteLine(number);
}

It looks simple.

But conceptually, the compiler transforms the loop into something similar to:

var enumerator = numbers.GetEnumerator();

while (enumerator.MoveNext())
{
    var number = enumerator.Current;

    Console.WriteLine(number);
}

And in a more complete conceptual form:

var enumerator = numbers.GetEnumerator();

try
{
    while (enumerator.MoveNext())
    {
        int number = enumerator.Current;

        Console.WriteLine(number);
    }
}
finally
{
    enumerator.Dispose();
}

So:

foreach
  ↓
GetEnumerator()
  ↓
MoveNext()
  ↓
Current
  ↓
MoveNext()
  ↓
Current
  ↓
...

This is the core of iteration in .NET.


10. What Does MoveNext() Do?

Suppose our collection contains:

10
20
30

Initially:

Position = Before First Item

Calling:

MoveNext()

moves to:

10

Then:

Current

returns:

10

Next:

MoveNext()

moves to:

20

Then:

Current

returns:

20

Eventually:

MoveNext()

returns:

false

The iteration ends.


11. Complete C# Console Application

Let's create a custom Iterator Pattern implementation.

Step 1 – Product

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

Step 2 – Iterator Interface

public interface IProductIterator
{
    Product Current { get; }

    bool MoveNext();

    void Reset();
}

Step 3 – Concrete Iterator

public class ProductIterator : IProductIterator
{
    private readonly List<Product> _products;

    private int _currentIndex = -1;

    public ProductIterator(List<Product> products)
    {
        _products = products;
    }

    public Product Current
    {
        get
        {
            if (_currentIndex < 0 ||
                _currentIndex >= _products.Count)
            {
                throw new InvalidOperationException();
            }

            return _products[_currentIndex];
        }
    }

    public bool MoveNext()
    {
        if (_currentIndex + 1 >= _products.Count)
        {
            return false;
        }

        _currentIndex++;

        return true;
    }

    public void Reset()
    {
        _currentIndex = -1;
    }
}

12. Product Collection

Now create our Aggregate.

public class ProductCollection
{
    private readonly List<Product> _products = new();

    public void Add(Product product)
    {
        _products.Add(product);
    }

    public IProductIterator CreateIterator()
    {
        return new ProductIterator(_products);
    }
}

13. Client Code

var products = new ProductCollection();

products.Add(new Product
{
    Id = 1,
    Name = "Laptop",
    Price = 1200
});

products.Add(new Product
{
    Id = 2,
    Name = "Monitor",
    Price = 400
});

products.Add(new Product
{
    Id = 3,
    Name = "Keyboard",
    Price = 100
});

var iterator = products.CreateIterator();

while (iterator.MoveNext())
{
    var product = iterator.Current;

    Console.WriteLine(
        $"{product.Id} - {product.Name} - ${product.Price}");
}

Output:

1 - Laptop - $1200
2 - Monitor - $400
3 - Keyboard - $100

Notice that the client does not access the internal _products list.

It simply uses:

MoveNext()

and:

Current

14. Implementing the Iterator with IEnumerable<T>

Modern C# applications usually don't need to manually implement the traditional pattern.

We can use:

IEnumerable<T>

For example:

public class ProductCollection
{
    private readonly List<Product> _products = new();

    public void Add(Product product)
    {
        _products.Add(product);
    }

    public IEnumerable<Product> GetProducts()
    {
        return _products;
    }
}

Now the client can write:

foreach (var product in collection.GetProducts())
{
    Console.WriteLine(product.Name);
}

This is one reason the Iterator Pattern is so deeply integrated into .NET.


15. Understanding yield return

One of the most useful C# features related to iteration is:

yield return

Example:

public IEnumerable<int> GetNumbers()
{
    yield return 10;
    yield return 20;
    yield return 30;
}

Usage:

foreach (var number in GetNumbers())
{
    Console.WriteLine(number);
}

Output:

10
20
30

The important point is that the method does not need to create and return a complete list.


16. How yield return Works

Consider:

public IEnumerable<int> GetNumbers()
{
    yield return 10;
    yield return 20;
    yield return 30;
}

Conceptually, the compiler generates a state machine that remembers where execution should resume.

The flow is approximately:

GetNumbers()
     ↓
yield return 10
     ↓
Pause
     ↓
MoveNext()
     ↓
yield return 20
     ↓
Pause
     ↓
MoveNext()
     ↓
yield return 30

This makes iterator methods useful for lazy evaluation.


17. Custom Iterator Using yield return

For example:

public IEnumerable<int> GetEvenNumbers(
    int start,
    int end)
{
    for (int i = start; i <= end; i++)
    {
        if (i % 2 == 0)
        {
            yield return i;
        }
    }
}

Usage:

foreach (var number in GetEvenNumbers(1, 10))
{
    Console.WriteLine(number);
}

Output:

2
4
6
8
10

18. Why Lazy Evaluation Matters

Consider:

var numbers = Enumerable.Range(1, 1_000_000);

If we only want the first five values:

var result = numbers.Take(5);

we don't need to process the entire sequence first.

This is one of the major benefits of deferred execution in LINQ.

The general idea is:

Large Data Source
       ↓
Iterator
       ↓
Request one item
       ↓
Process
       ↓
Request next item

Instead of:

Large Data Source
       ↓
Load Everything
       ↓
Store in Memory
       ↓
Process

19. ASP.NET Core Implementation

Let's create an ASP.NET Core Web API that exposes products.

Model

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = string.Empty;

    public decimal Price { get; set; }
}

Repository Interface

public interface IProductRepository
{
    IEnumerable<Product> GetProducts();
}

Repository Implementation

public class ProductRepository : IProductRepository
{
    private readonly List<Product> _products =
        new()
        {
            new Product
            {
                Id = 1,
                Name = "Laptop",
                Price = 1200
            },
            new Product
            {
                Id = 2,
                Name = "Monitor",
                Price = 400
            },
            new Product
            {
                Id = 3,
                Name = "Keyboard",
                Price = 100
            }
        };

    public IEnumerable<Product> GetProducts()
    {
        foreach (var product in _products)
        {
            yield return product;
        }
    }
}

20. Register Dependency Injection

In Program.cs:

builder.Services.AddScoped<IProductRepository,
                           ProductRepository>();

21. Controller

[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
    private readonly IProductRepository _repository;

    public ProductsController(
        IProductRepository repository)
    {
        _repository = repository;
    }

    [HttpGet]
    public IActionResult GetProducts()
    {
        var products = _repository.GetProducts();

        return Ok(products);
    }
}

The architecture becomes:

Angular / Client
      ↓
ProductsController
      ↓
IProductRepository
      ↓
ProductRepository
      ↓
IEnumerable<Product>
      ↓
Iterator

22. Database and Repository Example

In enterprise applications, repositories often retrieve data from databases.

For example:

public IEnumerable<Product> GetProducts()
{
    foreach (var product in _dbContext.Products)
    {
        yield return product;
    }
}

However, there is an important consideration here.

yield return does not automatically make database access efficient.

You need to understand:

  • Database query execution

  • EF Core query translation

  • Materialization

  • Connection lifetime

  • Tracking

  • Pagination

  • Streaming behavior

For Entity Framework Core applications, IQueryable<T> and async APIs such as IAsyncEnumerable<T> can be more appropriate depending on the scenario.


23. IEnumerable<T> vs IQueryable<T>

This is an important interview topic.

IEnumerable<T>

Generally works with data already available to the application process.

Example:

IEnumerable<Product> products =
    productsList.Where(x => x.Price > 500);

The filtering occurs in application memory.


IQueryable<T>

Represents a query that can be translated by a provider.

Example:

IQueryable<Product> products =
    dbContext.Products
        .Where(x => x.Price > 500);

With Entity Framework Core, the provider can translate the expression into SQL.

Conceptually:

IQueryable
    ↓
Expression Tree
    ↓
EF Core Provider
    ↓
SQL
    ↓
Database

This distinction is extremely important when working with large datasets.


24. Lazy Iteration

Lazy iteration means:

Produce each item only when it is requested.

Example:

public IEnumerable<int> GetNumbers()
{
    for (int i = 1; i <= 1000000; i++)
    {
        yield return i;
    }
}

Calling:

var numbers = GetNumbers();

does not necessarily generate all one million values immediately.

Values are produced as the iterator is consumed.

For example:

foreach (var number in numbers.Take(5))
{
    Console.WriteLine(number);
}

Only the required portion of the sequence is consumed.


25. Streaming Large Datasets

Suppose an application needs to process millions of records.

A naive approach might be:

var customers = dbContext.Customers.ToList();

This materializes the entire result set into memory.

For very large datasets, that may be expensive.

A more scalable design can use:

  • Pagination

  • Streaming

  • Async iteration

  • Database-side filtering

  • Projection

  • Batching

For asynchronous streaming, modern C# provides:

IAsyncEnumerable<T>

For example:

public async IAsyncEnumerable<Product> GetProductsAsync()
{
    await foreach (var product in GetProductStreamAsync())
    {
        yield return product;
    }
}

The exact implementation should be designed carefully around the database provider and connection lifetime.


26. IAsyncEnumerable<T>

For asynchronous data sources, .NET provides:

IAsyncEnumerable<T>

and:

IAsyncEnumerator<T>

Usage:

await foreach (var product in products)
{
    Console.WriteLine(product.Name);
}

The conceptual flow becomes:

IAsyncEnumerable<T>
       ↓
GetAsyncEnumerator()
       ↓
MoveNextAsync()
       ↓
Current
       ↓
MoveNextAsync()
       ↓
Current

This is particularly useful when consuming asynchronous streams.


27. Pagination

Pagination is another practical application of iterator-style processing.

Suppose there are:

1,000,000 records

Instead of returning all records:

Page 1 → 1–100
Page 2 → 101–200
Page 3 → 201–300

An API could accept:

GET /api/products?pageNumber=2&pageSize=100

The server processes only the requested page.

A simple implementation:

[HttpGet]
public IActionResult GetProducts(
    int pageNumber = 1,
    int pageSize = 20)
{
    var products = _repository
        .GetProducts()
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize);

    return Ok(products);
}

For very large database tables, keyset/seek pagination can often be more efficient than large Skip() offsets.


28. Real-World Enterprise Examples

1. E-Commerce

Iterate through:

Products
Orders
Cart Items
Customers

2. Banking

Process:

Transactions
Accounts
Statements
Payment Records

3. Healthcare

Iterate through:

Patient Records
Appointments
Medical Claims
Billing Transactions

4. Reporting

Process:

Millions of database records

without unnecessarily loading everything into memory.


5. File Processing

Read files sequentially:

Line 1
Line 2
Line 3
...

rather than loading the entire file into memory.


6. Log Processing

Process application logs one record at a time.


7. Message Processing

Iterate through messages from:

Message Queue
Event Stream
Service Bus

8. API Pagination

Iterate through large datasets in manageable pages.


29. Advantages

1. Encapsulation

The internal structure of the collection is hidden.


2. Separation of Responsibilities

The collection stores data.

The iterator handles traversal.


3. Consistent Traversal

Different collections can expose a common iteration interface.


4. Lazy Evaluation

Iterators can produce data on demand.


5. Memory Efficiency

Large sequences don't always need to be materialized completely.


6. Supports Multiple Traversals

Different iterators can represent different traversal strategies.

For example:

Forward Iterator
Reverse Iterator
Filtered Iterator
Sorted Iterator

7. Excellent .NET Integration

The pattern is deeply integrated into:

IEnumerable<T>
IEnumerator<T>
foreach
LINQ
yield return
IAsyncEnumerable<T>

30. Disadvantages

1. Additional Abstraction

For simple collections, introducing custom iterator classes may be unnecessary.


2. Debugging Can Be More Complex

Lazy execution can make it less obvious when code actually runs.


3. Deferred Execution Can Surprise Developers

For example:

var query = products.Where(p => p.Price > 500);

The filtering may not execute until the query is enumerated.


4. Multiple Enumeration

This can be inefficient:

var query = products.Where(p => p.Price > 500);

var count = query.Count();

var first = query.First();

Depending on the source, the sequence may be evaluated more than once.


5. Resource Lifetime Issues

An iterator that depends on a database connection, file, or stream must not outlive the resource it needs.


31. Best Practices

1. Prefer IEnumerable<T> for Read-Only Enumeration

Expose:

IEnumerable<Product>

instead of:

List<Product>

when callers only need to enumerate.


2. Use yield return for Simple Custom Iterators

Instead of manually implementing:

IEnumerator
MoveNext
Current
Reset

consider:

yield return

when appropriate.


3. Avoid Unnecessary Materialization

Be careful with:

ToList()
ToArray()

because they materialize the sequence.


4. Filter as Early as Possible

Instead of:

var products = db.Products.ToList();

var expensiveProducts =
    products.Where(p => p.Price > 1000);

prefer database-side filtering:

var expensiveProducts =
    db.Products.Where(p => p.Price > 1000);

This allows the provider to perform filtering closer to the data source.


5. Use Pagination for Large Datasets

Don't return millions of records through a normal API response.


6. Consider IAsyncEnumerable<T> for Asynchronous Streams

For asynchronous streaming scenarios:

await foreach

can be more appropriate than synchronous enumeration.


7. Be Careful with Multiple Enumeration

If a sequence is expensive to generate, materialize it once when appropriate:

var products = query.ToList();

Then reuse the materialized result.


32. Common Mistakes

Mistake 1 – Confusing IEnumerable with IEnumerator

Remember:

IEnumerable<T>
     ↓
Provides Enumerator

IEnumerator<T>
     ↓
Performs Traversal

Mistake 2 – Assuming yield return Loads Everything

It generally enables deferred, state-machine-based iteration rather than requiring the complete sequence to be built up front.


Mistake 3 – Calling ToList() Too Early

For example:

var result = db.Products
    .ToList()
    .Where(p => p.Price > 1000);

This can cause all rows to be loaded before filtering.

Better:

var result = db.Products
    .Where(p => p.Price > 1000)
    .ToList();

Mistake 4 – Returning Huge Collections from APIs

Use:

Filtering
Pagination
Projection
Streaming

where appropriate.


Mistake 5 – Ignoring Resource Lifetime

An iterator that lazily reads from a database or stream requires the underlying resource to remain available during enumeration.


33. Iterator vs IEnumerable<T>

These terms are related but aren't exactly the same thing.

Iterator PatternIEnumerable<T>
Design pattern.NET interface
General software conceptFramework abstraction
Defines traversal conceptProvides enumerator
Language/framework independentC#/.NET
Can be manually implementedBuilt into .NET

The Iterator Pattern is the design concept.

IEnumerable<T> is one of the major .NET implementations/abstractions that supports it.


34. Iterator vs yield return

Iterator Patternyield return
Design patternC# language feature
General conceptCompiler-supported implementation technique
Can be manually implementedSimplifies iterator creation
Works beyond C#Specific to C#/.NET

yield return is not itself a design pattern.

It is a convenient language feature for creating iterator methods.


35. Iterator vs foreach

foreach is the syntax used by the client to consume an enumerable sequence.

For example:

foreach (var product in products)
{
    Console.WriteLine(product.Name);
}

Conceptually:

foreach
   ↓
GetEnumerator()
   ↓
MoveNext()
   ↓
Current

So:

Iterator Pattern
      ↓
IEnumerable / IEnumerator
      ↓
foreach

36. Iterator vs Strategy

These are behavioral patterns but solve different problems.

IteratorStrategy
Traverses a collectionSelects an algorithm
Focuses on iterationFocuses on behavior
MoveNext() / CurrentDifferent algorithm implementations
IEnumerable<T>Strategy interface

Example Iterator:

Products
   ↓
Iterator
   ↓
Product 1
Product 2
Product 3

Example Strategy:

Payment
  ├── CreditCardStrategy
  ├── BankTransferStrategy
  └── WalletStrategy

37. Interview Questions

Beginner Questions

1. What is the Iterator Design Pattern?

It provides a standard mechanism for traversing a collection without exposing its internal representation.


2. What type of pattern is Iterator?

It is a Behavioral Design Pattern.


3. What is IEnumerable<T>?

IEnumerable<T> represents a sequence that can provide an enumerator.


4. What is IEnumerator<T>?

It represents the mechanism used to traverse the sequence.


5. What does MoveNext() do?

It advances the enumerator to the next element and returns true if an element exists.


6. What does Current do?

It returns the element at the enumerator's current position.


7. How does foreach work internally?

Conceptually, it obtains an enumerator and repeatedly calls:

MoveNext()

and accesses:

Current

until MoveNext() returns false.


38. Intermediate Interview Questions

8. What is yield return?

It allows a method to produce an iterator sequence incrementally, with the compiler generating the necessary state-machine implementation.


9. What is lazy evaluation?

It means values are produced or computed when requested rather than necessarily being computed all at once.


10. What is deferred execution in LINQ?

The query is generally not executed when it is defined; execution happens when the sequence is enumerated or materialized.


11. Difference between IEnumerable<T> and IQueryable<T>?

IEnumerable<T> generally operates over in-process data.

IQueryable<T> represents a query that can be translated by a query provider, such as EF Core's database provider.


12. Why should we avoid unnecessary ToList()?

Because it materializes the entire sequence, which can increase memory usage and cause unnecessary work.


13. What is IAsyncEnumerable<T>?

It provides asynchronous iteration using:

await foreach

and asynchronous enumeration operations.


14. Is yield return the same as the Iterator Pattern?

No.

yield return is a C# language feature that makes implementing iterator behavior easier.


39. Advanced Interview Questions

15. How can Iterator improve memory efficiency?

It can enable lazy processing where items are generated and consumed one at a time instead of materializing the complete sequence.


16. Can Iterator be used with database queries?

Yes, but developers must understand the difference between:

IEnumerable
IQueryable

and how the underlying database provider executes the query.


17. What happens if an iterator depends on a disposed resource?

Enumeration may fail because the iterator still needs access to the underlying resource.

This is particularly important with:

Database connections
Streams
Files
Network resources

18. Why is Iterator useful for large datasets?

It allows applications to process data incrementally and can reduce memory pressure when the underlying source supports streaming or incremental access.


19. How does LINQ relate to the Iterator Pattern?

LINQ relies heavily on IEnumerable<T> and iterator-based sequence processing.

Many LINQ operators use deferred execution and produce sequences that are consumed through enumeration.


20. Is List<T> an Iterator?

No.

List<T> is a collection.

It provides an enumerator through:

GetEnumerator()

The enumerator performs the traversal.


40. Practical Enterprise Architecture

A typical enterprise application might look like:

                 Angular Application
                         |
                         ↓
                 ASP.NET Core API
                         |
                         ↓
                    Controller
                         |
                         ↓
                    Service Layer
                         |
                         ↓
                  Repository Layer
                         |
                         ↓
                    EF Core
                         |
                         ↓
                     Database

For large result sets:

Database
    ↓
Query
    ↓
IQueryable<T>
    ↓
Filtering / Projection
    ↓
Enumeration
    ↓
IEnumerable<T>
    ↓
API Response / Stream

For asynchronous streaming:

Database / Service
        ↓
IAsyncEnumerable<T>
        ↓
await foreach
        ↓
Process one item
        ↓
Next item

41. Complete Conceptual Flow

The entire Iterator Pattern can be remembered using this diagram:

                 COLLECTION
                     |
                     |
              GetEnumerator()
                     |
                     ↓
                 ENUMERATOR
                     |
             +-------+-------+
             |               |
         MoveNext()       Current
             |               |
             ↓               ↓
          Next Item      Current Item
             |
             ↓
       Continue Until
       MoveNext() = false

And in C#:

foreach (var item in collection)
{
    Process(item);
}

Conceptually:

var enumerator = collection.GetEnumerator();

while (enumerator.MoveNext())
{
    var item = enumerator.Current;

    Process(item);
}

42. Key Takeaways

The most important concepts from this article are:

1. Iterator is a Behavioral Design Pattern

It focuses on how objects are traversed.

2. IEnumerable<T> provides an enumeration abstraction

IEnumerable<T>

allows a client to obtain an enumerator.

3. IEnumerator<T> performs traversal

The main concepts are:

Current
MoveNext()
Reset()

4. foreach uses enumeration

Conceptually:

GetEnumerator()
→ MoveNext()
→ Current
→ MoveNext()
→ Current

5. yield return simplifies iterator implementation

It allows C# developers to write iterator methods without manually implementing all iterator state-management code.

6. Lazy iteration can reduce memory usage

Instead of:

Load Everything
↓
Process

we can often use:

Request Item
↓
Process
↓
Request Next Item

7. Large datasets require careful design

Use appropriate combinations of:

  • Filtering

  • Projection

  • Pagination

  • Streaming

  • IEnumerable<T>

  • IQueryable<T>

  • IAsyncEnumerable<T>


Conclusion

The Iterator Design Pattern is one of the most important behavioral patterns for C# developers because its concepts are deeply embedded into the .NET ecosystem.

When you write:

foreach (var customer in customers)
{
    Console.WriteLine(customer.Name);
}

you are using a mechanism built around enumeration and iteration.

The key concepts are:

IEnumerable<T>
      ↓
GetEnumerator()
      ↓
IEnumerator<T>
      ↓
MoveNext()
      ↓
Current

C# makes iterator-based programming even easier through:

yield return

and modern .NET extends these ideas with:

IAsyncEnumerable<T>

This becomes especially important in enterprise applications where we process:

  • Large database result sets

  • Millions of records

  • Files

  • Logs

  • API results

  • Message streams

  • Paginated data

  • Asynchronous streams

The key lesson is:

The Iterator Pattern allows clients to traverse a collection without needing to know how that collection stores or manages its data.

For modern .NET developers, understanding the Iterator Pattern also means understanding how foreach, IEnumerable<T>, IEnumerator<T>, LINQ, yield return, lazy evaluation, and asynchronous iteration fit together.


🚀 Coming Up Next: Part 4.5 – Mediator Design Pattern

In the next article, we'll explore the Mediator Design Pattern, including:

  • What is the Mediator Pattern?

  • Why do we need it?

  • Mediator vs direct object communication

  • Colleague and Mediator concepts

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Dependency Injection

  • Request/Response communication

  • CQRS and Mediator

  • MediatR-style architecture

  • Command and Query handlers

  • Pipeline behaviors

  • Validation

  • Logging

  • Authorization

  • Transaction handling

  • Banking example

  • E-commerce example

  • Real-world enterprise scenarios

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Mediator vs Observer

  • Mediator vs Facade

  • Mediator vs Command

  • Interview questions

The Mediator Pattern is particularly important for modern .NET developers because it provides a foundation for understanding loosely coupled communication, CQRS, command/query handlers, pipeline behaviors, and clean application-layer architecture.

Don't Copy

Protected by Copyscape Online Plagiarism Checker