How to use WeakMap for private data in JavaScript

WeakMap provides true private data storage in JavaScript without memory leaks, as entries are automatically garbage collected when objects are no longer referenced. As the creator of CoreUI with 26 years of JavaScript development experience, I’ve used WeakMap for private instance data in production libraries serving millions of users.

The most effective approach stores private data in a WeakMap keyed by the object instance.

Direct Answer

Use WeakMap to store private instance data:

const privateData = new WeakMap()

class User {
  constructor(name, password) {
    privateData.set(this, { password })
    this.name = name
  }

  checkPassword(input) {
    return privateData.get(this).password === input
  }
}

const user = new User('John', 'secret123')
console.log(user.name) // 'John'
console.log(user.password) // undefined
console.log(user.checkPassword('secret123')) // true

Multiple Private Properties

const privateProps = new WeakMap()

class BankAccount {
  constructor(owner, balance) {
    privateProps.set(this, {
      balance,
      transactions: []
    })
    this.owner = owner
  }

  deposit(amount) {
    const data = privateProps.get(this)
    data.balance += amount
    data.transactions.push({ type: 'deposit', amount })
  }

  withdraw(amount) {
    const data = privateProps.get(this)
    if (data.balance >= amount) {
      data.balance -= amount
      data.transactions.push({ type: 'withdraw', amount })
      return true
    }
    return false
  }

  getBalance() {
    return privateProps.get(this).balance
  }
}

Private Methods

const privateMethods = new WeakMap()

class Calculator {
  constructor() {
    privateMethods.set(this, {
      validate: (num) => {
        if (typeof num !== 'number') {
          throw new Error('Not a number')
        }
      }
    })
  }

  add(a, b) {
    const methods = privateMethods.get(this)
    methods.validate(a)
    methods.validate(b)
    return a + b
  }
}

Factory Pattern

const createCounter = () => {
  const privateData = new WeakMap()

  class Counter {
    constructor(initial = 0) {
      privateData.set(this, { count: initial })
    }

    increment() {
      const data = privateData.get(this)
      data.count++
    }

    decrement() {
      const data = privateData.get(this)
      data.count--
    }

    getValue() {
      return privateData.get(this).count
    }
  }

  return Counter
}

const Counter = createCounter()
const counter = new Counter(10)
counter.increment()
console.log(counter.getValue()) // 11

Why WeakMap vs Symbol

// Symbol approach (not truly private)
const _password = Symbol('password')

class User {
  constructor(password) {
    this[_password] = password
  }
}

const user = new User('secret')
console.log(Object.getOwnPropertySymbols(user)) // Can still access!

// WeakMap approach (truly private)
const privateData = new WeakMap()

class SecureUser {
  constructor(password) {
    privateData.set(this, { password })
  }
}

const secureUser = new SecureUser('secret')
// No way to access password from outside!

Memory Management

const privateData = new WeakMap()

class Component {
  constructor(id) {
    privateData.set(this, { id, data: new Array(1000000) })
  }
}

let component = new Component(1)
console.log(privateData.has(component)) // true

component = null
// Private data is automatically garbage collected!

Best Practice Note

This is the same WeakMap pattern we use in CoreUI for private component state without memory leaks. Unlike regular objects or Maps, WeakMap entries are automatically garbage collected when the key object is no longer referenced, preventing memory leaks in long-running applications.

For related encapsulation patterns, check out how to implement module pattern in JavaScript and how to create immutable objects 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