completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Getters & setters

Learn how JavaScript accessor properties run functions on reads and writes, how their descriptors differ from data properties, and how to use them for computed values, validation, caching, and compatibility wrappers.

By the end you can
  • 01
    Write getters and settersSpot get and set in objects, classes, and descriptors, and say which one runs on a read or a write.
  • 02
    Read their descriptorsTell a getter's descriptor (get, set) from a data property's descriptor (value, writable).
  • 03
    Use them safelyBuild getters that work out values, setters that check values, and run-once getters, without a setter that calls itself.

Properties that run code

In the Property flags & descriptors lesson, a property held a stored value. It also had flags, like writable. We call this kind a data property.

JavaScript has a second kind, the accessor property. From the outside, it looks like any other property. But when you read it or write to it, JavaScript runs a function.

One-sentence definition

A getter is a function that runs when code reads a property. A setter is a function that runs when code assigns a new value to that property.

Real-life analogyA getter is like the button on a snack machine

You only see one button. Behind it, a machine does the work each time you press it. A getter is the same. user.fullName looks like plain data, but a small function builds the answer each time you read it.

In real life: Pressing the snack button
In JavaScript: Reading user.fullName
In real life: The machine picks the snack and drops it out
In JavaScript: The getter runs and returns a value
In real life: The snacks stored behind the glass
In JavaScript: The stored data the getter uses, like firstName and lastName

You saw getters and setters in classes in the Class basics lesson. This lesson goes further. You will learn:

  • how to write them in plain objects, not just in classes
  • what their descriptors look like
  • how a setter can check a new value before it keeps it
  • how a getter can work out a value once and save it
  • how to keep an old property name working after you rename it

A getter or setter belongs to one property. A later lesson covers Proxy, a tool that can watch every property of an object at once.

Writing get and set

STEP THROUGH

Inside an object, write get name() for the function that runs on a read. Write set name(value) for the function that runs on a write. The value parameter gets the new value.

Code outside the object never calls them with brackets. It uses normal property code:

  • Reading: user.fullName
  • Writing: user.fullName = "Grace Hopper"

Step through the code below and watch which function runs.

Reading runs the getter, writing runs the setter
Step 0 of 10Ready
Your turn: follow the blue line

Reading fullName runs the getter. Writing to fullName runs the setter with the new value.

Running in
  1. script
Next: line 1
  firstName: "Ada",  lastName: "Lovelace",  get fullName() {    return this.firstName + " " + this.lastName;  },  set fullName(value) {    const parts = value.split(" ");    this.firstName = parts[0];    this.lastName = parts[1];  }}; console.log(user.fullName);user.fullName = "Grace Hopper";console.log(user.firstName);console.log(user.fullName);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Line 14 reads user.fullName. That runs the getter on lines 4 to 6. It joins firstName and lastName and prints Ada Lovelace.

Line 15 writes to user.fullName. That runs the setter on lines 7 to 11. It splits "Grace Hopper" at the space and stores the two parts in firstName and lastName.

So firstName and lastName are data properties. They hold the real text. fullName is the accessor property. It stores nothing itself. Line 17 reads it again, and the getter builds Grace Hopper from the new data.

Left: reading user.fullName runs get fullName(), which gives "Ada Lovelace". Right: user.fullName = "Grace Hopper" runs set fullName(), which stores "Grace" in firstName and "Hopper" in lastName.readuser.fullNameget fullName()"Ada Lovelace"the value you getwriteuser.fullName = "Grace Hopper"set fullName()"Grace""Hopper"firstNamelastName
Reading user.fullName runs the getter, which joins the two names. Writing to it runs the setter, which splits the text into firstName and lastName.

A class can have getters and setters too. Here the real name is kept in a private field, #name. A private field is a property that only code inside the class can use.

A getter and setter in a classPop out in the code editor (opens in a new tab)JavaScript
class User {
  #name;
  constructor(name) {
    this.#name = name;
  }
  get name() {
    return this.#name;
  }
  set name(value) {
    this.#name = value.trim();
  }
}

const user = new User("Asha");
user.name = " Ben ";
console.log(user.name);
console.log("#name" in user);

The getter and setter for name look the same as in an object. JavaScript puts them on the class's prototype, the shared object every User uses. Line 15 runs the setter, which trims the spaces, so line 16 prints Ben.

