why does the garbage collector is not cleaning up connections when we open new connection before closing the previous connection?

# Production Postmortem: Unreleased WebSocket Connections Preventing Garbage 



## 

A JavaScript WebSocket implementation was leaking connections because new connections were created without closing existing ones. Overwriting the reference to the old connection did not automatically trigger garbage collection or socket cleanup, leading to resource exhaustion.



## Root 

- The `ws` variable was reassigned to a new WebSocket instance without explicitly closing the previous 

- Open WebSocket connections maintain internal references to event handlers (`onopen`, `onmessage`, `onclose`)

- Browser resources retain active sockets until manually 

- Previous connection instances remained active despite losing JavaScript 



## Why This Happens in Real 

- Connections retain state (networking buffers, OS sockets, event listeners)

- Browser garbage collection cannot reclaim objects actively interfacing with system 

- Degree of garbage collector aggression varies by browser/JavaScript 

- Event listeners create implicit references to connection 

- Active connections prevent socket reuse due to TCP/IP 



## Real-World 

- Socket exhaustion blocking new network 

- Memory leaks accumulating per abandoned 

- Resource starvation impacting entire browser tab 

- Increased server load from zombie 

- Gradual performance degradation leading to tab 



## Example

const openButton = document.getElementById(“openConn”);

const closeButton = document.getElementById(“closeConn”);

let connectionCount = 0;

let activeConnection = null;

openButton.onclick = () => {

// Leak occurs here – previous connection not

activeConnection = new WebSocket(“ws://localhost:1234”);

connectionCount++;

activeConnection.onopen = () => console.log(Connection ${connectionCount} opened);

};

closeButton.onclick = () => {

if (activeConnection) activeConnection.close();

};

## How Senior Engineers Fix 

1. **Explicit connection lifecycle management**: Always close before 

2. **Connection reuse patterns**: Singleton pattern for single persistent 

3. **Reference cleanup**: Remove event listeners before overwriting 

4. **Error handling**: Add `onerror` handlers to catch failed 

5. **Prevention patterns**: Implement connection state checks before 

6. **Pooling**: Maintain connection pool with scope-based 

7. **Memory monitoring**: Profile heap/socket usage during 



Fixed implementation:

openButton.onclick = () => {

if (activeConnection) {

activeConnection.close(); // Close existing connection

activeConnection = null;

}

activeConnection = new WebSocket(“ws://localhost:1234”);

connectionCount++;

activeConnection.onopen = () => console.log(Connection ${connectionCount} opened);

activeConnection.onclose = () => console.log(Connection ${connectionCount} closed);

};

## Why Juniors Miss 

- Misunderstanding GC behavior: Expecting unreferenced objects to immediately release 

- Overlooking resource ownership: Not recognizing that sockets require explicit 

- Limited debugging visibility: Console logs show generic messages, not connection 

- Event handler unawareness: Missing how callbacks anchor objects in 

- Framework over-reliance: Assuming libraries automatically handle resource 

- Protocol misunderstandings: Thinking WebSocket closes like HTTP request 

- Scope misconceptions: Believing variable reassignment severs connection