Skip to content

Performance Utilities API Reference

Performance utilities for optimizing function execution with debouncing and throttling.


Table of Contents


Overview

Bindra provides performance optimization utilities:

  • debounce() - Delay function execution until after a pause
  • throttle() - Limit function execution rate
  • debounceWithMaxWait() - Debounce with guaranteed maximum wait

These utilities are essential for:

  • Search input handling
  • Scroll event optimization
  • Resize event handling
  • Auto-save functionality
  • API rate limiting
typescript
import { debounce, throttle, debounceWithMaxWait } from 'bindra';

Functions

debounce()

Creates a debounced function that delays execution until after a specified delay since the last invocation.

typescript
function debounce<T extends (...args: any[]) => any>(
  fn: T,
  delay: number
): (...args: Parameters<T>) => void

Parameters

  • fn (Function) - The function to debounce
  • delay (number) - Delay in milliseconds

Returns

Debounced version of the function

How It Works

The function only executes after delay milliseconds of inactivity. Each new call resets the timer.

Call 1 ─┬─── (timer starts)
Call 2 ─┴─┬─ (timer resets)
Call 3 ───┴─┬─ (timer resets)
          └─────► Execute (after delay)

Example

typescript
import { debounce } from 'bindra';

const search = debounce((query: string) => {
  console.log('Searching for:', query);
  // API call here
}, 300);

// User types quickly
search('a');   // Timer starts
search('al');  // Timer resets
search('ali'); // Timer resets
search('alic'); // Timer resets
search('alice'); // Timer resets
// After 300ms of no calls: "Searching for: alice"

throttle()

Creates a throttled function that only executes at most once per specified time limit.

typescript
function throttle<T extends (...args: any[]) => any>(
  fn: T,
  limit: number
): (...args: Parameters<T>) => void

Parameters

  • fn (Function) - The function to throttle
  • limit (number) - Minimum time between executions in milliseconds

Returns

Throttled version of the function

How It Works

The function executes immediately on first call, then ignores subsequent calls until limit milliseconds have passed.

Call 1 ─► Execute immediately
Call 2 ─X (ignored - within limit)
Call 3 ─X (ignored - within limit)
        └── (limit ms passes)
Call 4 ─► Execute

Example

typescript
import { throttle } from 'bindra';

const handleScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
  // Update UI based on scroll
}, 100);

window.addEventListener('scroll', handleScroll);
// Executes at most once per 100ms

debounceWithMaxWait()

Creates a debounced function with a maximum wait time, ensuring execution at least once per max wait period.

typescript
function debounceWithMaxWait<T extends (...args: any[]) => any>(
  fn: T,
  delay: number,
  maxWait: number
): (...args: Parameters<T>) => void

Parameters

  • fn (Function) - The function to debounce
  • delay (number) - Normal debounce delay in milliseconds
  • maxWait (number) - Maximum time to wait before forced execution

Returns

Debounced function with max wait guarantee

How It Works

Combines debounce behavior with a maximum wait guarantee. If maxWait is reached, the function executes regardless of continued calls.

Call 1 ─┬─── (timers start)
Call 2 ─┴─┬─ (delay resets, maxWait continues)
Call 3 ───┴─┬─ (delay resets, maxWait continues)
          └──────► Execute (maxWait reached)
                   OR
          └────► Execute (delay reached)

Example

typescript
import { debounceWithMaxWait } from 'bindra';

const autoSave = debounceWithMaxWait(
  (data) => {
    console.log('Auto-saving:', data);
    // Save to API
  },
  1000,   // Normal delay: 1 second
  5000    // Max wait: 5 seconds
);

// User types continuously
autoSave({ content: 'a' });
autoSave({ content: 'ab' });
autoSave({ content: 'abc' });
// ... keeps typing for 5 seconds
// Forced save after 5 seconds, even if still typing

Usage Examples

Search Input with Debounce

typescript
import { debounce } from 'bindra';
import { DataSource } from 'bindra';

interface Product {
  id: number;
  name: string;
  price: number;
}

const products = new DataSource<Product>({
  url: '/api/products'
});

// Debounce search to avoid excessive API calls
const debouncedSearch = debounce(async (query: string) => {
  if (query.length < 2) return;
  
  await products.fetch({
    filter: { name: query }
  });
}, 300); // Wait 300ms after user stops typing

// In your UI
searchInput.addEventListener('input', (e) => {
  const query = (e.target as HTMLInputElement).value;
  debouncedSearch(query);
});

Scroll Handler with Throttle

typescript
import { throttle } from 'bindra';

const handleScroll = throttle(() => {
  const scrollY = window.scrollY;
  const windowHeight = window.innerHeight;
  const docHeight = document.documentElement.scrollHeight;
  
  // Check if near bottom
  if (scrollY + windowHeight >= docHeight - 100) {
    loadMoreContent();
  }
  
  // Update scroll indicator
  updateScrollIndicator(scrollY);
}, 100); // Execute at most every 100ms

window.addEventListener('scroll', handleScroll);

Auto-Save with Max Wait

typescript
import { debounceWithMaxWait } from 'bindra';

interface Document {
  id: number;
  content: string;
  lastSaved: Date;
}

const documents = new DataSource<Document>({
  url: '/api/documents'
});

// Auto-save with debounce but guarantee save every 5 seconds
const autoSave = debounceWithMaxWait(
  async (id: number, content: string) => {
    try {
      await documents.update(id, { content });
      showSaveIndicator('Saved');
    } catch (error) {
      showSaveIndicator('Failed to save');
    }
  },
  1000,  // Debounce: 1 second after user stops typing
  5000   // Max wait: Save at least every 5 seconds
);

// In your editor
editor.addEventListener('input', (e) => {
  const content = e.target.value;
  autoSave(documentId, content);
  showSaveIndicator('Saving...');
});

Window Resize with Throttle

typescript
import { throttle } from 'bindra';

const handleResize = throttle(() => {
  const width = window.innerWidth;
  
  // Update layout based on width
  if (width < 768) {
    switchToMobileLayout();
  } else if (width < 1024) {
    switchToTabletLayout();
  } else {
    switchToDesktopLayout();
  }
  
  // Recalculate dimensions
  recalculateLayout();
}, 200); // Execute at most every 200ms

window.addEventListener('resize', handleResize);

Infinite Scroll with Throttle

typescript
import { throttle } from 'bindra';

const products = new DataSource<Product>({
  url: '/api/products',
  pagination: {
    enabled: true,
    pageSize: 20
  }
});

const checkScroll = throttle(async () => {
  const scrollBottom = window.scrollY + window.innerHeight;
  const docHeight = document.documentElement.scrollHeight;
  
  // Load more when 200px from bottom
  if (scrollBottom >= docHeight - 200 && products.hasMore) {
    showLoadingIndicator();
    await products.fetchMore();
    hideLoadingIndicator();
  }
}, 300);

window.addEventListener('scroll', checkScroll);

API Rate Limiting with Throttle

typescript
import { throttle } from 'bindra';

// Limit API calls to once per second
const updateUserStatus = throttle(async (status: string) => {
  await fetch('/api/user/status', {
    method: 'POST',
    body: JSON.stringify({ status })
  });
}, 1000); // Max 1 call per second

// User can spam button, but API only called once/second
statusButton.addEventListener('click', () => {
  updateUserStatus('active');
});

Form Validation with Debounce

typescript
import { debounce } from 'bindra';

const validateUsername = debounce(async (username: string) => {
  if (username.length < 3) {
    showError('Username too short');
    return;
  }
  
  // Check availability on server
  const response = await fetch(`/api/check-username?name=${username}`);
  const { available } = await response.json();
  
  if (!available) {
    showError('Username taken');
  } else {
    showSuccess('Username available');
  }
}, 500); // Wait 500ms after user stops typing

usernameInput.addEventListener('input', (e) => {
  const username = (e.target as HTMLInputElement).value;
  validateUsername(username);
});

Mouse Move Tracking with Throttle

typescript
import { throttle } from 'bindra';

interface MousePosition {
  x: number;
  y: number;
  timestamp: number;
}

