# How to Prevent Memory Leaks by Properly Cleaning Up Baileys Event Listeners

> Stop Baileys memory leaks by pairing ev.on() with ev.off(), using bindWaitForEvent, and removing WebSocket listeners before closing sockets. Ensure clean resource management.

- Repository: [WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys)
- Tags: best-practices
- Published: 2026-08-01

---

**Eliminate memory leaks in Baileys by always pairing `ev.on()` with `ev.off()`, using the `bindWaitForEvent` helper for temporary listeners, and removing WebSocket listeners before closing sockets.**

Baileys uses an internal `EventEmitter`—the `BaileysEventEmitter`—to dispatch everything from incoming messages to connection state changes. Leftover listeners retain references to sockets, authentication state, and closure-captured data, blocking garbage collection and causing memory leaks in long-running services. This guide shows how to properly clean up Baileys event listeners using patterns from the official source code.

---

## Core EventEmitter Architecture in Baileys

Baileys centralizes event flow through three listener systems. Understanding how each registers and removes listeners is critical to preventing memory leaks.

| Component | Purpose | Registration | Cleanup |
|-----------|---------|-------------|---------|
| `BaileysEventEmitter` (`ev`) | High‑level events (`messages.upsert`, `connection.update`, etc.) | `ev.on(event, listener)` | `ev.off(event, listener)` |
| WebSocket (`ws`) | Low‑level protocol messages | `ws.on('CB:*', handler)` | `ws.off('CB:*', handler)` |
| `bindWaitForEvent` | Auto‑cleaning wait‑for helper | Internal wrapper | Guaranteed `finally` block cleanup |

---

## The Guaranteed Cleanup Pattern in [`generics.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/generics.ts)

