Skip to content

Private Fields and Static Members

Two features round out modern class syntax: private fields for encapsulating internal data, and static members for attaching properties and methods to the class itself.

By default, every property on a class instance is public — any code that has a reference to the instance can read or write it. Sometimes you want to protect internal state from outside interference.

Private fields are declared with a # prefix. They can only be accessed from inside the class body:

class Expense {
#id;
#createdAt;
constructor({ description, amount, category, date }) {
this.#id = crypto.randomUUID();
this.#createdAt = Date.now();
this.description = description.trim();
this.amount = amount;
this.category = category ?? 'Uncategorized';
this.date = date ?? new Date().toISOString().split('T')[0];
}
get id() {
return this.#id;
}
get createdAt() {
return this.#createdAt;
}
}
const coffee = new Expense({ description: 'Coffee', amount: 4.50, category: 'Food' });
console.log(coffee.id); // the UUID — accessible via getter
console.log(coffee.#id); // SyntaxError — private field, inaccessible from outside

#id cannot be read or written from outside the class. The only way to access it is through the id getter, which you control.

A getter is a method that is accessed like a property — no parentheses needed:

get id() {
return this.#id;
}
console.log(coffee.id); // called like a property, not coffee.id()

Getters are useful for computed or read-only properties. You can also define setters that run when a property is assigned:

set amount(value) {
if (typeof value !== 'number' || value <= 0) {
throw new Error('Amount must be a positive number');
}
this.amount = value;
}

A static property belongs to the class, not to instances. It is useful for constants and class-level configuration:

class Expense {
static CATEGORIES = ['Food', 'Transport', 'Health', 'Entertainment', 'Other'];
// ...
}
console.log(Expense.CATEGORIES); // ['Food', 'Transport', ...]
console.log(coffee.CATEGORIES); // undefined — not on instances

Expense.CATEGORIES is available anywhere you have access to the Expense class — useful for populating a category dropdown or validating input.

You saw fromJSON as a static method in lesson 03. Static methods are also useful for utility operations that relate to the class but do not need an instance:

class Expense {
static CATEGORIES = ['Food', 'Transport', 'Health', 'Entertainment', 'Other'];
static isValidCategory(category) {
return Expense.CATEGORIES.includes(category);
}
static fromJSON(data) {
return new Expense(data);
}
}
console.log(Expense.isValidCategory('Food')); // true
console.log(Expense.isValidCategory('Snacks')); // false

Here is the full Expense class you will use in BudgetBuddy, combining everything from this module:

class Expense {
static CATEGORIES = ['Food', 'Transport', 'Health', 'Entertainment', 'Other'];
#id;
#createdAt;
constructor({ id, description, amount, category, date, createdAt } = {}) {
if (!description || description.trim() === '') throw new Error('Description is required');
if (typeof amount !== 'number' || amount <= 0) throw new Error('Amount must be positive');
this.#id = id ?? crypto.randomUUID();
this.#createdAt = createdAt ?? Date.now();
this.description = description.trim();
this.amount = amount;
this.category = category ?? 'Other';
this.date = date ?? new Date().toISOString().split('T')[0];
}
get id() { return this.#id; }
get createdAt() { return this.#createdAt; }
format() {
return `${this.description}: $${this.amount.toFixed(2)}`;
}
isInCategory(category) {
return this.category.toLowerCase() === category.toLowerCase();
}
toJSON() {
return {
id: this.#id,
description: this.description,
amount: this.amount,
category: this.category,
date: this.date,
createdAt: this.#createdAt,
};
}
static fromJSON(data) {
return new Expense(data);
}
static isValidCategory(category) {
return Expense.CATEGORIES.includes(category);
}
}
  1. Add private fields #id and #createdAt to your Expense class. Expose them with get accessors.
  2. Add static CATEGORIES with five category strings.
  3. Add static isValidCategory(category) that checks whether a string is in CATEGORIES.
  4. Confirm that trying to access expense.#id from outside the class throws a SyntaxError.
  5. Confirm that Expense.CATEGORIES is accessible but expense.CATEGORIES is undefined.
  • Private fields (#name) can only be accessed inside the class body — they protect internal state.
  • Getters expose private or computed values as read-only properties.
  • Static properties (static name = value) belong to the class, not instances — useful for constants and configuration.
  • Static methods (static methodName()) attach utility logic to the class itself — factory methods, validators, helpers.