디바운스 vs 쓰로틀 완벽 비교 가이드 - 언제 어떤 것을 사용할까?

이번에는 **디바운스(Debounce)**와 **쓰로틀(Throttle)**의 차이점을 명확히 이해하고, 실전에서 언제 어떤 것을 사용해야 하는지 알아보겠습니다.

핵심 차이점

특징디바운스쓰로틀
실행 시점마지막 이벤트 후 일정 시간 지난 후일정 시간마다 한 번씩
실행 횟수그룹의 마지막에 1번만주기적으로 여러 번
적합한 상황입력 완료 후 실행실시간 반응 필요

시각적 비교

디바운스 (Debounce)

이벤트: |--|--|--|--|--|--|--|--|--|--|
실행:                                    |
  • 연속된 이벤트를 그룹화하여 마지막에 한 번만 실행

쓰로틀 (Throttle)

이벤트: |--|--|--|--|--|--|--|--|--|--|
실행:    |     |     |     |     |     |
  • 일정 주기마다 한 번씩 실행

언제 어떤 것을 사용할까?

디바운스 사용 시기

  • 검색창 자동완성: 사용자가 타이핑을 멈춘 후 검색
  • 폼 자동 저장: 입력 완료 후 저장
  • 윈도우 리사이즈: 크기 조정 완료 후 레이아웃 재계산

쓰로틀 사용 시기

  • 스크롤 이벤트: 무한 스크롤, 고정 헤더
  • 게임 캐릭터 이동: 마우스/키보드 입력 처리
  • 실시간 차트 업데이트: 주기적인 데이터 갱신

실전 예제: 검색 vs 스크롤

// 디바운스: 검색창 (입력 완료 후 실행)
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {
  fetchSearchResults(e.target.value);
}, 300));

// 쓰로틀: 스크롤 (주기적 실행)
window.addEventListener('scroll', throttle(() => {
  updateHeaderPosition();
}, 100));

고급 활용: React Hook

// 커스텀 디바운스 Hook
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// 커스텀 쓰로틀 Hook
function useThrottle(callback, delay) {
  const lastRun = useRef(Date.now());

  return useCallback((...args) => {
    if (Date.now() - lastRun.current >= delay) {
      callback(...args);
      lastRun.current = Date.now();
    }
  }, [callback, delay]);
}

// 사용 예시
function SearchComponent() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 300);

  useEffect(() => {
    if (debouncedQuery) {
      fetchSearchResults(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="검색어를 입력하세요"
    />
  );
}

성능 최적화 팁

1. 적절한 딜레이 설정

// 검색: 300ms (사용자가 타이핑을 멈출 시간)
const searchDebounce = debounce(searchFunction, 300);

// 스크롤: 100ms (부드러운 반응)
const scrollThrottle = throttle(scrollFunction, 100);

// 리사이즈: 500ms (레이아웃 재계산 비용 고려)
const resizeDebounce = debounce(resizeFunction, 500);

2. 메모리 누수 방지

// 컴포넌트 언마운트 시 정리
useEffect(() => {
  const debouncedHandler = debounce(handleInput, 300);

  input.addEventListener('input', debouncedHandler);

  return () => {
    input.removeEventListener('input', debouncedHandler);
  };
}, []);

3. 조건부 적용

// 네트워크 상태에 따른 동적 조정
function adaptiveDebounce(callback, baseDelay = 300) {
  const isSlowConnection = navigator.connection?.effectiveType === 'slow-2g';
  const delay = isSlowConnection ? baseDelay * 2 : baseDelay;

  return debounce(callback, delay);
}

실제 프로젝트 적용 사례

1. 쇼핑몰 검색 기능

class SearchManager {
  constructor() {
    this.searchInput = document.getElementById('search');
    this.resultsContainer = document.getElementById('results');
    this.setupEventListeners();
  }

  setupEventListeners() {
    // 디바운스: 검색 요청 최적화
    this.searchInput.addEventListener('input', debounce((e) => {
      this.performSearch(e.target.value);
    }, 300));

    // 쓰로틀: 검색 결과 스크롤
    this.resultsContainer.addEventListener('scroll', throttle(() => {
      this.handleInfiniteScroll();
    }, 200));
  }

  async performSearch(query) {
    if (query.length < 2) return;

    try {
      const results = await fetch(`/api/search?q=${query}`);
      this.displayResults(await results.json());
    } catch (error) {
      console.error('검색 실패:', error);
    }
  }

  handleInfiniteScroll() {
    const { scrollTop, scrollHeight, clientHeight } = this.resultsContainer;

    if (scrollTop + clientHeight >= scrollHeight - 100) {
      this.loadMoreResults();
    }
  }
}

2. 대시보드 실시간 업데이트

class DashboardManager {
  constructor() {
    this.charts = document.querySelectorAll('.chart');
    this.setupRealTimeUpdates();
  }

  setupRealTimeUpdates() {
    // 쓰로틀: 차트 업데이트 (실시간성 유지)
    window.addEventListener('resize', throttle(() => {
      this.resizeCharts();
    }, 100));

    // 디바운스: 설정 저장 (입력 완료 후)
    this.setupAutoSave();
  }

  setupAutoSave() {
    const settingsForm = document.getElementById('settings');

    settingsForm.addEventListener('change', debounce(() => {
      this.saveSettings();
    }, 1000));
  }

  async saveSettings() {
    const formData = new FormData(settingsForm);

    try {
      await fetch('/api/settings', {
        method: 'POST',
        body: formData
      });
      console.log('설정 저장 완료');
    } catch (error) {
      console.error('설정 저장 실패:', error);
    }
  }
}

디버깅 팁

1. 실행 횟수 모니터링

function createMonitoredDebounce(callback, delay) {
  let callCount = 0;

  const debouncedFunction = debounce((...args) => {
    callCount++;
    console.log(`디바운스 실행 횟수: ${callCount}`);
    callback(...args);
  }, delay);

  return debouncedFunction;
}

function createMonitoredThrottle(callback, delay) {
  let callCount = 0;

  const throttledFunction = throttle((...args) => {
    callCount++;
    console.log(`쓰로틀 실행 횟수: ${callCount}`);
    callback(...args);
  }, delay);

  return throttledFunction;
}

2. 성능 측정

function measurePerformance(func, name) {
  return function (...args) {
    const start = performance.now();
    const result = func.apply(this, args);
    const end = performance.now();

    console.log(`${name} 실행 시간: ${end - start}ms`);
    return result;
  };
}

// 사용 예시
const optimizedSearch = measurePerformance(
  debounce(searchFunction, 300),
  '디바운스 검색'
);

핵심 포인트:

  • 디바운스: 입력 완료 후 실행 (검색, 저장)
  • 쓰로틀: 주기적 실행 (스크롤, 실시간 업데이트)
  • 성능 최적화: 적절한 딜레이 설정과 메모리 관리
  • 실전 적용: 프로젝트 특성에 맞는 패턴 선택