How to implement throttle with leading edge in JavaScript
Throttle with leading edge executes a function immediately on the first call, then enforces a cooldown period before allowing subsequent executions. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve implemented throttle with leading edge for scroll handlers and button clicks that reduced event processing by 90% while maintaining immediate user feedback.
The most effective approach executes immediately on the first call with configurable trailing edge behavior.
Basic Throttle with Leading Edge
function throttle(func, limit) {
let inThrottle
return function(...args) {
if (!inThrottle) {
func.apply(this, args)
inThrottle = true
setTimeout(() => {
inThrottle = false
}, limit)
}
}
}
// Usage
const handleScroll = throttle(() => {
console.log('Scroll position:', window.scrollY)
}, 1000)
window.addEventListener('scroll', handleScroll)
Throttle with Configurable Options
function throttle(func, limit, options = {}) {
let lastRun = 0
let timeout
let lastArgs
const {
leading = true,
trailing = true
} = options
const invoke = function(context, args) {
lastRun = Date.now()
lastArgs = null
func.apply(context, args)
}
const throttled = function(...args) {
const now = Date.now()
const remaining = limit - (now - lastRun)
lastArgs = args
if (remaining <= 0 || remaining > limit) {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
if (leading) {
invoke(this, args)
}
} else if (trailing && !timeout) {
timeout = setTimeout(() => {
invoke(this, lastArgs)
timeout = null
}, remaining)
}
}
throttled.cancel = () => {
if (timeout) {
clearTimeout(timeout)
timeout = null
}
lastRun = 0
lastArgs = null
}
return throttled
}
Scroll Handler Example
const handleScroll = throttle(() => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
if (scrollTop > 300) {
document.getElementById('back-to-top').style.display = 'block'
} else {
document.getElementById('back-to-top').style.display = 'none'
}
}, 200, { leading: true, trailing: false })
window.addEventListener('scroll', handleScroll)
Button Click Protection
const submitForm = throttle(async (e) => {
e.preventDefault()
console.log('Submitting form...')
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: new FormData(e.target)
})
const result = await response.json()
console.log('Form submitted:', result)
} catch (error) {
console.error('Submission failed:', error)
}
}, 2000, { leading: true, trailing: false })
document.getElementById('form').addEventListener('submit', submitForm)
React Hook Implementation
import { useRef, useCallback } from 'react'
function useThrottle(callback, limit, options = {}) {
const { leading = true, trailing = true } = options
const lastRun = useRef(0)
const timeout = useRef(null)
const throttledCallback = useCallback(
(...args) => {
const now = Date.now()
const remaining = limit - (now - lastRun.current)
if (remaining <= 0 || remaining > limit) {
if (timeout.current) {
clearTimeout(timeout.current)
timeout.current = null
}
if (leading) {
lastRun.current = now
callback(...args)
}
} else if (trailing && !timeout.current) {
timeout.current = setTimeout(() => {
lastRun.current = Date.now()
callback(...args)
timeout.current = null
}, remaining)
}
},
[callback, limit, leading, trailing]
)
return throttledCallback
}
// Usage in component
function SearchComponent() {
const handleSearch = useThrottle(
(query) => {
console.log('Searching for:', query)
// Perform search
},
500,
{ leading: true, trailing: true }
)
return (
<input
type="text"
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search..."
/>
)
}
Infinite Scroll Example
const loadMore = throttle(() => {
const scrollTop = window.pageYOffset
const windowHeight = window.innerHeight
const documentHeight = document.documentElement.scrollHeight
if (scrollTop + windowHeight >= documentHeight - 100) {
console.log('Loading more items...')
fetchMoreItems().then(items => {
appendItems(items)
})
}
}, 200, { leading: true, trailing: false })
window.addEventListener('scroll', loadMore)
Best Practice Note
This is the same throttle implementation we use in CoreUI’s scroll handlers and event-heavy components. Leading edge execution provides immediate user feedback while throttling prevents performance degradation from excessive function calls. Use throttle for continuous events (scroll, resize, mousemove) and debounce for discrete events (search input, form validation).
Related Articles
For related performance patterns, check out how to implement debounce with abort in JavaScript and how to throttle a function in JavaScript.