const positions: MousePosition[] = [];

const trackMouse = throttle((e: MouseEvent) => {
  positions.push({
    x: e.clientX,
    y: e.clientY,
    timestamp: Date.now()
  });
  
  // Limit array size
  if (positions.length > 100) {
    positions.shift();
  }
  
  // Update heatmap or analytics
  updateHeatmap(positions);
}, 50); // Track every 50ms

document.addEventListener('mousemove', trackMouse);

Real-time Collaboration with DebounceWithMaxWait

typescript
import { debounceWithMaxWait } from 'bindra';

const syncDocument = debounceWithMaxWait(
  async (content: string) => {
    // Send to server
    await fetch('/api/sync', {
      method: 'POST',
      body: JSON.stringify({ content })
    });
    
    // Broadcast to other users via WebSocket
    ws.send(JSON.stringify({
      type: 'document.update',
      content
    }));
  },
  500,   // Normal: 500ms after user stops typing
  3000   // Max: Sync at least every 3 seconds
);

editor.addEventListener('input', (e) => {
  syncDocument(e.target.value);
});

Best Practices

1. Choose the Right Tool

typescript
// ✅ Use debounce for user input
const search = debounce(searchFn, 300);

// ✅ Use throttle for continuous events
const scroll = throttle(scrollFn, 100);

// ✅ Use debounceWithMaxWait for auto-save
const save = debounceWithMaxWait(saveFn, 1000, 5000);

2. Set Appropriate Delays

typescript
// ✅ Good delays
const search = debounce(fn, 300);        // Search: 300ms
const scroll = throttle(fn, 100);        // Scroll: 100ms
const resize = throttle(fn, 200);        // Resize: 200ms
const autoSave = debounceWithMaxWait(fn, 1000, 5000); // Save: 1s/5s

// ❌ Too short - Still too many calls
const search = debounce(fn, 10);

// ❌ Too long - Poor UX
const search = debounce(fn, 5000);

3. Store Function References

typescript
// ✅ Good - Can remove listener
const handleScroll = throttle(() => {
  console.log('Scrolling');
}, 100);

window.addEventListener('scroll', handleScroll);
// Later...
window.removeEventListener('scroll', handleScroll);

// ❌ Bad - Can't remove
window.addEventListener('scroll', throttle(() => {
  console.log('Scrolling');
}, 100));

4. Consider Memory with Closures

typescript
// ✅ Good - Clean scope
const search = debounce(async (query: string) => {
  const results = await fetchResults(query);
  updateUI(results);
}, 300);

// ❌ Bad - Captures large objects
const largeData = new Array(1000000);
const search = debounce(() => {
  // Closure captures largeData
  console.log(largeData.length);
}, 300);

5. Use MaxWait for Critical Operations

typescript
// ✅ Good - Guarantees periodic saves
const autoSave = debounceWithMaxWait(saveFn, 1000, 5000);

// ❌ Bad - Might never save if user keeps typing
const autoSave = debounce(saveFn, 1000);

Performance Comparisons

Debounce vs Throttle

ScenarioBest ChoiceReason
Search inputDebounceWait for user to finish typing
Scroll eventsThrottleExecute periodically while scrolling
Resize eventsThrottleExecute periodically while resizing
Auto-saveDebounceWithMaxWaitWait for pause but guarantee periodic saves
Mouse moveThrottleTrack at regular intervals
Form validationDebounceValidate after user finishes field

Timing Examples

Debounce (300ms):

Input: a -> ab -> abc -> abcd
Time:  0ms  50ms  100ms 150ms
Execute:                     ───► (450ms) "abcd"

Throttle (300ms):

Scroll: ▼    ▼    ▼    ▼    ▼    ▼
Time:   0ms  50ms 100ms 150ms 200ms 250ms
Execute: ✓                   ✓
         0ms                 300ms

DebounceWithMaxWait (300ms delay, 1000ms max):

Input: a -> ab -> abc -> ... -> continuous typing
Time:  0ms  50ms  100ms ... 1000ms
Execute:                      ───► (1000ms) - forced by maxWait


← Back to API Reference

Released under the MIT License.