Let me give you a little guidance on this (if you happen to still be using D). Most functions in std.algorithm (and many more throughout the rest of the standard library) return "ranges", which is like a begin iterator and an end iterator zipped up into one data structure. There is a "range hierarchy" that mirrors the C++ iterator hierarchy:

Input/Output Range > Forward Range > Bidirectional Range > Random Access Range

Input ranges provide the following API:

bool empty()
T front()
void popFront()

Forward ranges provide the same API, and also add a "save" function that creates a copy of the range's current state.

Bidirectional ranges add "back" and "popBack" functions, which don't really need any explanation.

Random access ranges additionally add a "length" function and constant-time array-like indexing.

In addition to most functions in std.algorithm _returning_ ranges, most of them also _accept_ ranges. Therefore, you can freely pass the result of any function in std.algorithm into another function that takes a range, and it should all just work. It's pretty much the same deal as in Rust/Java/Swift/etc. with their various iterator protocols, although the API for ranges in D is more influenced by C++.

For example, the first Project Euler problem, using a range-based approach:

import std.algorithm;
import std.range;
import std.stdio;

void main()
{
//List all the natural numbers below 1000 that are multiples of 3 or 5
auto natsBelow1000 = iota(1000); //iota returns a range that lazily computes the numbers from 0-999
auto multiplesOf3Or5 = natsBelow1000.filter!(n => n % 3 == 0 || n % 5 == 0); //filter accepts a range as an argument, which it lazily filters according to the given predicate
auto sum = multiplesOf3Or5.sum(); //Sum accepts a range of "summable" items and computes the... sum
writeln(sum); //Prints 233168
}

A D programmer wouldn't generally write things this way, preferring to make a more idiomatic "range chain" like you'd see with Java's streams or Rust's iterators, but you get the idea.

As you might have guessed, arrays are also ranges - random access ranges - which is why they're so easy to use.

I would highly recommend against putting `.array` everywhere in your code, as it will force the range you pass to it to be eagerly evaluated and allocate more memory from the GC. It's much better to take some initial data store, like an array, do all your filtering and mapping, etc. on it, and when you're done, _then_ turn it back into an array with a call to `.array`. That way you'll get all the lazy iteration and low memory footprint of ranges, and then at the end you have a nicely allocated array of data with the result that is easy to work with.

Honestly, though, in most of my code I usually just pass around ranges as they're very good for creating interlocking adapters over different backing stores. One really great article on advanced usage of ranges is H. S. Teoh's article on Component Programming with Ranges: https://wiki.dlang.org/Component_programming_with_ranges

A couple great resources on ranges in D:

http://www.informit.com/articles/printerfriendly/1407357

https://tour.dlang.org/tour/en/basics/ranges

http://ddili.org/ders/d.en/ranges.html

http://dconf.org/2015/talks/davis.html