Question: For a custom container wrapping an iterator, I intended to delegate some of my logic to itertools.islice. One consideration was to avoid unnecessary iterations over the wrapped iterator. When calling itertools.islice(iterable, start, stop, step) with stop<=start, the result is an empty generator, as expected. But even though it's not absolutely needed, itertools.islice(iterable, start, stop, step) will always iterate at least start number of times over the iterable. Test case to repro: from unittest.mock import Mock import itertools iterableMock = Mock() iterableMock.__iter__ = Mock(return_value=iterableMock) iterableMock.__next__ = Mock(side_effect=range(10)) iterable = iterableMock start = 5 stop = 0 step = None isliceResult = list(itertools.islice(iterable, start, stop, step)) assert isliceResult == [] assert iterableMock.__next__.call_count == 0 # <= FAILS since call_count == 5  Is this behavior:  Expected / by design. We want to skip the elements until start is reached no matter what. Just a side effect of the current implementation that only impacts the performance of some corner cases. A potential improvement that can be addressed without concerns.  Documentation can be a bit misleading/misinterpreted on the expected behavior.  elements from the iterable are skipped until start is reached it stops at the specified position  The suggested "equivalent implementation" and the source code clearly iterate until start-1. stop parameter is only considered subsequently.

Answer: Consuming elements from the underlying iterator is part of the islice contract, and cannot be optimized out. There's even a recipe in the recipes section of the official itertools docs that relies on empty islices consuming elements: def consume(iterator, n=None): "Advance the iterator n-steps ahead. If n is None, consume entirely." # Use functions that consume iterators at C speed. if n is None: collections.deque(iterator, maxlen=0) else: next(islice(iterator, n, n), None)  However, the contract arose more by historical accident than actual design. Raymond Hettinger, the person who designed the function and wrote the original docs, did not intend islice to make any promises about the final state of the underlying iterator - not even the promise that the consume recipe relies on. Quoting one of Raymond's posts in a conversation about a similar edge case with step:  Currently, there are no promises or guarantees about the final state of the iterator.  And their next post in that conversation:  I wrote the tools, the docs, and the tests. If you interpret a "promise" in text, I can assure you it was not intended. The behavior is undefined because I never defined it. I'm happy to clarify the docs to make that explicit.  As a result of that conversation, the implementation was adjusted to change the behavior in that edge case, and element consumption details were considered defined from then on. Years later, someone else brought up the start>stop case. Raymond's response includes the following line:  Having islice() always consume at least "start" number of values seems reasonable enough and it is the documented behavior: "If start is non-zero, then elements from the iterable are skipped until start is reached. ... If stop is None, then iteration continues until the iterator is exhausted, if at all; otherwise, it stops at the specified position."  indicating that they considered consuming start items in the start>stop case to be part of the documented contract.