Debounce과 Throttle을 활용한 최적화

dyeon-dev·2025년 10월 2일
post-thumbnail

Debounce

디바운스는 짧은 시간 동안 동일한 이벤트가 연속적으로 발생할 때, 마지막 이벤트만 실행되도록 하는 기법으로 성능 최적화에 자주 사용된다.
많은 이벤트가 발생하는 상황에서 불필요한 함수 호출을 줄이고, 최종 입력이나 동작만 반영하고 싶을 때 활용된다.

활용 예시

1) 검색 자동완성
사용자가 키보드를 입력할 때마다 API 요청을 보내면 서버 부하가 크다.
디바운스를 적용하면 입력이 멈춘 뒤 일정 시간이 지난 후에만 검색 요청을 보낸다.
2) 입력 검증 (Form Validation)
이메일, 비밀번호 등 실시간 입력 검증 시 매 글자마다 검증하지 않고, 입력이 멈춘 후에만 검증 로직을 실행한다.
3) 마우스 Hover 기반 UI 처리
마우스 이동 시 mousemove 이벤트는 매우 빈번하게 발생한다.
예: 특정 UI에서 마우스를 올리면 다른 데이터가 노출될 때, 디바운스를 적용하여 불필요한 연속 호출을 막는다.
실제 사례: 과거 아마존은 마우스 움직임의 x, y 좌표 기울기를 계산해
좌우 45도 기울기 기준으로 스와이핑과 스크롤을 구분,
스크롤 동작일 경우 스와이프 기능을 preventDefault()로 막는 방식에 디바운스를 활용했다.
4) 윈도우 리사이즈 이벤트 처리
브라우저 창 크기를 변경하면 resize 이벤트가 매우 자주 발생한다.
디바운스를 적용해 최종적으로 크기 변경이 끝난 뒤 일정 시간 후에 한 번만 실행되도록 할 수 있다.
예: "창 크기 변경 후 2초가 지나면 레이아웃 다시 계산" 같은 방식.

디바운싱 간단 예시 코드(ts)

  titleInput?.addEventListener('input', debounce(function(e: Event) {
    console.log(e.target.value)
  }, 300))

  const debounce = <E extends Event>(fn: (e: E) => void, delay: number) => {
    let timer: number | undefined;
    return (e: E) => {
      if (timer) clearTimeout(timer);
      timer = window.setTimeout(() => fn(e), delay);
    };
  };
  1. input 이벤트 등록
    사용자가 입력 필드에 값을 입력할 때마다 input 이벤트가 발생하고, 이 이벤트를 addEventListener로 감지한다.
  2. debounce 래퍼 적용
    이벤트 핸들러로 바로 함수를 전달하는 대신, debounce 함수로 감싼 핸들러를 전달한다. 이렇게 하면 입력이 연속으로 빠르게 발생해도 최종 입력 후 일정 시간 동안 추가 입력이 없을 때만 실제 함수가 실행된다.
  3. 타이머 관리
    debounce 내부에서는 setTimeout을 사용해 지정된 지연 시간(delay) 이후에 콜백을 실행한다. 새로운 이벤트가 발생하면 이전에 설정된 타이머를 clearTimeout으로 취소하고, 다시 타이머를 설정한다. 따라서 마지막 입력만 유효하게 처리된다.
  4. 실제 콜백 실행
    지연 시간이 모두 지나면 fn(e)가 실행된다.
    • 여기서 e는 input 이벤트 객체이며, e.target은 이벤트가 발생한 input 요소를 가리킨다.
    • 따라서 e.target.value를 통해 사용자가 최종적으로 입력한 텍스트 값을 얻을 수 있다.
    • 이렇게 하면 사용자가 키보드를 빠르게 입력할 때마다 매번 실행되지 않고, 최종적으로 멈춘 시점의 값만 처리된다.

Throttle

쓰로틀(throttle)은 짧은 시간 동안 동일한 이벤트가 연속적으로 발생할 때, 일정 주기마다 한 번씩만 실행되도록 하는 기법이다.
debounce와 비슷한 개념이지만, throttle은 일정 주기마다 함수를 실행하는 방식이라는 점에서 차이가 있다.
throttle의 설정 시간으로 100ms를 주게 되면, 해당 이벤트는 100ms 동안 최대 한 번만 발생하게 된다. 즉 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 한다.
디바운스와 달리 “마지막 이벤트만 실행”하는 것이 아니라, 일정 간격을 두고 주기적으로 실행하는 점이 특징이다.

활용 예시

1) 스크롤 이벤트 처리
스크롤할 때 scroll 이벤트는 수십~수백 번씩 연속 발생한다.
스크롤 위치에 따라 헤더 숨김/노출, 무한 스크롤 데이터 로드 등을 처리할 때 쓰로틀을 적용하면 0.2초 간격마다 실행되도록 제한할 수 있다.
2) 마우스 이동(mousemove) 이벤트
드래그 앤 드롭 구현 시 마우스를 조금만 움직여도 이벤트가 과도하게 발생한다.
쓰로틀을 적용해 일정 간격마다 좌표를 갱신하면 부드럽게 UI를 업데이트할 수 있다.
3) 페이지네이션 (버튼 기반 로드)
사용자가 버튼을 너무 빨리 여러 번 클릭하는 경우, API가 중복 호출될 수 있다.
쓰로틀을 적용해 1초에 1번만 클릭 이벤트 처리되도록 제한하면 서버 요청을 보호할 수 있다.
4) 리사이즈 중간 값 반영
디바운스는 보통 리사이즈가 끝난 후 한 번 실행되지만,
쓰로틀은 리사이즈가 진행되는 동안에도 0.5초 간격으로 레이아웃 계산 같은 작업을 수행할 수 있다.
즉, 사용자에게 실시간에 가까운 피드백을 주면서도 성능 부담을 줄일 수 있다.

쓰로틀링 간단 예시 코드(ts)

 // throttle 유틸 함수
const throttle = <E extends Event>(fn: (e: E) => void, limit: number) => {
  let waiting = false;

  return (e: E) => {
    if (!waiting) {
      fn(e); // 즉시 실행
      waiting = true;
      setTimeout(() => {
        waiting = false; // 일정 시간 후 다시 실행 가능
      }, limit);
    }
  };
};

// 사용 예시: 스크롤 이벤트
const scrollHandler = throttle((e: Event) => {
  console.log("현재 스크롤 위치:", window.scrollY);
}, 500);

window.addEventListener("scroll", scrollHandler);
  1. 이벤트 등록
    scroll 이벤트에 throttle로 감싼 함수를 등록한다.
  2. 즉시 실행 & 대기 상태 진입
    이벤트가 발생했을 때 waiting이 false라면 fn(e)를 실행하고, waiting을 true로 바꾼다.
  3. 지정된 시간 동안 무시
    setTimeout을 사용해 일정 시간(limit) 동안은 새로운 이벤트를 무시한다.
  4. 재실행 가능 상태로 전환
    limit 시간이 지나면 waiting을 false로 되돌려 다시 실행할 수 있다.

debounce vs throttle 비교

debounce

  • 스크롤이 내리다가 스크롤을 멈추고 특정 시간 후 이벤트가 하나 발생한다.

throttle

  • 이벤트가 하나 발생한 뒤 특정 시간동안 이벤트가 발생하지 않지만 그 시간이 지나면 다시 이벤트가 동작한다.

Leading edge vs Tradiling edge

debounce는 연이어 발생한 이벤트를 하나의 그룹으로 묶어서 처리하는 방식으로, 주로 처음이나 마지막으로 실행된 함수를 처리하는 방식으로 사용된다.
debounce의 예시 코드는 마지막으로 실행된 함수를 처리하는 방식이고, 처음은 다루지 않았다.
debounce의 개념으로 이벤트의 처음이나 마지막을 처리하는 방식을 나눠서 살펴볼 수 있다.

Leading edge

  • 처음 실행하는 함수를 처리
  • 처음에 실행한 함수를 실행하고 그 뒤의 이벤트들을 무시함
  • 예시: 입력 필드에 '안녕하세요' 입력시 'ㅇ'만 요청이 보내짐
Tradiling edge
  • 가장 마지막에 실행하는 함수를 처리
  • 마지막에 실행한 함수를 실행하고 그 전의 이벤트들을 무시함

Debounce Leading edge vs Trottle 차이

요청이 들어왔을 때 그 후 일정 시간 동안 모든 요청을 무시한다는 것에서 Leading edge과 Trottle 작동이 유사하기도 하다.
그렇다면 이 두 개는 어떤 차이가 있는걸까?

Debounce Leading edge

  • 설정한 타이머 시간 안에 요청이 지속적으로 들어올 경우 모든 요청을 무시하게 된다.

Trottle

  • 반면에 Trottle은 지속적으로 요청이 들어올 경우 정해진 타이머 시간이 지나면 요청을 허용한다.

디바운싱 적용해보기

디바운싱을 간단하게 Tradiling edge 방식으로 글자수 계산에 적용해보았다.
디바운싱을 0.3초로 설정해두어 매번 요청하는 부하를 줄일 수 있었다.

0개의 댓글