Line 17 prints false. #name is not a normal property, so code outside the class can't see it or reach it, not even with user["#name"].

Real-life analogyA setter is like a front desk that checks deliveries

Without a setter, a new value goes straight into the property. With a setter, the value stops at a desk first. There the object can trim text, change units, or refuse a value that makes no sense.

In real life: A package arrives at the front desk
In JavaScript: Code runs sensor.celsius = value
In real life: The person at the desk checks the label
In JavaScript: The setter checks the value
In real life: Good packages go to the store room
In JavaScript: The setter stores the value in _celsius
In real life: Wrong packages are sent back
In JavaScript: The setter throws a RangeError

What the descriptor of a getter looks like

VERIFY

A descriptor is a plain object that describes one property. You get it with Object.getOwnPropertyDescriptor. The descriptor shows which kind of property you have:

  • A data property has value and writable.
  • An accessor property has get, set, or both.
  • Both kinds have enumerable and configurable.

Read the table one row at a time. Each row asks a question and answers it for both kinds of property.

Data property or accessor property: what each one does
QuestionData propertyAccessor property
What is in its descriptor?value, writable, enumerable, configurableget, set, enumerable, configurable
What happens when you read it?You get the stored value.get runs, and you get what it returns.
What happens when you write to it?The new value is stored, if writable is true.set(value) runs, if there is a setter.
When should you use it?For plain stored data, like firstName.For values that are worked out, checked, or passed on to another name.
Look at a getter's descriptor, then add a new getterPop out in the code editor (opens in a new tab)JavaScript
const account = {  firstName: "Ada",  lastName: "Lovelace",  get fullName() {    return this.firstName + " " + this.lastName;  }}; const descriptor = Object.getOwnPropertyDescriptor(account, "fullName");console.log(typeof descriptor.get);console.log("value" in descriptor);console.log("writable" in descriptor); Object.defineProperty(account, "initials", {  get() {    return this.firstName[0] + this.lastName[0];  },  enumerable: true,  configurable: true});console.log(account.initials);

Line 9 gets the descriptor of fullName. Line 10 prints function, because the descriptor has a get function. Lines 11 and 12 print false: there is no value and no writable.

Lines 14 to 20 add a new getter, initials, with Object.defineProperty. Its descriptor has a get function and two flags. Line 21 prints AL. One descriptor can't have both get and value. Pick one kind.

JSON includes some getters and skips othersPop out in the code editor (opens in a new tab)JavaScript
const user = {
  first: "Ada",
  last: "Lovelace",
  get fullName() {
    return this.first + " " + this.last;
  }
};

class Profile {
  constructor(name) {
    this.name = name;
  }
  get greeting() {
    return "Hello " + this.name;
  }
}

console.log(JSON.stringify(user));
console.log(JSON.stringify(new Profile("Asha")));

JSON.stringify turns an object into JSON text. It only includes properties that are both:

  • own: on the object itself, not on its prototype
  • enumerable: allowed to show up in lists

The getter in user is on the object itself, so line 18 runs it and includes "fullName":"Ada Lovelace". The class getter greeting is on the prototype, so line 19 leaves it out. Reading profile.greeting by name still works.

One more rule for Object.defineProperty: any flag you leave out is false. So a getter defined without enumerable: true still works when you read it. But it won't show up in Object.keys or in JSON. Set the flags you want on purpose.

Work out values and check new ones

INTERACTIVE

Getters and setters each have one main job:

  • A getter can give a computed value: one worked out from other values, like fullName from two names.
  • A setter can validate a new value: check it before storing it. The code still looks like a plain assignment.
Names and temperature playground
Getters that work out values, and a setter that checks themPop out in the code editor (opens in a new tab)JavaScript
const profile = {  firstName: "Ada",  lastName: "Lovelace",  _celsius: 20,  get fullName() {    return this.firstName + " " + this.lastName;  },  set celsius(value) {    if (value < -273.15) {      throw new RangeError("Temperature cannot go below absolute zero.");    }    this._celsius = value;  },  get fahrenheit() {    return this._celsius * 9 / 5 + 32;  }};
Live objectaccepted
firstNameAda
lastNameLovelace
fullName getterAda Lovelace
_celsius (stored)20°C
fahrenheit getter68°F
Try it yourself

The fullName getter now returns "Ada Lovelace". The fahrenheit getter turns 20°C into 68°F.

Type a name or a temperature. Each box acts like an assignment. A new Celsius value goes through the setter, which checks it. The getters show new results from the current data.

Try -500 in the Celsius box. Nothing can be colder than -273.15°C, so the setter refuses it. It throws a RangeError, an error for a value that is out of range, and stores nothing. Now change a name. The fullName getter shows the new name at once, because it reads the current names every time.

Top: the value 25 goes into set celsius(), which stores it, so _celsius is 25. Bottom: the value -500 goes into set celsius(), which throws a RangeError, so nothing is stored.new valuethe setter checks25set celsius()_celsius = 25stored-500set celsius()RangeErrornot stored
Every new value for celsius goes through the setter first. 25 is stored, but -500 is below the coldest possible temperature, so the setter throws a RangeError.

A getter runs on every read. It does not remember its last answer. Step through the code below and count how many times it runs.

The getter runs on every read
Step 0 of 11Ready
Your turn: follow the blue line

Step through two reads and one write. The getter runs on each read. The setter gets the value from the write.

Running in
  1. script
Next: line 1
  calls: 0,  _celsius: 20,  get fahrenheit() {    this.calls += 1;    return this._celsius * 9 / 5 + 32;  },  set fahrenheit(value) {    this._celsius = (value - 32) * 5 / 9;  }}; console.log(meter.fahrenheit);console.log(meter.fahrenheit);meter.fahrenheit = 68;console.log(meter._celsius);console.log(meter.calls);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Lines 13 and 14 read meter.fahrenheit twice, so the getter runs twice and calls becomes 2. Line 15 writes to the same name, so the setter runs instead. It turns 68°F into 20°C and stores it in _celsius.

Keep getters quick and safe

A getter looks like a plain property read, so people expect it to be fast and to change nothing. Don't put slow work, saving, or network requests in a getter. Use a method, like save() or buildReport(). The call brackets, (), tell the reader that work is happening.

No setter, a setter that calls itself, and run-once getters

EDGE CASES

A property can have a getter but no setter. Then assignment has nowhere to go. What happens depends on the mode:

  • Sloppy mode, the normal rules when strict mode is off: JavaScript quietly ignores the assignment.
  • Strict mode, the stricter rules turned on by "use strict": JavaScript throws a TypeError.

The code below shows both, as two separate programs.

Writing to a property that only has a getterPop out in the code editor (opens in a new tab)JavaScript
const settings = {  get mode() {    return "read-only";  }};settings.mode = "edit";console.log(settings.mode); "use strict";const settings = {  get mode() {    return "read-only";  }};settings.mode = "edit";

In the first program, line 6 is ignored, so line 7 still prints read-only. In the second program, line 15 does the same thing in strict mode and throws a TypeError.

A common mistake is a setter that calls itself. Look at line 3 below. Inside set name(value), it runs this.name = value. That is a write to name, so it runs set name again. That run writes to name again, and so on.

A setter that calls itselfPop out in the code editor (opens in a new tab)JavaScript
const person = {  set name(value) {    this.name = value;  }};person.name = "Ada";

A function that calls itself is called recursion. Here it never stops on its own. Each call waits on the call stack, the list of functions that are still running. The list gets too long, and JavaScript stops with a RangeError.

The fix: store the value in a different property, like _name, or in a private field like #name.

Left: inside set name(), this.name = value calls set name() again, and again, until JavaScript stops with a RangeError. Right: this._name = value puts "Ada" in a separate box called _name, and the setter ends.this.name = valueset name()calls againset name()calls againset name()… until JavaScript stops itRangeErrorthis._name = valueset name()"Ada"_namestored once
On the left, the setter writes to its own name, so it calls itself again and again. On the right, it writes to a different property, _name, so it stores the value once and stops.

A getter can also work out a value only once. This is called a lazy getter. On the first read, it works out the value. Then it uses Object.defineProperty to put a plain data property with that value in its own place. Later reads just return the stored value. The getter is gone, so it never runs again.

A getter that runs once, then stores its answer
Step 0 of 6Ready
Your turn: follow the blue line

