DEV Community

Cover image for Iterator Design Pattern: A Hands-On Guide
Gunjan Modi
Gunjan Modi

Posted on

Iterator Design Pattern: A Hands-On Guide

The Iterator design pattern is a powerful tool in software development, providing a structured way to traverse collections without exposing their internal workings. Whether you're working with lists, trees, or custom data structures, this pattern ensures clean and efficient navigation, promoting code that is both readable and maintainable. This blog explores the journey from a naive solution to a refined implementation of the Iterator pattern.

Background

Imagine you have a collection of shapes (e.g., circles, squares) and need to iterate through them. Initially, you might hardcode the iteration logic within the client code.

class Shape:
    def __init__(self, name: str):
        self.name = name

shapes = [Shape("Circle"), Shape("Square"), Shape("Triangle")]

# Naive iteration logic in the client
for i in range(len(shapes)):
    print(shapes[i].name)
Enter fullscreen mode Exit fullscreen mode

Problems

While this works for small cases, it quickly becomes unmanageable as your application grows.

  • Tight Coupling: The client is tightly bound to the collection's structure.
  • Single Responsibility Principle Violation: The client handles both business logic and iteration.
  • Extensibility Issues: Changing the collection type requires rewriting the iteration logic. E.g. you might want to change list to dictionary. This change will be ripple effect for all the components using the previous data type.

Incremental Refinement

The key to solving the problem is externalizing the iteration logic. Let's refine the naive solution step by step.

Step 1: External Iterator Class

Move the iteration logic into a separate class.

from typing import List

class ShapeIterator:
    def __init__(self, shapes: List[Shape]):
        self.shapes = shapes
        self.index = 0

    def has_next(self) -> bool:
        return self.index < len(self.shapes)

    def next(self) -> Shape:
        shape = self.shapes[self.index]
        self.index += 1
        return shape

shapes = [Shape("Circle"), Shape("Square"), Shape("Triangle")]
iterator = ShapeIterator(shapes)

while iterator.has_next():
    print(iterator.next().name)

Enter fullscreen mode Exit fullscreen mode

What do we have achieved? - The client is no longer concerned with the collection's structure.

Step 2: Implementing an Iterable Interface

Standardize iteration by introducing an interface for collections.

class Iterable:
    def __iter__(self):
        raise NotImplementedError

class ShapeCollection(Iterable):
    def __init__(self):
        self.shapes = []

    def add_shape(self, shape: Shape):
        self.shapes.append(shape)

    def __iter__(self):
        return ShapeIterator(self.shapes)

shapes = ShapeCollection()
shapes.add_shape(Shape("Circle"))
shapes.add_shape(Shape("Square"))
shapes.add_shape(Shape("Triangle"))

for shape in shapes:
    print(shape.name)
Enter fullscreen mode Exit fullscreen mode

Benefits:

  • Single Responsibility Principle: Separation of concerns between client, collection, and iterator.
  • Open/Closed Principle: Adding new collection types doesn't affect existing logic.

Key Takeaways

The Iterator design pattern is more than just a way to traverse collections - it's a tool for writing clean, extensible, and maintainable code. By decoupling iteration logic from collections, we achieve better separation of concerns and adherence to design principles like SRP and OCP. Python's built-in mechanisms, such as iter and next, make it easy to implement this pattern elegantly. Embrace this approach in your projects to simplify traversal while keeping your codebase flexible and future-proof.


I hope you have learned something. If you found it valuable, hit the heart button ❤️ and consider following me for more such content.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay