Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Classes  ›  Capstone Project

Private Fields

Private Fields 12 minA field declared with a # prefix (#balance) is only accessible from methods declared inside that same class — reading it from outside is a SyntaxError, and it's invisible to Object.keys, JSON.stringify and bracket-string access, unlike a merely-conventional underscore prefix.

Private Fields

In short: a field name starting with # (#balance) is genuinely private — it can only be read or written from inside the class that declares it. Code outside the class can't reach it even by trying: obj.#balance outside the class body isn't just blocked, it's a SyntaxError, because # names aren't valid to even write outside a class that declares them. This is stronger than the old convention of naming a field _balance with a leading underscore — that's just a hint to other programmers, easily ignored; #balance is enforced by the language itself.

An Account instance with a public get balance() door and a locked #balance drawer that only methods declared IN this class can open — a.balance works via the getter, but a.#balance outside the class is a SyntaxError, and Object.keys/JSON.stringify skip #fields entirely, truly hidden, not just a naming convention

Read and update a balance through public methods only

The #balance field itself is never touched from outside — every interaction goes through ordinary public methods that happen to read or write it internally:

javascript

a.balance prints 150 — but that's the get balance() method running, not direct access to #balance. From outside, #balance simply doesn't exist as something you can name.

Confirm a private field is invisible everywhere

Unlike a regular property, a #field doesn't show up in any of the usual ways of listing or serialising an object's properties:

javascript

Object.keys(a) is [], JSON.stringify(a) is "{}", and even a["#balance"] — trying to sneak in through bracket notation with the literal string "#balance" — gives undefined. There is no way to reach a #field from outside its class, by any route.

The trap: assuming an underscore is the same thing

A leading underscore, this._balance, is only a convention — a polite signal that says "please don't touch this directly." JavaScript does nothing to stop anyone from ignoring it:

class OldStyleAccount {
  constructor(balance) {
    this._balance = balance; // convention only — NOT enforced
  }
}
const a = new OldStyleAccount(100);
a._balance = 999999; // works, silently, from anywhere

Nothing prevents that assignment — _balance is a completely ordinary, public property that merely looks private by agreement. #balance closes that gap entirely: there's no syntax that lets outside code reach it, on purpose or by accident.

Quick practice

quick drill

What happens when code outside the class tries to write a.#balance?

quick drill

Does Object.keys(accountInstance) include private #fields?

quick drill

How does this._balance (underscore) differ from this.#balance (private field)?

quick drill

Given a public get balance() method that internally reads this.#balance, what does calling a.balance from outside actually do?

quick drill

What does a["#balance"] return, trying to access a private field through bracket notation from outside?

🎯 Your turn

Given class Account { #balance; constructor(balance) { this.#balance = balance; } deposit(amount) { this.#balance += amount; } get balance() { return this.#balance; } }, explain why a.balance works from outside the class while a.#balance does not, and why this is a stronger guarantee than naming a regular field _balance by convention.

Write your explanation and submit it — there's no code to run here, just your reasoning.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainTruly hidden, not just a convention

Given class Account { #balance; constructor(balance) { this.#balance = balance; } deposit(amount) { this.#balance += amount; } get balance() { return this.#balance; } }, explain why a.balance works from outside the class while a.#balance does not, and why this is a stronger guarantee than naming a regular field _balance by convention.