2804. Array Prototype ForEach 🔒
Problem Description
This problem asks you to implement a custom forEach
method for JavaScript/TypeScript arrays. The goal is to extend the Array prototype so that any array can call array.forEach(callback, context)
.
The method should iterate through each element of the array and execute a callback function for each element. The callback function receives three arguments:
currentValue
: The current element being processedindex
: The index of the current element in the arrayarray
: A reference to the entire array
Additionally, the method accepts an optional context
parameter that determines what this
refers to inside the callback function. When the context is provided, the callback should be executed with that context as its this
value.
Key requirements:
- The
forEach
method should not return any value (void return type) - You should implement this without using built-in array methods
- The callback should be invoked for each element in the array in order from index 0 to length-1
For example, if you have an array [1, 2, 3]
and a callback that doubles each value, after calling forEach
, the array would be modified to [2, 4, 6]
. The context object allows you to control the execution context of the callback function.
The solution uses a simple for loop to iterate through the array and callback.call(context, ...)
to invoke the callback with the specified context and arguments for each element.
Intuition
The key insight is understanding how JavaScript's built-in forEach
works under the hood. At its core, forEach
is simply a loop that visits each element in an array and executes a function for each one.
To implement this, we need to think about the basic mechanics:
- We need to traverse the array from start to end
- For each element, we need to invoke the callback function
- The callback needs access to three pieces of information: the current element, its index, and the full array
The straightforward approach is to use a for loop with an index counter i
that goes from 0
to this.length - 1
. At each iteration, we have:
- The current element:
this[i]
- The current index:
i
- The array itself:
this
(since we're extending the Array prototype)
The trickier part is handling the context
parameter. In JavaScript, when you call a function normally like callback()
, the this
inside that function might not be what you expect. The context
parameter allows users to explicitly set what this
should be inside the callback.
This is where callback.call()
comes in. The call()
method lets us invoke a function while explicitly setting its this
value. The first argument to call()
becomes the this
value inside the function, and subsequent arguments are passed as regular parameters.
So callback.call(context, this[i], i, this)
means:
- Execute
callback
- Set its
this
tocontext
- Pass
this[i]
as the first argument (currentValue) - Pass
i
as the second argument (index) - Pass
this
as the third argument (array)
This mirrors exactly how the native forEach
behaves, allowing the callback to access all necessary information while respecting the provided execution context.
Solution Approach
The implementation follows a straightforward iterative approach using a traditional for loop to traverse the array elements.
Implementation Steps:
-
Method Extension: We extend
Array.prototype
to add our customforEach
method that accepts two parameters:callback
: The function to execute for each elementcontext
: The optional object to use asthis
when executing the callback
-
Loop Structure: We use a standard for loop with index
i
starting from0
and continuing whilei < this.length
:for (let i = 0; i < this.length; ++i)
Here,
this
refers to the array instance on whichforEach
is called. -
Callback Invocation: For each iteration, we invoke the callback using the
call
method:callback.call(context, this[i], i, this)
This ensures:
- The callback's
this
value is set to the providedcontext
- The callback receives three arguments in order:
this[i]
: The current element valuei
: The current indexthis
: The entire array reference
- The callback's
-
No Return Value: The method completes without returning anything (void), as specified by the problem requirements.
Key Design Decisions:
-
Using
++i
(pre-increment) instead ofi++
is a minor optimization that avoids creating a temporary variable, though the difference is negligible in modern JavaScript engines. -
The
call
method is essential here because it allows us to control the execution context. If we simply calledcallback(this[i], i, this)
, thethis
inside the callback would be undefined (in strict mode) or the global object (in non-strict mode), not the desired context. -
We iterate through all indices from
0
tolength - 1
, which handles both dense and sparse arrays correctly, executing the callback even for undefined elements (matching nativeforEach
behavior).
The entire implementation is concise yet complete, requiring only a single loop and one function invocation per element, resulting in O(n) time complexity where n is the array length.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to see how our custom forEach
implementation works.
Example Setup:
// Define our custom forEach
Array.prototype.forEach = function(callback, context) {
for (let i = 0; i < this.length; ++i) {
callback.call(context, this[i], i, this);
}
};
// Create a sample array and context object
const numbers = [10, 20, 30];
const multiplier = {
factor: 2,
results: []
};
// Define a callback that uses the context
function multiplyAndStore(value, index, array) {
// 'this' refers to the multiplier object
const result = value * this.factor;
this.results.push(`Index ${index}: ${value} * ${this.factor} = ${result}`);
}
// Execute forEach with context
numbers.forEach(multiplyAndStore, multiplier);
Step-by-step execution:
Iteration 1 (i = 0):
- Current element:
this[0]
= 10 - Execute:
callback.call(multiplier, 10, 0, [10, 20, 30])
- Inside callback:
this
= multiplier objectvalue
= 10,index
= 0,array
= [10, 20, 30]- Calculates: 10 * 2 = 20
- Pushes to results: "Index 0: 10 * 2 = 20"
Iteration 2 (i = 1):
- Current element:
this[1]
= 20 - Execute:
callback.call(multiplier, 20, 1, [10, 20, 30])
- Inside callback:
this
= multiplier objectvalue
= 20,index
= 1,array
= [10, 20, 30]- Calculates: 20 * 2 = 40
- Pushes to results: "Index 1: 20 * 2 = 40"
Iteration 3 (i = 2):
- Current element:
this[2]
= 30 - Execute:
callback.call(multiplier, 30, 2, [10, 20, 30])
- Inside callback:
this
= multiplier objectvalue
= 30,index
= 2,array
= [10, 20, 30]- Calculates: 30 * 2 = 60
- Pushes to results: "Index 2: 30 * 2 = 60"
Final Result:
console.log(multiplier.results);
// Output: [
// "Index 0: 10 * 2 = 20",
// "Index 1: 20 * 2 = 40",
// "Index 2: 30 * 2 = 60"
// ]
The key insight here is how callback.call(context, ...)
allows the callback function to access properties and methods of the multiplier
object through this
. Without the context parameter, this.factor
and this.results
would be undefined, causing the callback to fail. This demonstrates why the call
method is essential for properly implementing forEach
with context support.
Solution Implementation
1class Array(list):
2 """
3 Custom Array class extending list with forEach method implementation
4 """
5
6 def forEach(self, callback, context=None):
7 """
8 Custom implementation of JavaScript's Array.prototype.forEach method
9 Iterates through array elements and executes a callback function for each element
10
11 Args:
12 callback: Function to execute for each element
13 context: Optional context object to bind as 'this' when executing callback
14 """
15 # Iterate through each element in the array
16 for i in range(len(self)):
17 # Execute callback with specified context, passing current element, index, and array
18 if context is not None:
19 # Bind context as 'self' for the callback if provided
20 callback(self[i], i, self)
21 else:
22 # Execute callback without specific context binding
23 callback(self[i], i, self)
24
25
26# Example usage demonstration
27if __name__ == "__main__":
28 # Create an Array instance with initial values
29 arr = Array([1, 2, 3])
30
31 # Define callback function that doubles each element
32 def callback(val, i, arr):
33 """Callback function that modifies array elements in place"""
34 arr[i] = val * 2
35
36 # Context object (not directly used in Python version as Python doesn't have 'this' binding)
37 context = {"context": True}
38
39 # Execute forEach with callback and context
40 arr.forEach(callback, context)
41
42 # Display the modified array
43 print(arr) # Output: [2, 4, 6]
44
1/**
2 * Custom implementation of forEach method for arrays
3 * Iterates through array elements and executes a callback function for each element
4 */
5public class ArrayForEach {
6
7 /**
8 * Functional interface representing a callback function for forEach operation
9 * @param <T> The type of elements in the array
10 */
11 @FunctionalInterface
12 public interface ForEachCallback<T> {
13 /**
14 * Executes the callback logic for each element
15 * @param element The current element being processed
16 * @param index The index of the current element
17 * @param array The array being iterated
18 */
19 void call(T element, int index, T[] array);
20 }
21
22 /**
23 * Custom forEach implementation for generic arrays
24 * @param array The array to iterate through
25 * @param callback The callback function to execute for each element
26 * @param context The context object (not directly used in Java implementation)
27 * @param <T> The type of elements in the array
28 */
29 public static <T> void forEach(T[] array, ForEachCallback<T> callback, Object context) {
30 // Iterate through each element in the array
31 for (int i = 0; i < array.length; i++) {
32 // Execute callback, passing current element, index, and array
33 callback.call(array[i], i, array);
34 }
35 }
36
37 /**
38 * Example usage demonstrating the forEach method
39 */
40 public static void main(String[] args) {
41 // Initialize an array with sample values
42 Integer[] arr = {1, 2, 3};
43
44 // Define the callback function that doubles each element
45 ForEachCallback<Integer> callback = (val, i, array) -> {
46 // Multiply each element by 2 and update the array
47 array[i] = val * 2;
48 };
49
50 // Create a context object (for compatibility with original signature)
51 Object context = new Object();
52
53 // Execute forEach with the callback
54 forEach(arr, callback, context);
55
56 // Print the modified array
57 System.out.print("Output: [");
58 for (int i = 0; i < arr.length; i++) {
59 System.out.print(arr[i]);
60 if (i < arr.length - 1) {
61 System.out.print(", ");
62 }
63 }
64 System.out.println("]"); // Output: [2, 4, 6]
65 }
66}
67
1#include <vector>
2#include <functional>
3
4/**
5 * Custom implementation of forEach method for vectors
6 * Iterates through vector elements and executes a callback function for each element
7 *
8 * @tparam T The type of elements in the vector
9 * @param callback Function to execute for each element, takes (element, index, vector reference)
10 * @param context Optional context object (can be nullptr)
11 */
12template<typename T>
13void forEach(std::vector<T>& vec,
14 std::function<void(T&, size_t, std::vector<T>&)> callback,
15 void* context = nullptr) {
16 // Iterate through each element in the vector
17 for (size_t i = 0; i < vec.size(); ++i) {
18 // Execute callback, passing current element by reference, index, and vector reference
19 callback(vec[i], i, vec);
20 }
21}
22
23/**
24 * Example usage:
25 *
26 * int main() {
27 * std::vector<int> arr = {1, 2, 3};
28 *
29 * // Define callback function that doubles each element
30 * auto callback = [](int& val, size_t i, std::vector<int>& arr) {
31 * arr[i] = val * 2;
32 * };
33 *
34 * // Context object (optional, not used in this example)
35 * void* context = nullptr;
36 *
37 * // Apply forEach to the vector
38 * forEach(arr, callback, context);
39 *
40 * // Print result: [2, 4, 6]
41 * for (const auto& elem : arr) {
42 * std::cout << elem << " ";
43 * }
44 *
45 * return 0;
46 * }
47 */
48
1/**
2 * Custom implementation of Array.prototype.forEach method
3 * Iterates through array elements and executes a callback function for each element
4 */
5Array.prototype.forEach = function (callback: Function, context: any): void {
6 // Iterate through each element in the array
7 for (let i = 0; i < this.length; ++i) {
8 // Execute callback with specified context, passing current element, index, and array
9 callback.call(context, this[i], i, this);
10 }
11};
12
13/**
14 * Example usage:
15 *
16 * const arr = [1, 2, 3];
17 * const callback = (val: number, i: number, arr: number[]) => arr[i] = val * 2;
18 * const context = { "context": true };
19 *
20 * arr.forEach(callback, context);
21 *
22 * console.log(arr); // Output: [2, 4, 6]
23 */
24
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the array. The implementation uses a single for loop that iterates through each element of the array exactly once, calling the callback function for each element. Each callback invocation is assumed to be O(1)
for basic operations, though the actual complexity depends on the callback implementation.
Space Complexity: O(1)
for the forEach implementation itself. The method only uses a constant amount of extra space for the loop counter variable i
. No additional data structures are created that scale with the input size. However, the total space complexity can vary depending on what the callback function does internally - if the callback creates new objects or data structures, that would add to the space complexity, but the forEach method itself maintains constant space usage.
Common Pitfalls
1. Modifying Array Length During Iteration
One of the most common pitfalls when implementing forEach
is not handling cases where the callback function modifies the array's length during iteration. This can lead to skipped elements or accessing undefined indices.
Problem Example:
const arr = [1, 2, 3, 4, 5];
arr.forEach((val, i, array) => {
if (val === 2) {
array.push(6); // Adding elements during iteration
}
console.log(val);
});
In the basic implementation, the loop condition i < this.length
is evaluated on each iteration, so adding elements will extend the loop, potentially causing infinite loops. Removing elements might skip some elements.
Solution: Cache the initial array length before starting the iteration:
Array.prototype.forEach = function(callback, context) {
const len = this.length; // Cache initial length
for (let i = 0; i < len; ++i) {
if (i in this) { // Check if index still exists
callback.call(context, this[i], i, this);
}
}
};
2. Not Handling Sparse Arrays Correctly
JavaScript arrays can be sparse (have "holes" where indices are missing). The native forEach
skips these holes, but a naive implementation might not.
Problem Example:
const sparseArr = [1, , 3]; // Index 1 is a hole
sparseArr.forEach(val => console.log(val));
// Should only log: 1, 3 (not undefined for index 1)
Solution:
Use the in
operator to check if an index actually exists:
Array.prototype.forEach = function(callback, context) {
for (let i = 0; i < this.length; ++i) {
if (i in this) { // Only process existing indices
callback.call(context, this[i], i, this);
}
}
};
3. Incorrect Context Binding in Arrow Functions
When the callback is an arrow function, attempting to bind a context won't work as arrow functions have lexically bound this
that cannot be changed.
Problem Example:
const context = { multiplier: 2 };
[1, 2, 3].forEach((val) => {
console.log(this.multiplier * val); // 'this' won't be 'context'
}, context);
Solution: Document this behavior and suggest using regular functions when context binding is needed:
// Use regular function for context binding
[1, 2, 3].forEach(function(val) {
console.log(this.multiplier * val); // Works correctly
}, context);
4. Not Validating Callback Parameter
Failing to check if the callback is actually a function can cause runtime errors.
Problem Example:
[1, 2, 3].forEach("not a function"); // Should throw TypeError
Solution: Add type validation:
Array.prototype.forEach = function(callback, context) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
for (let i = 0; i < this.length; ++i) {
if (i in this) {
callback.call(context, this[i], i, this);
}
}
};
5. Performance Issues with Large Arrays
Using this.length
directly in the loop condition causes property lookup on each iteration, which can impact performance for very large arrays.
Solution: Cache the length value:
Array.prototype.forEach = function(callback, context) {
const len = this.length;
for (let i = 0; i < len; ++i) {
if (i in this) {
callback.call(context, this[i], i, this);
}
}
};
These pitfalls highlight the importance of considering edge cases and following the native implementation's behavior closely when creating polyfills or custom implementations of built-in methods.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!