# Private Fields, Getters and Setters

A private field keeps an object's data out of reach of other code, a getter lets that code read it, a setter checks a new value before storing it, and static members belong to the class itself.

Journeyman · JavaScript · OOP · Updated November 1, 2026

## After this lesson you can

- Keep an object's data in a private field and read it through a getter
- Write a setter that checks a value before it is stored
- Use a static method or property that belongs to the class, not to each object

## Key takeaways

- A private field's name starts with a hash sign, as in this.#shields. Only code inside the class can read or change it.
- A getter, get shields() { ... }, is read like a property, with no parentheses. With no setter beside it, code outside can read the value but not change it.
- A setter, set fuel(amount) { ... }, runs when code assigns a value, so it can check the value and refuse a bad one.
- A getter can also work a value out from others, such as canFly from the shields and the fuel.
- A static member belongs to the class itself, not to each instance. You read it on the class, as in Ship.MAX_FUEL.

In a game, a player who can type `comet.shields = 99999` in the browser's console has made themselves unbeatable. In a banking app, code that sets an account's balance to `-500` by mistake is far worse. Real classes protect their data: some values can only be read, and new values get checked before they're stored. You'll use this in every class that holds something that matters, and in this course's *Virtual Pet* project, whose hunger, energy and happiness can't go out of range.

## The problem: anyone can change anything

The `Ship` class from *Classes: Blueprints for Objects* stores its shields as an ordinary property, so any code anywhere can set them:

```javascript
class Ship {
  constructor(name, shields) {
    this.name = name;
    this.shields = shields;
  }
}

const comet = new Ship("Comet", 100);
comet.shields = 99999;
console.log(comet.shields);
```

```text
99999
```

Nothing stops it. The ship's `takeHit` method can be as careful as you like; code that skips it and writes the property directly undoes all that care.

## Private fields, written with \`#\`

Think of a bank. You can see your balance in the app, but you can't type in a new one: only the bank's own systems change it, by the rules (a deposit, a withdrawal).

A class can keep data private the same way. A field whose name starts with **`#`** (read "hash") is a **private field**: only code inside the class can see it. You declare it at the top of the class, then use it as `this.#shields`:

```javascript
class Ship {
  #shields;

  constructor(name, shields) {
    this.name = name;
    this.#shields = shields;
  }

  takeHit(damage) {
    this.#shields = Math.max(0, this.#shields - damage);
    return this.#shields;
  }
}

const comet = new Ship("Comet", 100);
console.log(comet.takeHit(30));
console.log(comet.shields);
console.log(comet);
```

```text
70
undefined
Ship { name: 'Comet' }
```

`takeHit` can use `#shields`, because it's inside the class. Outside, `comet.shields` is `undefined` (that's a different, missing property), and Node's printout doesn't even show the private field. Writing `comet.#shields` outside the class doesn't run at all: JavaScript refuses the whole file with *SyntaxError: Private field '#shields' must be declared in an enclosing class*.

Keeping an object's data behind its own methods like this has a name: **encapsulation**. In your textbook it's one of the main ideas of object-oriented programming.

## Getters: read it, but don't touch it

Now Comet's shields are safe, but the game's screen can't show them. A **getter** lets other code read a value, while the class decides what it gets. You write `get`, a name and a body that returns the value. Other code reads it like a property, **with no parentheses**:

```javascript
class Ship {
  #shields;

  constructor(name, shields) {
    this.name = name;
    this.#shields = shields;
  }

  get shields() {
    return this.#shields;
  }

  takeHit(damage) {
    this.#shields = Math.max(0, this.#shields - damage);
  }
}

const comet = new Ship("Comet", 100);
comet.takeHit(30);
console.log(comet.shields);

comet.shields = 99999;
console.log(comet.shields);
```

```text
70
70
```

`comet.shields` runs the getter and reads 70. And `comet.shields = 99999` changes nothing, because there's a getter and no setter. In a plain `.js` file Node ignores the line without a word; inside a class or a module file it stops with `TypeError: Cannot set property shields of #<Ship> which has only a getter`. Either way, the shields only change through `takeHit`.

**Try it.** Make `shields.js` with the class above. Add a line that tries `console.log(comet.#shields);` at the end and run it: read the error, then delete that line.

## Setters: check a value before it's stored

Sometimes other code *should* be able to change a value, as long as it's a sensible one. Fuel can be topped up, but a tank holds 0 to 100 units, never `-20`.

