쓰로틀

연속으로 발생하는 이벤트를 일정 시간 간격마다 최대 한 번만 실행되도록 제한하는 기법입니다.

동작 원리

이벤트 발생 타임라인 (간격: 200ms)

이벤트:  ● ● ● ● ● ● ● ● ● ● ● ● ● ●
         |       |       |       |
실행:    ○       ○       ○       ○  (200ms 간격으로 주기적 실행)

첫 이벤트 발생 시 즉시 실행하고, 이후 설정한 간격이 지나기 전까지 추가 이벤트는 무시합니다.

대표 사용 사례

사용 사례설명
스크롤 이벤트스크롤 위치에 따른 UI 업데이트 (헤더 고정, 애니메이션 등)
무한 스크롤일정 간격으로 스크롤 위치를 체크하여 데이터 추가 로딩
마우스 이동 추적드래그, 그리기 등에서 과도한 이벤트 호출 방지
실시간 대시보드빈번한 데이터 업데이트를 일정 주기로 제한
게임 입력 처리연속 클릭/키 입력의 실행 빈도 제한

JavaScript 구현

기본 구현

function throttle(fn, interval) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 사용 예: 스크롤 이벤트
window.addEventListener('scroll', throttle(() => {
  console.log('스크롤 위치:', window.scrollY);
}, 200));

타이머 기반 구현 (trailing 지원)

function throttle(fn, interval, { leading = true, trailing = true } = {}) {
  let lastTime = 0;
  let timer = null;

  return function (...args) {
    const now = Date.now();
    const remaining = interval - (now - lastTime);

    if (remaining <= 0) {
      clearTimeout(timer);
      timer = null;
      lastTime = now;
      if (leading) fn.apply(this, args);
    } else if (!timer && trailing) {
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = null;
        fn.apply(this, args);
      }, remaining);
    }
  };
}

React 예시: 무한 스크롤

import { useState, useEffect, useCallback } from 'react';

function InfiniteList() {
  const [items, setItems] = useState([]);
  const [page, setPage] = useState(1);

  const handleScroll = useCallback(
    throttle(() => {
      const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
      if (scrollHeight - scrollTop - clientHeight < 200) {
        setPage(prev => prev + 1);
      }
    }, 300),
    []
  );

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  useEffect(() => {
    fetch(`/api/items?page=${page}`)
      .then(res => res.json())
      .then(data => setItems(prev => [...prev, ...data]));
  }, [page]);

  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  );
}

디바운스 vs 쓰로틀 상세 비교

항목디바운스 (Debounce)쓰로틀 (Throttle)
핵심 전략마지막 이벤트 기준으로 지연 실행일정 주기마다 실행 허용
이벤트 중 실행X 실행하지 않음O 주기적으로 실행
실행 보장이벤트가 멈춰야 실행이벤트 중에도 주기적 실행
중간 상태 반영X 최종 상태만 반영O 중간 상태도 반영
대표 사례검색 입력, 폼 저장스크롤, 리사이즈, 드래그
5초 동안 연속 이벤트 발생 시 (간격: 1초)

디바운스:                              ○  → 총 1회
쓰로틀:  ○     ○     ○     ○     ○     → 총 5회

선택 가이드

이벤트 처리 전략 선택

사용자 입력이 "멈춘 후" 처리해야 하는가?
  ├── YES → 디바운스 (검색, 자동 저장)
  └── NO
        └── 이벤트 "도중에도" 주기적 반응이 필요한가?
              ├── YES → 쓰로틀 (스크롤, 드래그, 리사이즈)
              └── NO  → 일반 이벤트 핸들러

실무 팁: resize나 scroll 이벤트는 초당 수십~수백 회 발생할 수 있어, 쓰로틀 없이 처리하면 렌더링 성능이 급격히 저하됩니다. 보통 100~300ms 간격이 사용자 경험과 성능 사이의 적절한 균형점입니다.

CSLOG에서 보기