The `bindWaitForEvent` utility in [`src/Utils/generics.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/generics.ts) demonstrates the definitive pattern for temporary listeners. It adds both a target event listener and a connection‑state listener, then removes both in a `finally` block regardless of outcome.

```typescript
// src/Utils/generics.ts (lines 18-29)
ev.on('connection.update', closeListener)   // added
ev.on(event, listener)                      // added

try {
  // ... await condition ...
} finally {
  ev.off(event, listener)                   // removed
  ev.off('connection.update', closeListener) // removed
}

```

This pattern appears at lines 18‑29 of [[`src/Utils/generics.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/generics.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/generics.ts#L18-L29). The `finally` clause ensures cleanup on success, timeout, or exception—eliminating the most common leak vector.

---

## WebSocket Listener Cleanup in [`socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/socket.ts)

Temporary WebSocket listeners must be explicitly removed. In [`src/Socket/socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/socket.ts), Baileys attaches per‑message response listeners and removes them immediately after firing or upon error.

```typescript
// src/Socket/socket.ts (lines 207-210)
const onReply = (node: BinaryNode) => {
  ws.off('CB:iq,type:result', onReply)  // cleanup before handling
  // ... process node ...
}
ws.on('CB:iq,type:result', onReply)

```

Lines 207‑210 of [[`src/Socket/socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/socket.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/socket.ts#L207-L210) show this pattern. Permanent protocol listeners registered in [`messages-recv.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/messages-recv.ts) (lines 1999‑2018) are only removed when the socket itself tears down.

---

## Common Pitfalls That Cause Memory Leaks

Avoid these patterns that guaranteed leak references to socket state:

- **Orphaned `ev.on` calls without matching `ev.off`** — The emitter holds the callback and all captured variables indefinitely.
- **Anonymous arrow functions passed directly to `ev.on`** — No reference exists to pass to `ev.off` later. Store the function first.
- **Missing cleanup on WebSocket errors** — Listeners added during a session must be removed in `close`/`error` handlers.
- **Custom `once` implementations without removal** — Baileys does not expose `ev.once`; wrappers must manually call `ev.off` after firing.

---

## Recommended Cleanup Practices

### Use `bindWaitForEvent` for Single-Shot Waits

When waiting for one occurrence of an event, use the built‑in helper instead of manual listener management.

```typescript
import { bindWaitForEvent } from '../Utils/generics'

export async function waitForMessageFrom(
  ev: BaileysEventEmitter,
  jid: string,
  timeoutMs = 15_000
) {
  const waitForMessage = bindWaitForEvent(ev, 'messages.upsert')
  
  await waitForMessage(
    async ([msg]) => msg.key?.remoteJid === jid,
    timeoutMs
  )
  // Automatic cleanup—no ev.off needed
}

```

The helper removes both the target listener and the connection‑state listener in all code paths.

### Keep References for Manual Cleanup

When manual registration is required, store the function and remove it in a `finally` block.

```typescript
const handler = (update: ConnectionState) => {
  // ... handle update ...
}

ev.on('connection.update', handler)

try {
  // ... long-running operation ...
} finally {
  ev.off('connection.update', handler)  // guaranteed cleanup
}

```

### Clean Up Temporary WebSocket Listeners Immediately

Pair every `ws.on` with `ws.off` in the same async scope.

```typescript
async function requestSomeIq(sock: Socket) {
  return new Promise((resolve, reject) => {
    const onReply = (node: BinaryNode) => {
      sock.ws.off('CB:iq,type:result', onReply)  // remove first
      resolve(node)
    }

    sock.ws.on('CB:iq,type:result', onReply)
    sock.ws.send(/* ... iq stanza ... */)
  })
}

```

The listener is detached as soon as the response arrives, preventing lingering references.

### Teardown Long-Running Services Properly

Before closing a socket, remove all user‑registered listeners.

```typescript
async function gracefulShutdown(sock: Socket) {
  sock.ev.off('messages.upsert', myMessageHandler)
  sock.ev.off('connection.update', myConnHandler)
  sock.ev.off('creds.update', myCredsHandler)
  
  await sock.end()  // internal cleanup of ws listeners
}

```

All user‑added listeners must be removed before `sock.end()` to ensure no callbacks survive socket destruction.

### Follow the Test Cleanup Pattern

The Baileys test suite uses a helper that returns cleanup functions. See lines 263‑280 of [[`src/__tests__/e2e/helpers/test-client.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/__tests__/e2e/helpers/test-client.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/__tests__/e2e/helpers/test-client.ts#L263-L280):

```typescript
return () => this.sock.ev.off(event, handler)

```

This pattern allows tests to register listeners and automatically clean them up during teardown.

---

## Key Source Files for Reference

| File | Relevance |
|------|-----------|
| [[`src/Utils/generics.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/generics.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Utils/generics.ts#L5-L31) | `bindWaitForEvent` implementation with guaranteed cleanup |
| [[`src/Socket/socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/socket.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/socket.ts#L207-L210) | WebSocket listener registration and removal patterns |
| [[`src/Socket/messages-recv.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-recv.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/Socket/messages-recv.ts#L1999-L2018) | Permanent `CB:*` protocol listeners |
| [[`src/__tests__/e2e/helpers/test-client.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/__tests__/e2e/helpers/test-client.ts)](https://github.com/WhiskeySockets/Baileys/blob/master/src/__tests__/e2e/helpers/test-client.ts#L263-L280) | Test‑side cleanup pattern returning `ev.off` calls |

---

## Summary

- **Always pair `ev.on()` with `ev.off()`** using stored function references
- **Use `bindWaitForEvent`** from [`generics.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/generics.ts) for temporary event waits—it cleans up automatically
- **Remove WebSocket listeners immediately** after response or error in [`socket.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/socket.ts) patterns
- **Implement `finally` block cleanup** to guarantee listener removal on all code paths
- **Detach user listeners before `sock.end()`** when shutting down long‑running services

---

## Frequently Asked Questions

### What causes memory leaks in Baileys applications?

Memory leaks occur when event listeners remain attached to the `BaileysEventEmitter` or WebSocket after they are no longer needed. These listeners hold references to the socket instance, authentication credentials, and any variables captured in closures, preventing the garbage collector from reclaiming memory.

### Does Baileys provide a `once` method for one‑time listeners?

No. The `BaileysEventEmitter` does not expose a native `once` method. Use the `bindWaitForEvent` helper from [`src/Utils/generics.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/generics.ts) instead—it handles single‑event waiting with automatic cleanup, or implement your own wrapper that calls `ev.off` immediately after the listener fires.

### How do I clean up listeners when my application shuts down?

Call `ev.off(event, handler)` for every event you registered, then await `sock.end()` to close the underlying WebSocket. The internal `end()` implementation removes its own protocol listeners, but user‑registered listeners remain your responsibility.

### What is the difference between `ev` and `ws` listeners in Baileys?

`ev` (the `BaileysEventEmitter`) broadcasts high‑level events like `messages.upsert` and `connection.update`. `ws` (the WebSocket) handles low‑level protocol tags like `CB:iq`. Both require explicit cleanup with `off()`—`ev.off()` for emitter events and `ws.off()` for WebSocket events.