Private Fields
This lesson explains Private Fields with clear examples, use cases, and best practices so you can use modern JavaScript confidently in real projects.
ES6+ Private Fields Overview
Private fields let you declare class properties that are only
accessible inside the class body. They use the #
prefix syntax and provide true encapsulation in modern JavaScript
without relying on naming conventions or closures.
Private fields are useful for hiding internal state, protecting
sensitive values, and keeping public APIs clean while still using
ES6 class syntax.
| Feature | Description |
| Introduced In | ES2022 |
| Syntax | #fieldName |
| Access | Only inside the declaring class |
| Types | Instance fields, static fields, methods |
| Outside Access | Not accessible on instances or subclasses |
| Common Uses | Internal state, secrets, cache, validation |
Basic Private Field Example
class BankAccount {
#balance = 0;
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
withdraw(amount) {
if (
amount > 0 &&
amount <= this.#balance
) {
this.#balance -= amount;
}
}
getBalance() {
return this.#balance;
}
}
const account =
new BankAccount();
account.deposit(500);
account.withdraw(120);
console.log(
account.getBalance()
);
The #balance field is hidden from outside the class.
Users interact through public methods instead of changing internal
state directly.
How Private Fields Work
Private fields are declared with # at the class level.
They can be read and written only from methods inside the same
class declaration.
| Step | Description | Example |
| Declare Field | Add a # prefixed property. | #count = 0 |
| Use Inside Class | Access with this.#count. | this.#count++ |
| Expose Safely | Provide public methods or getters. | getCount() |
| Block Outside Access | External code cannot read the field. | obj.#count fails |
| Protect State | Keep implementation details private. | Encapsulation |
Private Fields in the Constructor
class User {
#id;
#email;
constructor(id, email) {
this.#id = id;
this.#email = email;
}
getProfile() {
return {
id: this.#id,
email: this.#email
};
}
}
const user =
new User(1, "alex@example.com");
console.log(
user.getProfile()
);
Private fields can be declared without an initial value and assigned
inside the constructor, similar to public properties.
Private Fields with Getters and Setters
class Product {
#price = 0;
get price() {
return this.#price;
}
set price(value) {
if (value < 0) {
throw new Error(
"Price cannot be negative"
);
}
this.#price = value;
}
}
const product =
new Product();
product.price = 49;
console.log(product.price);
A common pattern is to keep the real value in a private field while
exposing controlled access through getters and setters.
Private Methods
class PasswordManager {
#hash(value) {
return `hashed-${value}`;
}
#validate(password) {
return password.length >= 8;
}
save(password) {
if (!this.#validate(password)) {
throw new Error(
"Password too short"
);
}
return this.#hash(password);
}
}
const manager =
new PasswordManager();
console.log(
manager.save("securePass123")
);
Private methods use the same # syntax. They are useful
for helper logic that should not be part of the public class API.
Private Static Fields
class AppConfig {
static #instanceCount = 0;
constructor(appName) {
this.appName = appName;
AppConfig.#instanceCount += 1;
}
static getInstanceCount() {
return AppConfig.#instanceCount;
}
}
new AppConfig("Dashboard");
new AppConfig("Portal");
console.log(
AppConfig.getInstanceCount()
);
Static private fields belong to the class itself, not to individual
instances. Access them with ClassName.#field inside the
class body.
Outside Access Is Not Allowed
class Counter {
#value = 0;
increment() {
this.#value += 1;
}
getValue() {
return this.#value;
}
}
const counter =
new Counter();
counter.increment();
console.log(counter.getValue());
// This causes a syntax error:
// console.log(counter.#value);
Trying to access #value outside the class results in
a syntax error. Private fields are enforced by the language, not by
convention alone.
Private Fields and Inheritance
class Animal {
#energy = 100;
eat(amount) {
this.#energy += amount;
}
getEnergy() {
return this.#energy;
}
}
class Dog extends Animal {
bark() {
// Cannot access #energy here
return "Woof!";
}
}
const dog = new Dog();
dog.eat(20);
console.log(dog.getEnergy());
console.log(dog.bark());
Private fields are not inherited by subclasses. A child class cannot
access a parent's private fields directly, which keeps
encapsulation boundaries strict.
Private Fields vs Older Patterns
// Old pattern: naming convention
class OldUser {
constructor(name) {
this._name = name;
}
}
// Old pattern: WeakMap
const names = new WeakMap();
class WeakUser {
constructor(name) {
names.set(this, name);
}
getName() {
return names.get(this);
}
}
// Modern pattern: private field
class ModernUser {
#name;
constructor(name) {
this.#name = name;
}
getName() {
return this.#name;
}
}
Before private fields, developers used underscore prefixes or
WeakMap to simulate privacy. Native private fields are
cleaner and enforced by JavaScript itself.
Public Properties vs Private Fields
| Feature | Public Property | Private Field |
| Syntax | this.name | this.#name |
| Outside Access | Allowed | Not allowed |
| Enforcement | By convention only | Language enforced |
| Best For | Public API data | Internal implementation state |
Common Private Field Use Cases
- Hiding internal counters, caches, or buffers.
- Protecting sensitive values such as tokens or credentials.
- Keeping validation logic inside private helper methods.
- Exposing only safe public methods on domain classes.
- Preventing direct mutation of internal object state.
- Building reusable class libraries with clear APIs.
- Tracking static class-level private metadata.
Private Field Best Practices
- Use private fields for internal state that should never leak.
- Expose data through intentional public methods or getters.
- Keep private helper methods small and focused.
- Do not use private fields for every property by default.
- Name private fields clearly, such as
#balance or #cache. - Validate input in setters before writing to private fields.
- Check browser or runtime support for ES2022 class features.
Common Private Field Mistakes
- Trying to access
#field from outside the class. - Expecting subclasses to inherit private parent fields.
- Using private fields without declaring them in the class body.
- Confusing private fields with public properties.
- Hiding everything privately when a simple public API would be clearer.
- Forgetting that private fields require modern JavaScript support.
- Mixing old underscore conventions with real private fields inconsistently.
Key Takeaways
- Private fields use the
# prefix inside ES6 classes. - They are accessible only within the declaring class body.
- Private methods and static private fields follow the same rules.
- They provide real encapsulation instead of naming conventions.
- Use them to protect internal state and keep public APIs clean.
Pro Tip
Keep sensitive or mutable state in private fields and expose only
what callers need through methods like
deposit(), getBalance(), or controlled
getters and setters.