How to use WeakSet in JavaScript

WeakSet is a collection of objects that holds weak references, allowing garbage collection when objects are no longer needed elsewhere. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve used WeakSet in large-scale applications to track visited nodes, marked elements, and processed items without causing memory leaks.

The most practical approach uses WeakSet to track objects without preventing their garbage collection.

Create and Use WeakSet

const visitedNodes = new WeakSet()

const node1 = { id: 1, name: 'Node 1' }
const node2 = { id: 2, name: 'Node 2' }

// Add objects
visitedNodes.add(node1)
visitedNodes.add(node2)

// Check if exists
console.log(visitedNodes.has(node1)) // true
console.log(visitedNodes.has(node2)) // true

// Remove object
visitedNodes.delete(node1)
console.log(visitedNodes.has(node1)) // false

Track Processed DOM Elements

const processedElements = new WeakSet()

function processElement(element) {
  if (processedElements.has(element)) {
    console.log('Already processed')
    return
  }

  // Process element
  element.classList.add('processed')

  // Mark as processed
  processedElements.add(element)
}

const button = document.querySelector('button')
processElement(button) // Processes
processElement(button) // Already processed

Prevent Circular Reference Issues

function traverse(obj, visited = new WeakSet()) {
  // Prevent infinite loops
  if (visited.has(obj)) {
    return
  }

  visited.add(obj)

  // Traverse object properties
  for (const key in obj) {
    if (obj[key] && typeof obj[key] === 'object') {
      traverse(obj[key], visited)
    }
  }
}

// Circular reference
const a = { name: 'A' }
const b = { name: 'B' }
a.child = b
b.parent = a

traverse(a) // Won't cause infinite loop

Track Event Listeners

const attachedListeners = new WeakSet()

function attachListener(element, event, handler) {
  if (attachedListeners.has(element)) {
    console.log('Listener already attached')
    return
  }

  element.addEventListener(event, handler)
  attachedListeners.add(element)
}

const btn = document.querySelector('.submit')
const handler = () => console.log('Clicked')

attachListener(btn, 'click', handler) // Attaches
attachListener(btn, 'click', handler) // Already attached

Mark Objects in Processing Pipeline

class DataProcessor {
  constructor() {
    this.processing = new WeakSet()
    this.processed = new WeakSet()
  }

  async process(data) {
    if (this.processing.has(data)) {
      throw new Error('Already processing this data')
    }

    if (this.processed.has(data)) {
      console.log('Already processed')
      return
    }

    this.processing.add(data)

    try {
      // Simulate async processing
      await this.performWork(data)

      this.processed.add(data)
    } finally {
      this.processing.delete(data)
    }
  }

  async performWork(data) {
    return new Promise(resolve => {
      setTimeout(() => {
        console.log('Processed:', data.id)
        resolve()
      }, 100)
    })
  }
}

const processor = new DataProcessor()
const item = { id: 1, value: 'test' }

processor.process(item)
processor.process(item) // Error: Already processing

Automatic Garbage Collection

const trackedObjects = new WeakSet()

function createAndTrack() {
  const obj = { data: 'temporary' }
  trackedObjects.add(obj)
  return obj
}

// Object is tracked
let temp = createAndTrack()
console.log(trackedObjects.has(temp)) // true

// Object can be garbage collected when no longer referenced
temp = null
// trackedObjects doesn't prevent garbage collection

// Compare with regular Set
const regularSet = new Set()

function createAndTrackStrong() {
  const obj = { data: 'permanent' }
  regularSet.add(obj)
  return obj
}

let temp2 = createAndTrackStrong()
temp2 = null
// Object stays in memory because Set holds strong reference

WeakSet vs Set Comparison

// WeakSet - only objects, weak references
const weakSet = new WeakSet()
const obj1 = { id: 1 }
weakSet.add(obj1)

// Can't add primitives
// weakSet.add('string') // TypeError
// weakSet.add(123) // TypeError

// Can't iterate
// for (const item of weakSet) {} // TypeError
// weakSet.forEach() // TypeError

// Can't get size
// console.log(weakSet.size) // undefined

// Regular Set - any values, strong references
const regularSet = new Set()
regularSet.add('string')
regularSet.add(123)
regularSet.add(obj1)

// Can iterate
for (const item of regularSet) {
  console.log(item)
}

console.log(regularSet.size) // 3

Use Cases Summary

// 1. Track visited nodes in graph traversal
const visitedNodes = new WeakSet()

// 2. Mark processed DOM elements
const processedElements = new WeakSet()

// 3. Prevent duplicate event listeners
const attachedListeners = new WeakSet()

// 4. Cache computed results for objects
const cachedResults = new WeakSet()

// 5. Track objects in async operations
const pendingOperations = new WeakSet()

// When to use WeakSet:
// ✓ Need to track objects temporarily
// ✓ Want automatic cleanup via garbage collection
// ✓ Don't need to iterate over collection
// ✓ Objects are the primary reference

// When NOT to use WeakSet:
// ✗ Need to store primitives (use Set)
// ✗ Need to iterate over items (use Set)
// ✗ Need to know collection size (use Set)
// ✗ Need to serialize data (use Array/Set)

Best Practice Note

This is how we use WeakSet in CoreUI components for memory-efficient object tracking. WeakSet prevents memory leaks by allowing garbage collection of objects that are no longer referenced elsewhere in your application. Always use WeakSet when you need to track objects temporarily without preventing their cleanup, and use regular Set when you need iteration, size information, or to store primitive values.

For related JavaScript collections, check out how to use WeakMap for private data in JavaScript and how to use Set in JavaScript.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author