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.

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay