Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

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).

For related performance patterns, check out how to implement debounce with abort in JavaScript and how to throttle a function in JavaScript.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author