A lazy getter works out a value once. Then it replaces itself with a plain data property for later reads.

Running in
  1. script
Next: line 1
  title: "Weekly",  get summary() {    const value = this.title + " report";    Object.defineProperty(this, "summary", {      value,      enumerable: true,      configurable: true    });    return value;  }}; console.log(report.summary);console.log(report.summary);console.log(Object.getOwnPropertyDescriptor(report, "summary").value);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Line 4 builds "Weekly report". Lines 5 to 9 replace the getter with a data property that holds that text. Line 10 returns it for this first read. On line 15, the read gets the stored value, and the getter doesn't run.

Before the first read, the report cart holds a getter, get summary(). The first read runs it, and Object.defineProperty swaps it for a plain value. After the first read, the report cart holds summary as the value "Weekly report".before the first readget summary()reportruns code on each readfirst readswaps itafter the first readsummary"Weekly report"reportjust a stored value
Before the first read, summary is a getter. The first read runs it once and swaps it for a plain value, so later reads just return "Weekly report".

Keeping an old property name working

MIGRATION

Sometimes you rename a property that other code already uses. Say oldName becomes newName. Old code still uses oldName. A getter and setter called oldName can keep that code working. This is called a wrapper. It does two things:

  • It prints a warning that oldName is out of date.
  • It passes each read and write on to newName.
Keep oldName working and warn about itPop out in the code editor (opens in a new tab)JavaScript
const user = {  newName: "Ada Lovelace",  get oldName() {    console.warn("oldName is deprecated; use newName");    return this.newName;  },  set oldName(value) {    console.warn("oldName is deprecated; use newName");    this.newName = value;  }}; console.log(user.oldName);user.oldName = "Grace Hopper";console.log(user.newName);

Line 13 reads oldName. The getter warns, then returns newName. Line 14 writes to oldName. The setter warns, then stores "Grace Hopper" in newName. Line 15 prints Grace Hopper.

Use a wrapper only for a while. Tell people about the new name, warn them when they use the old one, and remove the wrapper once nobody uses oldName.

A setter that cleans up an emailPop out in the code editor (opens in a new tab)JavaScript
const account = {
  _email: "",
  set email(value) {
    this._email = value.trim().toLowerCase();
  },
  get email() {
    return this._email;
  }
};

account.email = " Asha@Example.COM ";
console.log(account.email);

The email setter removes the spaces at both ends with trim(). It makes every letter small with toLowerCase(). Then it stores the result in _email. So line 12 prints asha@example.com, and every email in the object has the same form.

The underscore in _email is only a habit that tells readers "don't use this directly". It doesn't hide anything. In a class, use a private field like #email when the value must stay hidden.

Where you'll use getters and setters

Use a getter or setter when a property name is the right shape, but you need a little code behind it:

  • A display name, like user.fullName, built from two fields.
  • One value shown in two units, like Celsius and Fahrenheit.
  • A check when a value is assigned, like a range or an email format.
  • An old property name that still works after a rename.
  • A value that is slow to work out, so a lazy getter works it out once.
  • A setter on a prototype. Assigning to an object can run a setter found on its prototype (see the Prototype chain lesson).

A profile form is a good place for a setter. account.email = input.value can clean up the email once, so every later part of the code gets the same form. A shopping cart can have a cart.total getter if adding up the items is quick. If the total needs tax from a server, use a method instead. Don't hide a network request behind a property read.

Check where a getter lives before you send an object as JSON. A getter on the object itself can show up in Object.keys and JSON.stringify. A class getter on the prototype usually won't. A property you can read is not always a property that gets sent, so test the JSON text.

Getter, setter, method, or plain property?
  • user.fullName built from firstName and lastName
  • Refuse sensor.celsius = -500 with a RangeError
  • profile.save() sends data to storage
  • article.id is already stored and needs no code
  • oldName passes reads on to newName after a rename
  • Trim and lowercase the email in account.email = value
  • buildReport() does slow work when asked
  • createdAt holds a time that is already known
Try it yourself
0 of 8 correct

Put each card in the simplest choice that is honest about the work it does.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • "A getter stores its result." No. It runs on every read, unless you save the result yourself, like a lazy getter does.
  • "A setter stores the value under its own name." No. A setter just gets the new value. It stores it wherever its code says, like _celsius.
  • "Properties that start with an underscore are private." No. _name is only a habit. Any code can still read and change it.
  • "Writing to a getter-only property always throws." Only in strict mode. In sloppy mode, JavaScript ignores the write.
  • "Getters and setters are the same as a Proxy." No. A Proxy watches a whole object. A getter or setter belongs to one property.
  • "A getter is fine for any work." No. Slow or hidden work in a getter surprises people. Use a method for work they should know about.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upAdd a setter that checks the value

Run the code and note what it prints. Then change 25 to a temperature below -273.15 and run it again to see the setter refuse it.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const sensor = {
  _celsius: 0,
  set celsius(value) {
    if (value < -273.15) throw new RangeError("too cold");
    this._celsius = value;
  },
  get celsius() {
    return this._celsius;
  }
};
sensor.celsius = 25;
console.log(sensor.celsius);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upCount how many times a getter runs

    Before you run it, guess the value box.calls prints at the end.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const box = {
      calls: 0,
      get label() {
        this.calls += 1;
        return "box";
      }
    };
    console.log(box.label);
    console.log(box.label + "!");
    console.log(box.calls);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix a setter that calls itself

      This code fails with a RangeError. Change it so person.name = " Ada " stores the name without the spaces, and reading person.name gives it back.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const person = {
        set name(value) {
          this.name = value;
        }
      };
      person.name = "Ada";

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeWrite a getter that runs only once

        Change this code into an invoice with subtotal: 40 and a total getter that adds 20% tax. What number does the descriptor's value show?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const report = {
          title: "Weekly",
          get summary() {
            const value = this.title + " report";
            Object.defineProperty(this, "summary", {
              value,
              enumerable: true,
              configurable: true
            });
            return value;
          }
        };
        
        console.log(report.summary);
        console.log(report.summary);
        console.log(Object.getOwnPropertyDescriptor(report, "summary").value);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeKeep an old property name working

          Keep oldName working while the real property is now newName. After the assignment, what name does the code print?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const account = {
            newName: "Ada",
            get oldName() {
              console.warn("oldName is deprecated; use newName");
              return this.newName;
            },
            set oldName(value) {
              console.warn("oldName is deprecated; use newName");
              this.newName = value;
            }
          };
          account.oldName = "Grace";
          console.log(account.newName);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Getters & setters quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which descriptor belongs to an accessor property (a property with a getter or setter)?

              Choose an answer to see the explanation.

            2. Question 2 of 7This code reads a getter once. What does it print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const user = {
                first: "Ada",
                last: "Lovelace",
                get name() { return this.first + " " + this.last; }
              };
              console.log(user.name);

              Choose an answer to see the explanation.

            3. Question 3 of 7Inside set name(value), why should the setter store the value in _name and not in name?

              Choose an answer to see the explanation.

            4. Question 4 of 7This code writes 68 through a setter. What does it print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const meter = {
                _c: 0,
                get f() { return this._c * 9 / 5 + 32; },
                set f(value) { this._c = (value - 32) * 5 / 9; }
              };
              meter.f = 68;
              console.log(meter._c);

              Choose an answer to see the explanation.

            5. Question 5 of 7In sloppy mode (strict mode off), code writes to a property that has a getter but no setter. What happens?

              Choose an answer to see the explanation.

            6. Question 6 of 7What is a wrapper (a getter and setter for an old property name) good for?

              Choose an answer to see the explanation.

            7. Question 7 of 7This code reads the same getter twice. What does it print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const item = {
                calls: 0,
                get code() { this.calls += 1; return "A"; }
              };
              console.log(item.code + item.code);
              console.log(item.calls);

              Choose an answer to see the explanation.

            Key takeaways

            • Reading an accessor property runs its getter. Writing to it runs its setter.
            • A getter's descriptor has get and set, not value and writable.
            • A setter should check the value first, then store it in a different property, like _name. Writing to its own name makes it call itself.
            • Keep getters quick, with no surprises. Use a method for work people should notice.
            • A lazy getter works out a value once. A wrapper keeps an old name working for a while.

            An accessor property is a property that runs a function when you read it, write to it, or both.

            Up next: Freezing, sealing & immutability.

            CompleteFrontend Clear concepts. Working examples.