Array:
• Array.prototype.slice may be expensive (it creates a collection, but it may be able to be done in such a way that you can’t tell).
• Array.prototype.filter is expensive (it creates a collection).
• Array.prototype.map is expensive (it creates a collection).
So you have two or three expensive operations, going through as much as the entire list (depends on how much you trim out with slice and filter) two or three times, creating an intermediate list at each step.
Iterator:
• Array.prototype.values is cheap, creating a lightweight iterator object.
• Iterator.prototype.drop is cheap, creating a lightweight iterator helper object.
• Iterator.prototype.take is cheap, creating a lightweight iterator helper object.
• Iterator.prototype.filter is cheap, creating a lightweight iterator helper object.
• Iterator.prototype.map is cheap, creating a lightweight iterator helper object.
• Iterator.prototype.toArray is the thing that actually drives everything. Now you drive the iterator chain through, going through the list only once, applying each filter or transformation as you go, and only doing one expensive allocation of a new array.
In the end, in terms of time complexity, both are O(n), but the array version has a much higher coefficient on that n. For small inputs, array may be faster. For large values, iterators will be faster.
> arr.slice(10, 20).filter(el => el < 10).map(el => el + 5)
(2s+d)As you point out, many engines optimise shallow with copy on write (zero cost).. so just 1 allocation at map.
> arr.values().drop(10).take(10).filter(el => el < 10).map(el => el + 5).toArray()
(3s+3d) .. significantly worse performanceNote the shallow copy: