How to implement lazy evaluation in JavaScript
Lazy evaluation defers computation until values are actually needed, reducing memory usage and improving performance for large datasets. As the creator of CoreUI with 25 years of JavaScript optimization experience, I’ve used lazy evaluation to process massive datasets without loading everything into memory.
The most effective approach uses JavaScript generators to create lazy iterables that compute values on demand.
Direct Answer
Use generators for lazy evaluation:
function* lazyRange(start, end) {
for (let i = start; i <= end; i++) {
yield i
}
}
const numbers = lazyRange(1, 1000000)
console.log(numbers.next().value) // 1 (only computes first value)
Lazy Map
function* lazyMap(iterable, fn) {
for (const item of iterable) {
yield fn(item)
}
}
const numbers = lazyRange(1, 1000)
const doubled = lazyMap(numbers, x => x * 2)
for (const num of doubled) {
if (num > 10) break // Only computes up to 10
console.log(num)
}
Lazy Filter
function* lazyFilter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item
}
}
}
const numbers = lazyRange(1, 1000000)
const evens = lazyFilter(numbers, x => x % 2 === 0)
const first10Evens = lazyFilter(evens, (x, i) => i < 10)
Chain Lazy Operations
function* lazyRange(start, end) {
for (let i = start; i <= end; i++) {
yield i
}
}
function* lazyMap(iterable, fn) {
for (const item of iterable) {
yield fn(item)
}
}
function* lazyFilter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) {
yield item
}
}
}
function* lazyTake(iterable, count) {
let taken = 0
for (const item of iterable) {
if (taken >= count) break
yield item
taken++
}
}
const result = lazyTake(
lazyMap(
lazyFilter(
lazyRange(1, 1000000),
x => x % 2 === 0
),
x => x * 2
),
5
)
console.log([...result]) // [4, 8, 12, 16, 20]
Lazy Object Properties
const createLazyObject = (props) => {
const cache = {}
return new Proxy({}, {
get(target, prop) {
if (prop in cache) {
return cache[prop]
}
if (prop in props) {
cache[prop] = props[prop]()
return cache[prop]
}
return undefined
}
})
}
const user = createLazyObject({
name: () => 'John Doe',
posts: () => {
console.log('Fetching posts...')
return ['Post 1', 'Post 2']
}
})
console.log(user.name) // No log
console.log(user.posts) // Logs: Fetching posts...
console.log(user.posts) // No log (cached)
Infinite Sequences
function* fibonacci() {
let [a, b] = [0, 1]
while (true) {
yield a
;[a, b] = [b, a + b]
}
}
const fibs = fibonacci()
console.log(fibs.next().value) // 0
console.log(fibs.next().value) // 1
console.log(fibs.next().value) // 1
console.log(fibs.next().value) // 2
Best Practice Note
This is the same lazy evaluation pattern we use in CoreUI for processing large data tables without loading everything into memory. Generators provide true lazy evaluation in JavaScript, computing values only when needed. This is essential for infinite sequences, large datasets, or expensive computations.
Related Articles
For related performance patterns, check out how to create a memoization function in JavaScript and how to optimize loop performance in JavaScript.