A **setter** runs when code assigns a value. You write `set`, a name and one parameter, the new value. The setter decides whether to store it:

```javascript
class Ship {
  #fuel = 0;

  constructor(name) {
    this.name = name;
  }

  get fuel() {
    return this.#fuel;
  }

  set fuel(amount) {
    if (amount < 0 || amount > 100) {
      console.log(`${this.name}: fuel must be from 0 to 100, not ${amount}.`);
      return;
    }
    this.#fuel = amount;
  }
}

const vega = new Ship("Vega");
vega.fuel = 80;
console.log(vega.fuel);

vega.fuel = -20;
console.log(vega.fuel);
```

```text
80
Vega: fuel must be from 0 to 100, not -20.
80
```

`vega.fuel = 80` looks like an ordinary assignment, but it runs the setter, which stores 80. `vega.fuel = -20` runs the setter too, and the setter refuses: the fuel stays at 80. A getter and a setter usually come as a pair with the same name, around a private field. `#fuel = 0` also shows that a private field can get a starting value where it's declared.

Here the setter refuses by printing a message and leaving the value alone. In *Throwing Your Own Errors*, in the next chapter, you'll learn a stronger way to refuse: stopping the program with an error that says what went wrong.

## A getter can work a value out

A getter doesn't have to return a stored field. It can work out an answer from other fields every time it's read. A ship can fly while it has shields and fuel left:

```javascript
class Ship {
  #shields;
  #fuel;

  constructor(name, shields, fuel) {
    this.name = name;
    this.#shields = shields;
    this.#fuel = fuel;
  }

  get canFly() {
    return this.#shields > 0 && this.#fuel > 0;
  }
}

const comet = new Ship("Comet", 70, 40);
const orion = new Ship("Orion", 0, 90);

console.log(comet.canFly);
console.log(orion.canFly);
```

```text
true
false
```

`canFly` is never stored, so it can never be out of date: it's worked out fresh each time.

## static: belongs to the class

Some values aren't about any one ship. The size of a fuel tank is the same for the whole fleet; a count of how many ships were built is about the class, not about Comet.

A member marked **`static`** belongs to the class itself. You read it on the class name, `Ship.MAX_FUEL`, not on an instance. A **static method** is a function you call on the class the same way:

```javascript
class Ship {
  static MAX_FUEL = 100;
  static count = 0;

  constructor(name) {
    this.name = name;
    Ship.count = Ship.count + 1;
  }

  static fullTank() {
    return `A full tank is ${Ship.MAX_FUEL} units.`;
  }
}

const comet = new Ship("Comet");
const vega = new Ship("Vega");

console.log(Ship.count);
console.log(Ship.fullTank());
console.log(comet.count);
```

```text
2
A full tank is 100 units.
undefined
```

There's one `Ship.count` for the whole class, and each constructor adds 1 to it. `comet.count` is `undefined`, because the count isn't on the instances. You've used static members before: `Math.max` and `Object.keys` are called on `Math` and `Object` themselves, never on an instance.

**Try it.** In `shields.js`, add `static MAX_SHIELDS = 150;` to the class, and change `takeHit` so a negative damage (a repair) can never lift the shields above `Ship.MAX_SHIELDS`.

## Check yourself

2 questions to try on the lesson page.

## So what

Private fields, getters and setters are how a class keeps its promises: shields only drop through `takeHit`, fuel is never negative, and nobody types in 99999. The more code (and the more people) use your class, the more those promises are worth.

## Key terms

- [Private Field](https://www.ask-codey.com/glossary/private-field)
- [Getter](https://www.ask-codey.com/glossary/getter-javascript)
- [Setter](https://www.ask-codey.com/glossary/setter-javascript)
- [Encapsulation](https://www.ask-codey.com/glossary/encapsulation-javascript)
- [Static Method](https://www.ask-codey.com/glossary/static-method)

## Cheat sheets

- [Private Fields, Getters and Setters](https://www.ask-codey.com/cheat-sheets/javascript-classes-private-fields-getters-setters-cheat-sheet)

## Related

- [Classes: Blueprints for Objects](https://www.ask-codey.com/lessons/javascript-classes-blueprints)
- [Inheritance with extends and super](https://www.ask-codey.com/lessons/javascript-classes-extends-and-super)

Source: https://www.ask-codey.com/lessons/javascript-classes-private-fields-getters-setters
