Iterate over a short list, pairing the elements with the sequential elements of a longer list
Creating a "manual" iterator using iter and then calling next can be tricky, as you found out.
Once an iterator is exhausted, it raises a StopIteration to signal its end.
If you do not catch that in a try/except block, your program will exit ungracefully.

Luckily, message, being a list, is perfectly iterable without being explicit about it.
Simply using it in a for loop will do the iter/next magic for you, and the loop will exit once StopIteration is reached, without error.

zip is a built-in so you can iterate over multiple iterables at once.
zip will iterate until the shortest of the passed iterables is exhausted.
This can be tricky if you expect the iterables to be of the same length.
There is currently talk about a strict flag in the Python ideas mailing list, which would throw errors if strict=True and the iterables are of different lengths.

Assuming you meant to include from itertools import cycle, alongside cycle, of course message is exhausted first.
cycle being infinite, this suprises no one in this case.
The for loop then simply ends after the last message element.

This hopefully does what you want to do:

from itertools import cycle

keys = [3, 1, 20]
message = ["a", "b", "c", "d", "e", "f"]

print(keys, message, sep="\n", end="\n" * 2)

for key, letter in zip(cycle(keys), message):
    print(f"{key}: {letter}")

print("Okay, it worked!")




This is more like your version, needlessly verbose.
Never do this in reality!
However, it can help you understand what is going on in your case:

from itertools import cycle

keys = [3, 1, 20]
message = ["a", "b", "c", "d", "e", "f"]

print(keys, message, sep="\n", end="\n" * 2)

message_iter = iter(message)

for key in cycle(keys):
    try:
        print(f"{key}: {next(message_iter)}")
    except StopIteration:
        break  # Break out of infinite loop

print("Okay, it worked!")