Have you ever had the task of creating a list of numbers from start to the end value? Unfortunately, JavaScript doesn't have a built-in function of the kind, say like range() in PHP or Ruby. There are a few ways to resolve this predicament: for loop, recursion, the Array.from() method, the step parameter, character sequence generation, etc.

So, we need to create a range function that takes the start and the end value, and returns a list of numbers in that range. The most obvious way is to use a for loop.

For loop

We take the start and end values as function parameters, create an empty array, then use the for loop from start to end value to add elements to the end of the array.

function range(start, end) {
const ans = [ ];
for (let i = start; i <= end; i++) {
ans.push(i);
}
return ans;
}
const myArray = range(5, 10);
console.log(myArray);

//Output: [ 5, 6, 7, 8, 9, 10 ]

Recursion

Usually, when the start value is equal to the end value, the answer is [start].

function range(start, end) {
if(start === end) return [start];
// recursive case
}

Let's say range(start, end) works in a scenario where start and end are different. How do we solve the problem? Using the spread expression syntax “...” and recursion, i.e. by calling the function ...range(start + 1, end) we fill the array with elements from start to end.

function range(start, end) {
if(start === end) return [start];
return [start, ...range(start + 1, end)];
}
const dates = range(2010, 2020);
console.log(dates);

//Output:
// [
// 2010, 2011, 2012,
// 2013, 2014, 2015,
// 2016, 2017, 2018,
// 2019, 2020
// ]

This is a more elegant solution. But we can go even further if we use the new Array(n) constructor, which creates an array with n elements. At the same time, we will make our code more clear and complete.

Method map()

So we have built an array with the given length. Now if we have a list of n elements we might make a range from it by mapping every element to its index applying map(). But the array still doesn't have element values to iterate over, so the map() function won't work. Let's take the fill() method to fill the array elements with undefined values. Now we can use map().

The map() method makes a new array filled with values that are the outcome of calling the given function once for each array element. It accepts up to 3 parameters: element, index, array.

Now if we specify 2 parameters (“_” placeholder to ignore the initial value of the element, and the index), we can assign a new value to the element. Using an arrow function: arr.map((_, i) => i).

The length of the array is calculated by the formula end-start+1, and the value of the element is i+start. The final result will be as follows:

function range(start, end) {
return (new Array(end - start + 1)).fill(undefined).map((_, i) => i + start);
}
var myArray = range(15, 21);
console.log(myArray);

//Output:
// [
// 15, 16, 17, 18,
// 19, 20, 21
// ]

Method Array.from()

Static Array.from() method creates a new copy of an array-like or iterable object. It takes a possible mapFn parameter, which allows you to execute the map() function for each element of the created array.

An arrow function: arr.from(arrayLike, (element, index) => i). Parameters in parentheses are passed to the map() function. So we create an array by specifying its length in curly braces { } and then apply the map() method.

function range(start, end) {
const mylength = end - start + 1;
return Array.from({ length: mylength }, (_, i) => start + i);
}
const myArr = range(105, 110);
console.log(myArr);

// Output:
// [ 105, 106, 107, 108, 109, 110 ]

Sequence generation

When processing large ranges, which is essentially creating huge arrays, we can optimize memory consumption using generators. The function keyword followed by (function) defines a generator function.

This function differs from the usual one in that you can exit it and enter it back. At the same time, its context, i.e. variable values are saved on subsequent entries. The yield statement returns a value and exits the function, while the yield* statement delegates execution to another function. Here's what our example would look like:

function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}

We can use this generator in for…of that loops through the iterable, in our case the range(start, end) array, to output the result, or use the array spread operator to get all the values (this creates the same array as the non-generator approaches).

for (const i of range(1, 5)) {
console.log(i);
}
/* Output
1 2 3 4 5 /

[...range(1, 5)] // [1, 2, 3, 4, 5]

Most programmers try to avoid for loops, we can also define a generator recursively, also check if the start and end values are not the same. Let's fill the array using the array spread operator.

function* range(start, end) {
yield start;
if (start === end) return;
yield* range(start + 1, end);
}

const myArr = [...range(55, 65)];
console.log(myArr);

//Output:
// [
// 55, 56, 57, 58, 59,
// 60, 61, 62, 63, 64,
// 65
// ]

Let's add the step parameter, i.e. by how much each next generated number should change. We use the Array.from() method with the mapFn parameter, the array length will be calculated by the formula (stop - start) / step + 1, and the element value as start + (i * step) will be written using the arrow function.

const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));

const myArr = range(23, 29, 0.5);

console.log(myArr);

//Output:
// [
// 23, 23.5, 24, 24.5,
// 25, 25.5, 26, 26.5,
// 27, 27.5, 28, 28.5,
// 29
// ]

Now let's try to create a sequence of characters, for example, an alphabet. We will use the already existing range function with the step parameter.

The charCodeAt(index) method returns the UTF-16 character code of the string, which is an integer from 0 to 65535, the index parameter specifies the position of the character. Since the characters A and Z in the Unicode table are in order, we can use their code as the start and end values for the range function, i.e. respectively 65 and 90.

Static String.fromCharCode(num1, ...) method returns a string created from a sequence of UTF-16 codes. The num1 parameter specifies the character code value. Now let's add a map() function so that each code returned by the range function is converted to a character, i.e. letter.

const range = (start, stop, step) => Array.from({ length: (stop - start) / step + 1}, (_, i) => start + (i * step));

var myAlph = range('A'.charCodeAt(0), 'Z'.charCodeAt(0), 1).map(x => String.fromCharCode(x));

console.log(myAlph);

//Output:
// [
// 'A', 'B', 'C', 'D', 'E', 'F',
// 'G', 'H', 'I', 'J', 'K', 'L',
// 'M', 'N', 'O', 'P', 'Q', 'R',
// 'S', 'T', 'U', 'V', 'W', 'X',
// 'Y', 'Z'
// ]