The Chores Rota (#3 in The `itertools` Series • `cycle()` and Combining Tools)
The third episode in Yteria's saga. The third article in the `itertools` series. Discover `itertools.cycle()` and how to combine iteration tools using "iterator algebra"
"It's your turn to take the bins out."
"No way, I washed up the dishes today, and vacuumed the rugs yesterday."
"But…"
And on and on it went. Yteria and her flatmate, Silvia, had these arguments every day. Yteria was hoping she'd be able to move soon—move to a new area and to a flat she didn't have to share with Silvia…or anyone else.
"Right. Let me set up a rota and then we'll follow it strictly", and Yteria got straight to work.
It had been several weeks since Yteria had lost the word "for". For this world is a bit different to yours and mine. People can lose words through some mishap or nefarious means. And if you lose a word, you can't speak it, you can't write it, you can't type it. You still know it's there somewhere, that it exists in the language, but you can't use it.
You can follow Yteria's origin story, how she lost the word "for" and the challenges she faced when programming here: The ‘itertools’ Series.
It’s unlikely you care, but I’ll tell you anyway. I launched a new publication last week. But it’s completely unrelated to Python and it’s unlikely there will be much overlap between the two audiences. Still, if you want to follow my ‘back to the future’ journey, here’s the first post that introduces the publication: Back on the Track • The 25-Year Gap • #1
Creating Infinite Iterables from Finite Ones
Yteria set up two lists, one with the chores and another with Silvia's name and her own:

Next, she wanted to write code to convert these lists into infinite sequences by repeating the contents of the lists forever:
But then she stopped.
Yteria had been programming without the ability to use the word "for" for several weeks by now. And she had discovered the itertools
module in Python's standard library. This module came to her rescue on several occasions.
And there it was: itertools.cycle()
. It was the perfect tool for what she needed:
The function itertools.cycle()
accepts any iterable and returns an iterator that will keep yielding items from the original iterable, restarting from the beginning each time it reaches the end.
If you want to brush up on the difference between iterable and iterator, you can read the following articles:
Iterable: Python's Stepping Stones • (Data Structure Categories #1)
A One-Way Stream of Data • Iterators in Python (Data Structure Categories #6)
But before we move on, let's still write the create_infinite_sequence()
function Yteria was about to write. A version of this function could be as follows:
This function includes a yield
rather than a return
. Therefore, this is a generator function. Calling this function creates a generator. You can read more about generators in this article: Pay As You Go • Generate Data Using Generators (Data Structure Categories #7)
A generator created from this generator function starts with index
equal to 0
and, therefore, starts by yielding the first element in the sequence. Next time, it yields the second, and so on. However, the final line in the function definition uses a conditional expression to reset the index to zero whenever it reaches the end of the sequence.
So, for a list with three elements, such as tasks
, here are the first few steps:
The generator starts with
index
equal to0
, yields the first element, then incrementsindex
to1
. The increment happens in the conditional expression. Note how the third operand in the conditional expression—the one after theelse
—isindex + 1
.Since
index
is now1
, the generator yields the second element and incrementsindex
to2
.When the generator yields
sequence[2]
, the conditional expression resetsindex
to0
sinceindex
, which is2
, is equal tolen(sequence) - 1
.The generator then yields the first element of the sequence and the whole process repeats itself.
Let's confirm that this gives the same output as itertools.cycle()
:
So, does it matter which option you choose?
Yes, it does.
First of all, once you know about itertools.cycle()
, it's much easier and quicker to use it than to write your own function. It also makes your code more readable for anyone who's aware of itertools.cycle()
—and even if they're not, the function name gives a good clue to what it does.
A second advantage of using itertools.cycle()
is that it works with any iterable. The create_infinite_sequence()
generator function only works with sequences. A sequence is an ordered collection in which you can use integers as indices to fetch data based on the order of the elements in the sequence. You can read more about sequences here: Sequences in Python (Data Structure Categories #2)
In Python, all sequences are iterable, but not all iterables are sequences. For example, dictionaries are iterable but they're not sequences. Therefore, itertools.cycle()
can be used on a larger group of data types than create_infinite_sequence()
.
And finally, there's another really good reason to use itertools.cycle()
instead of a homemade function:
You create two iterators. The first one, infinite_tasks
, is the generator you get from the generator function create_infinite_sequence()
. Note that all generators are iterators.
The second iterator is infinite_tasks_cyc
, which is the iterator that itertools.cycle()
returns. All the tools in itertools
return iterators.
Finally, you time how long it takes to get the first 10 million elements from each of these infinite iterators. Here's the output I got on my computer—your timings may vary:
Using 'create_infinite_sequence()':
0.753838583000288
Using 'itertools.cycle()':
0.19026683299944125
It's much quicker to use itertools.cycle()
. Sure, you may have ideas on writing a more efficient algorithm than the one I used in create_infinite_sequence()
. Go ahead, I'm sure you'll be able to do better than create_infinite_sequence()
. But can you do better than itertools.cycle()
?
Do you want to join a forum to discuss Python further with other Pythonistas? Upgrade to a paid subscription here on The Python Coding Stack to get exclusive access to The Python Coding Place's members' forum. More Python. More discussions. More fun.
And you'll also be supporting this publication. I put plenty of time and effort into crafting each article. Your support will help me keep this content coming regularly and, importantly, will help keep it free for everyone.
Creating the Rota • Combining Tools Using 'Iterator Algebra'
So, Yteria used itertools.cycle()
to create two infinite iterators: one for tasks
and the other for people
. Note that the original lists, tasks
and people
, don't have the same number of elements.
Next, Yteria needed to find a way to connect these two infinite iterators so that corresponding elements are matched. She needed a way to progress through the two infinite iterators at the same time. She needed something to "glue" them together…
…or better still, to "zip" them together.
This is where zip()
comes in. The zip()
built-in tool takes a number of iterators and zips them together, grouping the first elements of each iterator together, then grouping the second elements of each iterator together, and so on:
And there it is. Remember that rota
is an iterator since zip()
returns an iterator. So, each time you fetch the next value from the rota
iterator, you'll get a pairing between a person and the chore they need to do.
Yteria finished this off with some quick code to display each day's rota. It would have been easier to use a for
loop, but she couldn't. So she opted for this option, which is less tidy but still works:
You can write the easier for
loop version if you prefer. Note how Yteria, who's now proficient with the itertools
module, also used itertools.count()
to create a counter! She could have just created an integer and increment it each time, of course.
Side note: The while
loop above feels like something that could be implemented with the help of some itertools
tools. Yteria felt this way, too. She wrote a note to try to refactor this while
loop later, even if just as an exercise in playing with more of the tools in itertools
. Do you want to have a go, too? If Yteria gets round to replacing this code, I'll let you know in a future post in The 'itertools' Series.
Here's the output from this code for the first few days:
Press enter for the next day's rota...
Day 1:
It's Yteria's turn to take the bins out
It's Silvia's turn to clean floor and carpets
It's Yteria's turn to wash up
Press enter for the next day's rota...
Day 2:
It's Silvia's turn to take the bins out
It's Yteria's turn to clean floor and carpets
It's Silvia's turn to wash up
Press enter for the next day's rota...
Day 3:
It's Yteria's turn to take the bins out
It's Silvia's turn to clean floor and carpets
It's Yteria's turn to wash up
Press enter for the next day's rota...
Day 4:
It's Silvia's turn to take the bins out
It's Yteria's turn to clean floor and carpets
It's Silvia's turn to wash up
Press enter for the next day's rota...
And of course, this code works with any number of tasks and any number of people.
The itertools
documentation page has a great line about combining various iteration tools using 'iterator algebra'. Yteria's solution is an example of this. It combines two iteration tools, zip()
and cycle()
, to provide a neat solution. The tools in itertools
are often useful as standalone tools. But they're even more powerful when you combine them with each other.
Note that zip()
and enumerate()
aren't part of itertools
since they're both built-in callables. However, they fall in the same category as the other tools in itertools
—they're tools to help in particular iteration tasks.
Final Words
Problem solved. Yteria and Silvia could now share the daily chores and make sure that everyone contributes equally. Yteria felt that her forced abstention from using the for
keyword in Python led her to understand Pythonic iteration a lot better. She felt like an iteration pro now! Iterators are at the heart of iteration in Python. And itertools provides lots of useful iterators.
Code in this article uses Python 3.13
The code images used in this article are created using Snappify. [Affiliate link]
You can also support this publication by making a one-off contribution of any amount you wish.
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
Further reading related to this article’s topic:
Iterable: Python's Stepping Stones • (Data Structure Categories #1)
A One-Way Stream of Data • Iterators in Python (Data Structure Categories #6)
Pay As You Go • Generate Data Using Generators (Data Structure Categories #7)
If You Find if..else in List Comprehensions Confusing, Read This, Else…
Appendix: Code Blocks
Code Block #1
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"]
people = ["Yteria", "Silvia"]
Code Block #2
def create_infinite_sequence(sequence):
...
Code Block #3
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"]
import itertools
tasks_cyc = itertools.cycle(tasks)
next(tasks_cyc)
# 'Take the bins out'
next(tasks_cyc)
# 'Clean floor and carpets'
next(tasks_cyc)
# 'Wash up'
next(tasks_cyc)
# 'Take the bins out'
next(tasks_cyc)
# 'Clean floor and carpets'
next(tasks_cyc)
# 'Wash up'
Code Block #4
def create_infinite_sequence(sequence):
index = 0
while True:
yield sequence[index]
index = 0 if index == len(sequence) - 1 else index + 1
Code Block #5
tasks_inf_seq = create_infinite_sequence(tasks)
next(tasks_inf_seq)
# 'Take the bins out'
next(tasks_inf_seq)
# 'Clean floor and carpets'
next(tasks_inf_seq)
# 'Wash up'
next(tasks_inf_seq)
# 'Take the bins out'
next(tasks_inf_seq)
# 'Clean floor and carpets'
next(tasks_inf_seq)
# 'Wash up'
Code Block #6
import itertools
import timeit
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"]
people = ["Yteria", "Silvia"]
def create_infinite_sequence(sequence):
index = 0
while True:
yield sequence[index]
index = 0 if index == len(sequence) - 1 else index + 1
infinite_tasks = create_infinite_sequence(tasks)
infinite_tasks_cyc = itertools.cycle(tasks)
print(
"Using 'create_infinite_sequence()':\n",
timeit.timeit(
"next(infinite_tasks)",
number=10_000_000,
globals=globals(),
)
)
print(
"Using 'itertools.cycle()':\n",
timeit.timeit(
"next(infinite_tasks_cyc)",
number=10_000_000,
globals=globals(),
)
)
Code Block #7
import itertools
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"]
people = ["Yteria", "Silvia"]
rota = zip(
itertools.cycle(people),
itertools.cycle(tasks),
)
Code Block #8
import itertools
tasks = ["Take the bins out", "Clean floor and carpets", "Wash up"]
people = ["Yteria", "Silvia"]
rota = zip(
itertools.cycle(people),
itertools.cycle(tasks),
)
day_counter = itertools.count(start=1)
while True:
input("\nPress enter for the next day's rota...")
day = next(day_counter)
print(f"Day {day}:")
# The next bit would be easier using a 'for' loop,
# but Yteria couldn't do this!
while True:
person, task = next(rota)
print(f"It's {person}'s turn to {task.lower()}")
if task == tasks[-1]:
break
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com