Question: I have created an infinite iterator which maps natural numbers to all lattice points in a spiral like manner, akin to the Ulam Spiral:  The code is below, I have made it as fast as possible, and I didn't use a single if condition: from itertools import islice, repeat def ulamish_spiral_gen(): xc = yc = length = 0 yield 0, 0 while True: length += 1 yield from zip(range(xc + 1, (xc := xc + length) + 1, 1), repeat(yc)) yield from zip(repeat(xc), range(yc + 1, (yc := yc + length) + 1, 1)) length += 1 yield from zip(range(xc - 1, (xc := xc - length) - 1, -1), repeat(yc)) yield from zip(repeat(xc), range(yc - 1, (yc := yc - length) - 1, -1)) def ulamish_spiral(n): return list(islice(ulamish_spiral_gen(), n))  I want to know, how to memoize the output of the infinite iterator, so that list(islice(ulamish_spiral_gen(), n)) will be called only when the value of n is greater than the last n. Something like this: COMPUTED = [] def ulamish_spiral(n): global COMPUTED if n > len(COMPUTED): COMPUTED = list(islice(ulamish_spiral_gen(), n)) return COMPUTED[:n]  It is very easy, but the first len(COMPUTED) terms have already been computed, only terms in range(len(COMPUTED), n) need to be computed, but the call computes all the already computed terms. So I tried to reuse the same generator object and only ask for the next n - len(COMPUTED) items, and I have succeeded. But doing so actually makes code slower: COMPUTED = [] ULAMISH_GEN = ulamish_spiral_gen() def ulamish_spiral(n): if n > (l := len(COMPUTED)): COMPUTED.extend(islice(ULAMISH_GEN, n - l)) return COMPUTED[:n]  In [225]: %timeit COMPUTED.clear(); ULAMISH_GEN = ulamish_spiral_gen(); ulamish_spiral(8192) 928 µs ± 8.96 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) In [226]: %timeit COMPUTED.clear(); ULAMISH_GEN = ulamish_spiral_gen(); ulamish_spiral(1024); ulamish_spiral(2048); ulamish_spiral(4096); ulamish_spiral(8192) 993 µs ± 18.2 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) In [227]: %timeit COMPUTED.clear(); ULAMISH_GEN = ulamish_spiral_gen(); ulamish_spiral(1024); ulamish_spiral(2048); ulamish_spiral(4096); ulamish_spiral(8192); ulamish_spiral(16384) 2.14 ms ± 106 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [228]: %timeit COMPUTED.clear(); ULAMISH_GEN = ulamish_spiral_gen(); ulamish_spiral(16384) 2 ms ± 88.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [229]: COMPUTED.clear(); ULAMISH_GEN = ulamish_spiral_gen(); ulamish_spiral(1024); ulamish_spiral(2048); ulamish_spiral(16384) == list(islice(ulamish_spiral_gen(), 16384)) Out[229]: True  How can I skip already computed terms and make code faster?

Answer: You already covered "memoize"/"skip". Here are faster infinite iterators. Times for the first 16384 coordinates (as in your benchmark):  1.01 ± 0.00 ms gen_Kelly_4 1.01 ± 0.00 ms gen_Kelly_5 1.03 ± 0.00 ms gen_Kelly_3 1.06 ± 0.00 ms gen_Kelly_2 1.21 ± 0.00 ms gen_Kelly_1 1.57 ± 0.00 ms gen_original Python: 3.11.4 (main, Jun 24 2023, 10:18:04) [GCC 13.1.1 20230429]  You combine all the zip iterators with your own generator. My gen_Kelly_1 instead uses chain.from_iterable for that. You use the range function to produce the int objects over and over again. My gen_Kelly_2 instead keeps them in a list and reuses them. My gen_Kelly_3 further reuses the repeat iterators, gen_Kelly_4 reverses my list instead of using reverse iterators, and gen_Kelly_5 removes the now duplicated code (i goes through 0, 1, -1, 2, -2, 3, -3, etc): def gen_Kelly_5(): def parts(): i = 0 range = [] while True: rep = repeat(i) yield zip(rep, range) range.append(i) range.reverse() yield zip(range, rep) i = (i<1) - i return chain.from_iterable(parts())  Full code (Attempt This Online!): from timeit import timeit from statistics import mean, stdev from itertools import islice, repeat, cycle, chain, count import sys def gen_original(): xc = yc = length = 0 yield 0, 0 while True: length += 1 yield from zip(range(xc + 1, (xc := xc + length) + 1, 1), repeat(yc)) yield from zip(repeat(xc), range(yc + 1, (yc := yc + length) + 1, 1)) length += 1 yield from zip(range(xc - 1, (xc := xc - length) - 1, -1), repeat(yc)) yield from zip(repeat(xc), range(yc - 1, (yc := yc - length) - 1, -1)) def gen_Kelly_1(): def parts(): xc = yc = length = 0 yield (0, 0), while True: length += 1 yield zip(range(xc + 1, (xc := xc + length) + 1, 1), repeat(yc)) yield zip(repeat(xc), range(yc + 1, (yc := yc + length) + 1, 1)) length += 1 yield zip(range(xc - 1, (xc := xc - length) - 1, -1), repeat(yc)) yield zip(repeat(xc), range(yc - 1, (yc := yc - length) - 1, -1)) return chain.from_iterable(parts()) def gen_Kelly_2(): def parts(): i = 0 range = [] while True: yield zip(repeat(-i), reversed(range)) range.insert(0, -i) yield zip(range, repeat(-i)) i += 1 yield zip(repeat(i), range) range.append(i) yield zip(reversed(range), repeat(i)) return chain.from_iterable(parts()) def gen_Kelly_3(): def parts(): i = 0 range = [] while True: rep = repeat(-i) yield zip(rep, reversed(range)) range.insert(0, -i) yield zip(range, rep) i += 1 rep = repeat(i) yield zip(rep, range) range.append(i) yield zip(reversed(range), rep) return chain.from_iterable(parts()) def gen_Kelly_4(): def parts(): i = 0 range = [] while True: rep = repeat(-i) yield zip(rep, range) range.append(-i) range.reverse() yield zip(range, rep) i += 1 rep = repeat(i) yield zip(rep, range) range.append(i) range.reverse() yield zip(range, rep) return chain.from_iterable(parts()) def gen_Kelly_5(): def parts(): i = 0 range = [] while True: rep = repeat(i) yield zip(rep, range) range.append(i) range.reverse() yield zip(range, rep) i = (i<1) - i return chain.from_iterable(parts()) funcs = gen_original, gen_Kelly_1, gen_Kelly_2, gen_Kelly_3, gen_Kelly_4, gen_Kelly_5 n = 16384 # Correctness expect = list(islice(funcs[0](), n)) for f in funcs[1:]: result = list(islice(f(), n)) assert result == expect # Speed times = {f: [] for f in funcs} def stats(f): ts = [t * 1e3 for t in sorted(times[f])[:10]] return f'{mean(ts):6.2f} ± {stdev(ts):4.2f} ms ' for _ in range(1000): for f in funcs: t = timeit(lambda: list(islice(f(), n)), number=1) times[f].append(t) for f in sorted(funcs, key=stats): print(stats(f), f.__name__) print('\nPython:', sys.version)