# Typing indicators debounced at client and expire server-side on timeout

- **Pattern:** `ab-000746` (`community-realtime.presence.typing-indicator-debounce`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000746
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000746)

## Why it matters

A typing indicator fired on every keystroke with no debounce generates roughly one WebSocket event per 100ms per active user. For a community with 100 simultaneous typists, that is 1,000 events per second — before any real messages are sent. Without server-side expiry, users who close their browser mid-compose are shown as typing indefinitely. Both failure modes inflate bandwidth, CPU cost, and connection noise for zero user-visible benefit, violating ISO 25010 performance-efficiency requirements.

## Severity rationale

Medium because undebounced typing events generate excessive message volume that inflates server load and costs in proportion to the active user count.

## Remediation

Debounce the client-side typing event to at most one emission per 500 ms, and expire server-side typing state after 10 seconds without an update.

```ts
// Client: debounce before emitting
let stopTimer: ReturnType<typeof setTimeout>;
const onInput = () => {
  socket.emit('typing', { channel });
  clearTimeout(stopTimer);
  stopTimer = setTimeout(() => socket.emit('typing_stop', { channel }), 3_000);
};
input.addEventListener('input', onInput);

// Server: expire stale typing status
const typingTimers = new Map<string, ReturnType<typeof setTimeout>>();
socket.on('typing', ({ channel }) => {
  if (typingTimers.has(socket.userId)) clearTimeout(typingTimers.get(socket.userId)!);
  const t = setTimeout(() => {
    removeTypingUser(channel, socket.userId);
    broadcastTypingList(channel);
  }, 10_000);
  typingTimers.set(socket.userId, t);
  addTypingUser(channel, socket.userId);
  broadcastTypingList(channel);
});
```

## Detection

- **ID:** `typing-indicator-debounce`
- **Severity:** `medium`
- **What to look for:** Count all typing indicator event paths. Enumerate: client-side debounce mechanism, server-side expiry timeout. Quote the actual debounce interval and timeout values if found.
- **Pass criteria:** Typing events are debounced on the client at an interval of at least 200ms, and the server removes typing status after a timeout of no more than 30 seconds without an update.
- **Fail criteria:** Typing events are sent on every keystroke, or server doesn't expire typing status.
- **Skip (N/A) when:** The platform does not implement typing indicators.
- **Detail on fail:** `"Typing indicator sent on every keystroke without debounce. Client sends 100+ messages per second."`
- **Remediation:** Implement client-side debounce and server-side expiry:

  ```ts
  // Client: Debounce typing events
  let typingTimeout: NodeJS.Timeout;
  const debounceTyping = () => {
    clearTimeout(typingTimeout);
    socket.emit('typing', { channel: 'general' });
    typingTimeout = setTimeout(() => {
      socket.emit('typing_stop', { channel: 'general' });
    }, 3000);
  };

  input.addEventListener('input', debounceTyping);

  // Server: Expire typing status
  const typingUsers = new Map<string, Set<string>>();
  const typingTimers = new Map<string, NodeJS.Timeout>();

  socket.on('typing', (data) => {
    if (!typingUsers.has(data.channel)) {
      typingUsers.set(data.channel, new Set());
    }
    typingUsers.get(data.channel)!.add(socket.userId);

    // Clear old timer
    if (typingTimers.has(socket.userId)) {
      clearTimeout(typingTimers.get(socket.userId)!);
    }

    // Set new timeout to remove typing status
    const timer = setTimeout(() => {
      typingUsers.get(data.channel)?.delete(socket.userId);
      io.to(data.channel).emit('typing_update', { typing: Array.from(typingUsers.get(data.channel) || []) });
    }, 10000);

    typingTimers.set(socket.userId, timer);
    io.to(data.channel).emit('typing_update', { typing: Array.from(typingUsers.get(data.channel) || []) });
  });
  ```

## External references

- iso-25010 performance-efficiency.resource-utilization

Taxons: cost-efficiency, performance

HTML version: https://auditbuffet.com/patterns/ab-000746
