How to optimize array operations in JavaScript
Array operations can become performance bottlenecks when working with large datasets, especially when using inefficient methods or creating unnecessary copies. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve optimized array-heavy applications serving millions of users, reducing processing time from seconds to milliseconds by choosing the right methods and avoiding common performance traps.
The most effective approach uses native methods wisely and avoids creating intermediate arrays.
Use for Loop for Simple Iterations
const numbers = Array.from({ length: 100000 }, (_, i) => i)
// Slow - forEach has function call overhead
console.time('forEach')
let sum1 = 0
numbers.forEach(n => sum1 += n)
console.timeEnd('forEach')
// Fast - for loop is optimized by engines
console.time('for')
let sum2 = 0
for (let i = 0; i < numbers.length; i++) {
sum2 += numbers[i]
}
console.timeEnd('for')
// Fastest - cache length
console.time('for-cached')
let sum3 = 0
for (let i = 0, len = numbers.length; i < len; i++) {
sum3 += numbers[i]
}
console.timeEnd('for-cached')
// Results:
// forEach: ~15ms
// for: ~5ms
// for-cached: ~3ms
Avoid Creating Intermediate Arrays
const users = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `User ${i}`,
age: 20 + (i % 50),
active: i % 2 === 0
}))
// Bad - creates multiple intermediate arrays
console.time('chained')
const result1 = users
.filter(u => u.active)
.map(u => u.age)
.filter(age => age > 30)
console.timeEnd('chained')
// Good - single pass with reduce
console.time('reduce')
const result2 = users.reduce((acc, u) => {
if (u.active && u.age > 30) {
acc.push(u.age)
}
return acc
}, [])
console.timeEnd('reduce')
// Better - for loop, no intermediate arrays
console.time('for-loop')
const result3 = []
for (let i = 0; i < users.length; i++) {
const user = users[i]
if (user.active && user.age > 30) {
result3.push(user.age)
}
}
console.timeEnd('for-loop')
// Results:
// chained: ~45ms (3 passes + 2 intermediate arrays)
// reduce: ~20ms (1 pass)
// for-loop: ~10ms (1 pass, no function overhead)
Use find() Instead of filter()[0]
const users = Array.from({ length: 100000 }, (_, i) => ({
id: i,
name: `User ${i}`
}))
// Bad - iterates entire array
console.time('filter')
const user1 = users.filter(u => u.id === 50000)[0]
console.timeEnd('filter')
// Good - stops at first match
console.time('find')
const user2 = users.find(u => u.id === 50000)
console.timeEnd('find')
// Results:
// filter: ~50ms (full iteration)
// find: ~3ms (stops early)
Optimize Search with indexOf/includes
const ids = Array.from({ length: 100000 }, (_, i) => i)
// Slow - for checking existence
console.time('filter-check')
const exists1 = ids.filter(id => id === 50000).length > 0
console.timeEnd('filter-check')
// Fast - optimized native method
console.time('includes')
const exists2 = ids.includes(50000)
console.timeEnd('includes')
// Faster for multiple lookups - use Set
console.time('set-creation')
const idSet = new Set(ids)
console.timeEnd('set-creation')
console.time('set-has')
const exists3 = idSet.has(50000)
console.timeEnd('set-has')
// Results:
// filter-check: ~40ms
// includes: ~2ms
// set-creation: ~10ms (one-time cost)
// set-has: <1ms (O(1) lookup)
Preallocate Arrays When Size Known
const count = 100000
// Slow - array grows dynamically
console.time('push')
const arr1 = []
for (let i = 0; i < count; i++) {
arr1.push(i * 2)
}
console.timeEnd('push')
// Fast - preallocate size
console.time('preallocate')
const arr2 = new Array(count)
for (let i = 0; i < count; i++) {
arr2[i] = i * 2
}
console.timeEnd('preallocate')
// Fastest - Array.from with map
console.time('array-from')
const arr3 = Array.from({ length: count }, (_, i) => i * 2)
console.timeEnd('array-from')
// Results:
// push: ~15ms
// preallocate: ~8ms
// array-from: ~5ms
Use some() and every() for Early Exit
const numbers = Array.from({ length: 100000 }, (_, i) => i)
// Bad - checks all elements
console.time('filter-all')
const hasEven1 = numbers.filter(n => n % 2 === 0).length > 0
console.timeEnd('filter-all')
// Good - stops at first match
console.time('some')
const hasEven2 = numbers.some(n => n % 2 === 0)
console.timeEnd('some')
// Bad - checks all elements
console.time('filter-every')
const allPositive1 = numbers.filter(n => n >= 0).length === numbers.length
console.timeEnd('filter-every')
// Good - stops at first false
console.time('every')
const allPositive2 = numbers.every(n => n >= 0)
console.timeEnd('every')
// Results:
// filter-all: ~30ms
// some: <1ms (stops at index 0)
// filter-every: ~35ms
// every: ~15ms
Batch Array Operations
const data = Array.from({ length: 10000 }, (_, i) => i)
// Bad - multiple passes
console.time('multiple-passes')
const doubled = data.map(n => n * 2)
const filtered = doubled.filter(n => n > 5000)
const summed = filtered.reduce((sum, n) => sum + n, 0)
console.timeEnd('multiple-passes')
// Good - single pass
console.time('single-pass')
let result = 0
for (let i = 0; i < data.length; i++) {
const doubled = data[i] * 2
if (doubled > 5000) {
result += doubled
}
}
console.timeEnd('single-pass')
// Results:
// multiple-passes: ~25ms
// single-pass: ~5ms
Avoid splice() for Removals
const arr = Array.from({ length: 10000 }, (_, i) => i)
// Slow - splice shifts elements
console.time('splice')
const arr1 = [...arr]
for (let i = arr1.length - 1; i >= 0; i--) {
if (arr1[i] % 2 === 0) {
arr1.splice(i, 1)
}
}
console.timeEnd('splice')
// Fast - filter creates new array
console.time('filter')
const arr2 = arr.filter(n => n % 2 !== 0)
console.timeEnd('filter')
// Faster - manual copy
console.time('manual')
const arr3 = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] % 2 !== 0) {
arr3.push(arr[i])
}
}
console.timeEnd('manual')
// Results:
// splice: ~500ms (O(n²) due to shifting)
// filter: ~15ms
// manual: ~8ms
Optimize Sorting
const items = Array.from({ length: 100000 }, () => ({
id: Math.random(),
name: `Item ${Math.random()}`,
value: Math.floor(Math.random() * 1000)
}))
// Slow - creates comparison function every call
console.time('inline-compare')
const sorted1 = [...items].sort((a, b) => a.value - b.value)
console.timeEnd('inline-compare')
// Fast - reuse comparison function
console.time('cached-compare')
const compareByValue = (a, b) => a.value - b.value
const sorted2 = [...items].sort(compareByValue)
console.timeEnd('cached-compare')
// For strings - use localeCompare
const names = Array.from({ length: 10000 }, (_, i) => `Name ${i}`)
console.time('manual-string-compare')
names.sort((a, b) => a > b ? 1 : -1)
console.timeEnd('manual-string-compare')
console.time('locale-compare')
names.sort((a, b) => a.localeCompare(b))
console.timeEnd('locale-compare')
Flatten Arrays Efficiently
const nested = Array.from({ length: 1000 }, (_, i) =>
Array.from({ length: 10 }, (_, j) => i * 10 + j)
)
// Slow - concat with spread
console.time('concat-spread')
const flat1 = [].concat(...nested)
console.timeEnd('concat-spread')
// Fast - flat() method
console.time('flat')
const flat2 = nested.flat()
console.timeEnd('flat')
// Faster - manual push
console.time('manual-push')
const flat3 = []
for (let i = 0; i < nested.length; i++) {
for (let j = 0; j < nested[i].length; j++) {
flat3.push(nested[i][j])
}
}
console.timeEnd('manual-push')
// Fastest - preallocate
console.time('preallocate-flatten')
const totalLength = nested.reduce((sum, arr) => sum + arr.length, 0)
const flat4 = new Array(totalLength)
let index = 0
for (let i = 0; i < nested.length; i++) {
for (let j = 0; j < nested[i].length; j++) {
flat4[index++] = nested[i][j]
}
}
console.timeEnd('preallocate-flatten')
// Results:
// concat-spread: ~80ms
// flat: ~40ms
// manual-push: ~20ms
// preallocate-flatten: ~10ms
Use TypedArrays for Numeric Data
const size = 1000000
// Regular array
console.time('regular-array')
const arr1 = new Array(size)
for (let i = 0; i < size; i++) {
arr1[i] = i
}
const sum1 = arr1.reduce((sum, n) => sum + n, 0)
console.timeEnd('regular-array')
// Typed array - 30% faster
console.time('typed-array')
const arr2 = new Int32Array(size)
for (let i = 0; i < size; i++) {
arr2[i] = i
}
let sum2 = 0
for (let i = 0; i < size; i++) {
sum2 += arr2[i]
}
console.timeEnd('typed-array')
// Memory usage
console.log('Regular array memory:', arr1.length * 8, 'bytes')
console.log('Typed array memory:', arr2.byteLength, 'bytes')
// Results:
// regular-array: ~50ms
// typed-array: ~30ms
// Memory savings: 50%
Best Practice Note
This is how we optimize array operations across all CoreUI JavaScript applications for maximum performance. Array operations are fast in modern JavaScript engines, but choosing the right method matters for large datasets. Always use for loops for simple iterations when performance is critical, avoid creating intermediate arrays with method chaining, use find/some/every for early exits, preallocate arrays when size is known, and profile your code to identify actual bottlenecks. Remember that readable code is often fast enough - only optimize hot paths identified through profiling.
For production applications, consider using CoreUI’s Admin Templates which include performance-optimized data handling patterns.
Related Articles
For related JavaScript optimization, check out how to avoid memory leaks in JavaScript and how to profile JavaScript performance.



