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.
Related Articles
For related JavaScript collections, check out how to use WeakMap for private data in JavaScript and how to use Set in JavaScript.



