The other day, I was writing some code, and it just wasn't working for me. I was using forEach()
to iterate over an array of objects and then make an API call for one. Some of you probably recognize my mistake - you can't use async
functions inside a forEach()
! A friend of mine pointed out the problem, and I reverted back to a good old for
loop, but it still bugged me that it wasted my time.
I've known for a long time that forEach()
is less performant than for
, and that there are other issues (see below), but I prefer to make my code as "readable" as possible, and in this case the forEach()
was a much clearer way to explain what I was doing. Since this is something that I'm sure other people have run into, I decided to write a quick post about it... and then I started searching. Turns out, there are lots of other issues.
So, I collected a list of the biggest limitations of the method, and broke it down in this article with examples and code samples.
- Performance Issues:
forEach()
is 95% slower than a traditionalfor
loop. - Inability to Escape the Loop: You can't use
break
orcontinue
withinforEach()
. - Incompatibility with Async Functions:
forEach()
doesn't work well withasync/await
. - Better Options for Arrays:
map()
,reduce()
, andfilter()
are often more appropriate. - Lack of Return Value:
forEach()
always returnsundefined
. - Handling Sparse Arrays:
forEach()
skips over empty slots in sparse arrays. - Practical Examples and Case Studies: Real-world scenarios where
forEach()
falls short.
Hopefully, this will give you a more complete understanding of the limitations of forEach()
and be better equipped to choose the right approach for your specific use cases.
1. Performance Issues
Comparison with for
Loop
One of the biggest limitations of the forEach()
function is its performance compared to traditional for
loops. This becomes particularly evident in large-scale applications where even minor inefficiencies can lead to noticeable slowdowns.
Benchmarking Performance
To understand the performance difference, let's compare forEach()
with a traditional for
loop through a series of benchmarks. You can cut/paste this code into your console to see the results directly.
Code Sample: forEach()
Performance Test
let largeArray = Array.from({ length: 1e6 }, (_, i) => i);
console.time('forEach');
largeArray.forEach((num) => {
// Perform a simple operation
let result = num * 2;
});
console.timeEnd('forEach');
// Output:
// forEach: 9.346923828125 ms
Code Sample: for
Loop Performance Test
let largeArray = Array.from({ length: 1e6 }, (_, i) => i);
console.time('for');
for (let i = 0; i < largeArray.length; i++) {
// Perform a simple operation
let result = largeArray[i] * 2;
}
console.timeEnd('for');
// Output:
// for: 2.3388671875 ms
When you run these benchmarks, you will notice a substantial difference in execution time. In this case, we can see that for
is four times faster. Typically, the for
loop outperforms forEach()
by a significant margin. There are several reasons for this:
- Function Overhead:
forEach()
invokes a callback function for each element, which introduces additional function call overhead. - Iterator Protocol: The underlying iterator protocol used by
forEach()
can add to the execution time. - Optimization: Traditional
for
loops are more straightforward for JavaScript engines to optimize compared to higher-order functions likeforEach()
.
Real-World Scenarios
This performance difference can have a substantial impact in real-world applications, especially those dealing with large datasets or performance-critical operations. For instance, in data processing applications where arrays with millions of elements are common, using forEach()
could lead to noticeable slowdowns.
Example: Data Processing
let processData = (data) => {
console.time('forEach');
data.forEach((item) => {
// Simulate a complex operation
item.processed = item.value * 2;
});
console.timeEnd('forEach');
console.time('for');
for (let i = 0; i < data.length; i++) {
// Simulate a complex operation
data[i].processed = data[i].value * 2;
}
console.timeEnd('for');
};
let largeDataset = Array.from({ length: 1e6 }, (_, i) => ({ value: i }));
processData(largeDataset);
// Output:
// forEach: 167.56103515625 ms
// for: 6.554931640625 ms
In this example, processing the data with a traditional for
loop is considerably faster than using forEach()
- twenty-five times faster! In the above example, being 4 times faster is good, but 25 times faster means no contest.
Implications for Large-Scale Applications
For large-scale applications or anytime performance is a critical factor, you should carefully consider the choice of iteration methods. While forEach()
offers a cleaner and more readable syntax (which I do like!), the performance cost might be too high, especially with extensive data processing requirements. This is reason enough to probably use a for
loop as your default, even if the code isn't quite as clean.
Functional Limitations
While convenient for iterating over arrays, forEach()
has a few key limitations that make it a deal breaker in some cases. We will look at two major functional constraints here: the inability to escape the loop and the incompatibility with asynchronous functions.
2. Inability to Escape the Loop
One of the key limitations of the forEach()
function is that it does not support the use of control flow statements like break
or continue
. This means that once forEach()
starts iterating over an array, it will execute the provided callback function for each element without interruption. This can be a problem when you need to exit the loop early or skip certain loops based on specific conditions.
In a traditional for
loop, you can use break
to exit the loop early or continue
to skip to the next iteration. However, these control flow statements are not available in forEach()
.
Example: Exiting a Loop Early with for
const numbers = [1, 2, 3, 4, 5];
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] === 3) {
break; // Exit the loop early
}
console.log(numbers[i]);
}
// Output: 1, 2
Example: Attempting to Exit Early with forEach()
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((num) => {
if (num === 3) {
return; // This will only exit the current callback, not the entire loop
}
console.log(num);
});
// Output: 1, 2, 4, 5 (3 is skipped, but the loop continues)
As seen in the above examples, the inability to use break
or continue
in forEach()
will result in running all iterations and potentially performing redundant calls. This is another potential performance hit on top of the base issues we outlined above!.
Alternatives for Loop Control
Alternative loop constructs should be considered when you need to control the flow of iterations. The for
or for...of
loops give you all the control you need and are more suitable for situations requiring early exit or iteration skipping.
Example: Using for...of
with break
const numbers = [1, 2, 3, 4, 5];
for (const num of numbers) {
if (num === 3) {
break; // Exit the loop early
}
console.log(num);
}
// Output: 1, 2
By using for...of
, you get total control over the loop flow, which is not possible with forEach()
.
3. Incompatibility with Async Functions
Another significant limitation of forEach()
is its incompatibility with asynchronous functions. This is what prompted me to write an article in the first place!
The forEach()
method does not await the execution of promises within its callback, which pretty much makes async calls unusable. This can result in the loop completing before all asynchronous operations are finished. You might get lucky in some cases, but you will eventually see a problem. Even worse, the problem would be inconsistent, so a nightmare to debug.
Example: Async Operations with forEach()
let fetchData = async (id) => {
// Simulate an asynchronous API call
return new Promise((resolve) => setTimeout(() => resolve(`Data for ${id}`), 1000));
};
let ids = [1, 2, 3, 4, 5];
ids.forEach(async (id) => {
let data = await fetchData(id);
console.log(data);
});
// Output: Likely an empty array or undefined
In the example above, the forEach()
method does not wait for the fetchData
promise to resolve, leading to a situation where the loop completes before any data is logged.
Workarounds and Alternatives
To handle asynchronous operations within a loop, alternative constructs like for...of
and map()
combined with Promise.all()
can be used.
Example: Using for...of
with async/await
async function fetchData(id) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(`Data for ${id}`);
}, 1000);
});
}
let ids = [1, 2, 3, 4, 5];
async function processIds() {
for (id of ids) {
let data = await fetchData(id);
console.log(data);
}
}
processIds();
// Output: Data for 1, Data for 2, ..., Data for 5 (each logged after 1 second delay)
Example: Using map()
with Promise.all()
async function fetchData(id) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(`Data for ${id}`);
}, 1000);
});
}
let ids = [1, 2, 3, 4, 5];
async function processIds() {
let promises = ids.map(fetchData);
let results = await Promise.all(promises);
results.forEach(logData);
}
function logData(data) {
console.log(data);
}
processIds();
// Output: Data for 1, Data for 2, ..., Data for 5 (all logged at once after 1 second delay)
By using for...of
or map()
with Promise.all()
, you can handle asynchronous operations more effectively, ensuring that all promises are resolved before proceeding.
4. Better Options for Arrays
While the forEach()
function is a straightforward way to iterate over arrays, it is not always the best tool for array manipulation. In many cases, other array methods, such as map()
, reduce()
, and filter()
provide more efficient and readable solutions for transforming and working with arrays.
When to Use map()
, reduce()
, and filter()
map()
The map()
method is used for creating a new array by applying a function to each element of the original array. It is handy for transforming data without mutating the original array.
Example: Using forEach()
to Transform an Array
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = [];
numbers.forEach((num) => {
doubledNumbers.push(num * 2);
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
Example: Using map()
to Transform an Array
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = numbers.map((num) => num * 2);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
As you can see, map()
not only makes the code more concise but also avoids the need to handle the creation of a new array manually. This is faster and much easier to use!
reduce()
The reduce()
method is used to aggregate array values into a single output, such as summing numbers, flattening arrays, or creating more complex data structures. It applies a function against an accumulator and each element in the array to reduce it to a single value.
Example: Using forEach()
to Sum Array Values
let numbers = [1, 2, 3, 4, 5];
let sum = 0;
numbers.forEach((num) => {
sum += num;
});
console.log(sum); // 15
Example: Using reduce()
to Sum Array Values
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
reduce()
provides a clear and functional approach to aggregating data, making the code more readable and expressive.
filter()
The filter()
method is used to create a new array with elements that pass a specified test. It is really useful for extracting subsets of data based on certain conditions.
Example: Using forEach()
to Filter an Array
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = [];
numbers.forEach((num) => {
if (num % 2 === 0) {
evenNumbers.push(num);
}
});
console.log(evenNumbers); // [2, 4]
Example: Using filter()
to Filter an Array
let numbers = [1, 2, 3, 4, 5];
let evenNumbers = numbers.filter((num) => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
Just like the examples above, using filter()
simplifies the code and makes it more intuitive to understand the logic.
Performance and Readability Benefits
Using these higher-order functions makes the code more concise and can lead to performance improvements due to better optimization by JavaScript engines. These methods promote a functional programming style for extra cool kid points, which can enhance code readability and maintainability.
Readability and Maintenance
When writing code, readability and maintainability are crucial factors to consider. Cleaner code is easier to understand, debug, and extend. Now we can look at how you can chain these methods to powerup your code and go even further beyond what forEach()
can do on its own.
Code Readability
Example: Using forEach()
for Multiple Transformations
let numbers = [1, 2, 3, 4, 5];
let transformedNumbers = [];
numbers.forEach((num) => {
if (num % 2 === 0) {
transformedNumbers.push(num * 2);
}
});
console.log(transformedNumbers); // [4, 8]
Example: Using filter()
and map()
for Multiple Transformations
let numbers = [1, 2, 3, 4, 5];
let transformedNumbers = numbers
.filter((num) => num % 2 === 0)
.map((num) => num * 2);
console.log(transformedNumbers); // [4, 8]
By chaining filter()
and map()
, the code is easier to understand. It just looks better, and we know it will be faster.
Maintainability
Example: Extending forEach()
Logic
let numbers = [1, 2, 3, 4, 5];
let result = [];
numbers.forEach((num) => {
if (num % 2 === 0) {
result.push(num * 2);
}
if (num > 2) {
result.push(num + 1);
}
});
console.log(result); // [4, 8, 4, 5, 6]
Ooof - those nested if
s inside the loop is pretty smelly. It makes it much harder to understand what is going on, and heaven help you when you need to come back and change it in six months. This is basically writing code that is setting a trap for yourself.
Example: Extending Logic with Separate Functions
let numbers = [1, 2, 3, 4, 5];
function doubleEvenNumbers(num) {
return num % 2 === 0 ? num * 2 : null;
}
function incrementNumbersGreaterThanTwo(num) {
return num > 2 ? num + 1 : null;
}
function isNotNull(value) {
return value !== null;
}
let doubledEvens = numbers
.map(doubleEvenNumbers)
.filter(isNotNull);
let incrementedNumbers = numbers
.map(incrementNumbersGreaterThanTwo)
.filter(isNotNull);
let result = doubledEvens.concat(incrementedNumbers);
console.log(result); // [4, 8, 4, 5, 6]
By separating the logic into distinct functions and using map()
and filter()
, the code becomes modular and easier to maintain. Each function is responsible for a specific transformation, making it easier to update and debug.
Best Practices for Maintaining Clean and Readable Code
- Use Higher-Order Functions: Whenever possible, use
map()
,reduce()
, andfilter()
for their intended purposes. They make the code more declarative and easier to understand. - Keep Functions Small and Focused: Break down complex transformations into smaller, reusable functions.
- Avoid Mutating State: Prefer returning new arrays or values instead of mutating existing ones. This promotes immutability and reduces side effects.
- Document Intent: Use comments and meaningful function names to document the intent behind transformations.
By following these best practices, you can ensure that your code remains clean, readable, and maintainable, making it easier to work with in the long run.
Other Limitations
Beyond performance issues and functional limitations, the forEach()
function has several other limitations that can impact its usability and effectiveness in various scenarios. Let's look at two additional limitations: the lack of a return value and the handling of sparse arrays.
5. Lack of Return Value
One of the fundamental characteristics of the forEach()
function is that it always returns undefined
. This limitation means that forEach()
cannot be used in contexts where a return value is expected or needed.
In many situations, returning a value, either the transformed array or a specific aggregated result is helpful. Since forEach()
does not provide a return value, it means you have to use external variables to store results, which can lead to less functional and more imperative code.
Example: Using forEach()
Without Return Value
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = [];
numbers.forEach((num) => {
doubledNumbers.push(num * 2);
});
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
In this example, doubledNumbers
must be declared and managed outside the forEach()
callback, which adds extra complexity and potential for errors.
Comparison with Other Methods
Other array methods like map()
, reduce()
, and filter()
return values that can be directly used or chained with other operations, promoting a more functional programming style.
Example: Using map()
with Return Value
let numbers = [1, 2, 3, 4, 5];
let doubledNumbers = numbers.map((num) => num * 2);
console.log(doubledNumbers); // [2, 4, 6, 8, 10]
By using map()
, the transformation and the assignment to a new array are handled in a single, concise statement.
Example: Refactoring forEach()
with reduce()
for Aggregation
let numbers = [1, 2, 3, 4, 5];
let sum = 0;
numbers.forEach((num) => {
sum += num;
});
console.log(sum); // 15
Example: Using reduce()
for Aggregation
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce((acc, num) => acc + num, 0);
console.log(sum); // 15
Using reduce()
not only simplifies the code but also makes the aggregation operation more explicit and functional.
6. Handling Sparse Arrays
Sparse arrays are arrays with "holes" where some elements are missing or undefined. The behavior of forEach()
with sparse arrays can be counterintuitive and lead to unexpected results.
forEach()
skips over sparse elements in an array, meaning that the callback function is not called for missing or undefined elements. If you don't understand this, or are counting on each item being processed even if they are empty, then you're gonna have a bad time.
Example: Handling Sparse Arrays with forEach()
let sparseArray = [1, , 3, , 5];
sparseArray.forEach((num, index) => {
console.log(`Index ${index}: ${num}`);
});
// Output:
// Index 0: 1
// Index 2: 3
// Index 4: 5
In this example, the forEach()
method skips the holes in the sparse array, which might not be what you want.
Alternative Methods
Other iteration methods like for
, for...of
, and map()
do not skip sparse elements, making them more reliable.
Example: Handling Sparse Arrays with for...of
let sparseArray = [1, , 3, , 5];
for (const [index, num] of sparseArray.entries()) {
console.log(`Index ${index}: ${num}`);
}
// Output:
// Index 0: 1
// Index 1: undefined
// Index 2: 3
// Index 3: undefined
// Index 4: 5
Using for...of
with entries()
provides a complete iteration over the array, including the sparse elements.
Example: Handling Sparse Arrays with map()
let sparseArray = [1, , 3, , 5];
let filledArray = sparseArray.map((num, index) => num === undefined ? null : num);
console.log(filledArray); // [1, null, 3, null, 5]
With map()
, you can handle sparse elements more explicitly, filling them with a default value if necessary.
Example: Ensuring Correctness with reduce()
let sparseArray = [1, , 3, , 5];
let sum = sparseArray.reduce((acc, num) => acc + (num || 0), 0);
console.log(sum); // 9
By using reduce()
, you can aggregate values from a sparse array while ensuring that missing elements do not affect the result.
7. Practical Examples and Case Studies
To really understand the limitations of the forEach()
function, let's look at some real-world scenarios where these limitations cause problems. In this section, we will look at practical examples and case studies where using forEach()
has led to performance or functionality issues. We will also explore how refactoring the code using alternative methods improved the code's performance, readability, and maintainability.
Case Study 1: Performance Bottleneck in Data Processing
Scenario: A developer needs to process a large dataset containing millions of records. The initial implementation uses forEach()
to iterate over the array and perform some computationally intensive operations.
Initial Implementation with forEach()
let largeArray = Array.from({ length: 1e6 }, (_, i) => i);
let results = [];
console.time('forEach');
largeArray.forEach((num) => {
// Perform a complex operation
let result = num * 2;
results.push(result);
});
console.timeEnd('forEach');
// Output:
// forEach: 39.712158 ms
Issues:
- Performance: The
forEach()
implementation is much slower due to the overhead of the callback function. - Memory Usage: The
results
array grows in size as the loop progresses, consuming more memory.
Refactored Implementation with for
Loop
let largeArray = Array.from({ length: 1e6 }, (_, i) => i);
let results = [];
console.time('for');
for (let i = 0; i < largeArray.length; i++) {
// Perform a complex operation
let result = largeArray[i] * 2;
results.push(result);
}
console.timeEnd('for');
// Output:
// for: 18.368896 ms
Benefits:
- Improved Performance: The
for
loop reduces the execution time. - Better Control: The developer can easily manage the loop flow and optimize memory usage.
Case Study 2: Asynchronous Operations in Loop
Scenario: A web application needs to fetch data from an API for each item in an array. The initial implementation uses forEach()
with async/await
, expecting the loop to handle asynchronous operations correctly.
Initial Implementation with forEach()
let fetchData = async (id) => {
// Simulate an asynchronous API call
return new Promise((resolve) => setTimeout(() => resolve(`Data for ${id}`), 1000));
};
let ids = [1, 2, 3, 4, 5];
ids.forEach(async (id) => {
let data = await fetchData(id);
console.log(data);
});
// Output: Likely an empty array or undefined
Issues:
- Asynchronous Handling: The
forEach()
loop does not wait for the asynchronous operations to complete, leading to incorrect or incomplete results.
Refactored Implementation with for...of
Loop
async function fetchData(id) {
return new Promise(resolve => setTimeout(() => resolve(`Data for ${id}`), 1000));
}
let ids = [1, 2, 3, 4, 5];
async function processIds() {
for (let id of ids) {
let data = await fetchData(id);
console.log(data);
}
}
processIds();
// Output: Data for 1, Data for 2, ..., Data for 5 (each logged after 1 second delay)
Benefits:
- Correct Asynchronous Handling: The
for...of
loop ensures that each asynchronous operation completes before moving to the next iteration. - Readable Code: The code is easier to understand and maintain.
Case Study 3: Complex Data Transformations
Scenario: A developer needs to perform multiple transformations on an array of objects, such as filtering, mapping, and reducing data. The initial implementation uses forEach()
for each transformation step.
Initial Implementation with forEach()
let items = [
{ value: 1, active: true },
{ value: 2, active: false },
{ value: 3, active: true },
{ value: 4, active: true },
{ value: 5, active: false }
];
let activeItems = [];
let totalValue = 0;
items.forEach((item) => {
if (item.active) {
activeItems.push(item);
}
});
activeItems.forEach((item) => {
totalValue += item.value;
});
console.log(activeItems); // [{ value: 1, active: true }, { value: 3, active: true }, { value: 4, active: true }]
console.log(totalValue); // 8
Issues:
- Redundancy: Multiple
forEach()
loops make the code redundant and harder to maintain. - Performance: Iterating over the array multiple times is less efficient.
Refactored Implementation with filter()
, map()
, and reduce()
let items = [
{ value: 1, active: true },
{ value: 2, active: false },
{ value: 3, active: true },
{ value: 4, active: true },
{ value: 5, active: false }
];
let activeItems = items.filter(item => item.active);
let totalValue = activeItems.reduce((sum, item) => sum + item.value, 0);
console.log(activeItems); // [{ value: 1, active: true }, { value: 3, active: true }, { value: 4, active: true }]
console.log(totalValue); // 8
Benefits:
- Concise Code: The use of
filter()
andreduce()
makes the code more concise and easier to understand. - Improved Performance: The array is only iterated over twice, improving performance.
- Maintainability: The functional approach is more maintainable and scalable.
So - when should you use it?
At this point, you may be wondering if you should use forEach()
at all. Honestly, most of the time you probably shouldn't. It really only makes sense when you need to perform operations on each element of an array without modifying the array itself or returning a new array.
Here are some guidelines on when you might want to use forEach()
:
- Side Effects Only:
- Use
forEach()
when you need to execute side effects (such as logging, updating external variables, or performing DOM manipulations) on each element of an array.
- Use
- No Need for Return Values:
- Use
forEach()
when you do not need a return value or a transformed array from the iteration.
- Use
- Mutating External Variables:
- Use
forEach()
when you need to mutate external variables within the callback function.
- Use
- Simple Iteration Tasks:
- Use
forEach()
for simple iteration tasks where readability and simplicity are more important than performance.
- Use
- No Asynchronous Operations:
- Use
forEach()
for synchronous operations only, as it does not handle asynchronous callbacks effectively.
- Use
- No Requirement for Index:
- Use
forEach()
when you do not need the index of the current element in the iteration.
- Use
This list should raise some red flags for most developers, especially those who want to write more functional code that is less likely to lead to bugs. I am reluctant to say you should never use it... but I do think it is safe to say that it is best to avoid it. I'd say using a more direct array method (or a chain of them) should be the first thing to do, and then you can fall back to using a for
loop if all else fails.
You know then that you will be avoiding potential issues, edge cases, and future bugs... especially when another developer comes along and extends your code.
Excellent post! I knew about a few of the limitations but the amount of performance difference was surprising.
Are these limitations of Foreach Loop applicable in PHP, Java and C#?
In reply to Are these limitations of… by kingstonejob
This only applies to Javascript. I'm not sure how this applies to other languages.
When it comes to performance, for of is even much slower than forEach
In reply to When it comes to performance… by phạmquanghuy
I'm not so sure. The best I can find is that
for
is pretty much always the fastest, andfor of
only seems to be a slight issue with massive datasets (far beyond what a frontend app would do). Andfor of
is still faster thanforEach
.In any event,
forEach
still has way too many other limitations, andfor of
is much more readable thanfor
when you don't know the size of the array you are iterating over.See:
Do
map
andreduce
have the same performance overhead relative to a basicfor
loop?The difference in behavior for undefined elements in the array is a deal killer, however. 98% of the time, developers will fail to remember this issue, even if they read about it before.
I tried this on myself... Below is my results.
Sometimes for of is slower than forEach, Map is definitely slowest. I tested it in a few order for more accurate, but map is always a slowest and results is similar like below.
Tested on Mac Studio M1 Max.
First results:
map: 31.096923828125 ms
forEach: 14.196044921875 ms
forof: 16.885986328125 ms
for: 6.1611328125 ms
forsepconst: 6.281982421875 ms
Second results:
map: 27.2080078125 ms
forEach: 17.361083984375 ms
forof: 14.570068359375 ms
for: 6.177001953125 ms
forsepconst: 6.3369140625 ms
Third results, with uncommented querySelectorAll:
map: 2184.64697265625 ms
forEach: 2154.363037109375 ms
forof: 2177.132080078125 ms
for: 2176.41015625 ms
forsepconst: 2166.047119140625 ms
Great article, but there's one small issue. The behavior of
map
andreduce
is the same asforEach
for sparse arrays. All 3 higher order functions skip over empty items.The result won't be the same as the one shared in the example of
map
Similarly for reduce, it also skips over empty items in an array. So the code shared in it's example can be changed and it would give us the same result. Please refer the following: