Throttle: one submission every {interval}, won’t start a new task until elapsed time has passed.
Debounce: one submission every {interval}, will cancel all pending tasks to schedule a new one.

Both are instantiated with and the only public API is attempt(Runnable), where Runnable is the task to be queued.

In practical terms:

int value = 0;
debounce.attempt(() -> System.out.println(++value));
debounce.attempt(() -> System.out.println(++value));
debounce.attempt(() -> System.out.println(++value));

In the above example (Debounce), System.out will print “2” after 250 milliseconds, and the first 2 calls will never happen.

int value = 0;
throttle.attempt(() -> System.out.println(++value));
throttle.attempt(() -> System.out.println(++value));
throttle.attempt(() -> System.out.println(++value));

In the above example (Throttle), System.out will print “0” immediately and discard the subsequent calls.

Usage:

private Throttle mThrottle = new Throttle(250);
...
mThrottle.attempt(this::someMethod);
private Debounce mDebounce = new Debounce(250);
...
mDebounce.attempt(this::someMethod);
import android.view.animation.AnimationUtils;

public class Throttle {

  private long mLastFiredTimestamp;
  private long mInterval;

  public Throttle(long interval) {
    mInterval = interval;
  }

  public void attempt(Runnable runnable) {
    if (hasSatisfiedInterval()) {
      runnable.run();
      mLastFiredTimestamp = getNow();
    }
  }

  private boolean hasSatisfiedInterval() {
    long elapsed = getNow() - mLastFiredTimestamp;
    return elapsed >= mInterval;
  }

  private long getNow() {
    return AnimationUtils.currentAnimationTimeMillis();
  }

}
import android.os.Handler;

public class Debounce {

  private Handler mHandler = new Handler();
  private long mInterval;

  public Debounce(long interval) {
    mInterval = interval;
  }

  public void attempt(Runnable runnable) {
    mHandler.removeCallbacksAndMessages(null);
    mHandler.postDelayed(runnable, mInterval);
  }

}