Start learning free

LESSON 556 OF 569 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat Class in JavaScript: Private Fields

Practice O(1) Read the full explanation

A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could change the target directly, nothing would stop it from setting 95. So the target is a private field: only the thermostat's own methods can change it, and every one of them keeps it between 10 and 30.

Implement the class Thermostat:

  • Thermostat() creates a thermostat whose target is 20.
  • setTarget(t) sets the target to t. A value below 10 sets it to 10, and a value above 30 sets it to 30.
  • warmer() raises the target by 1 and cooler() lowers it by 1, but neither ever takes it outside 10 to 30: at 30, warmer() leaves it at 30, and at 10, cooler() leaves it at 10.
  • getTarget() returns the target. It is the only way for other code to read it.

Keep the target private the way the Access Modifiers lesson does in your language: self._target in Python (private by convention), a #target field in JavaScript, and the private keyword in Java, C++, C#, TypeScript, Kotlin, Scala and Swift.

The input is a list of operations, applied in order to one new Thermostat. The output lists what the getTarget calls return, in order. The starter code spells these names the way your language does: set_target and get_target in Python, SetTarget, Warmer, Cooler and GetTarget in C#.

 

Example 1:

Input:
    getTarget()
    warmer()
    warmer()
    getTarget()
    cooler()
    getTarget()
Output: 20 22 21
Explanation: A new thermostat starts at 20. Two warmer() calls take it to 22, and cooler() brings it back to 21.

Example 2:

Input:
    setTarget(35)
    getTarget()
    warmer()
    getTarget()
    cooler()
    getTarget()
Output: 30 30 29
Explanation: 35 is above 30, so the target becomes 30. warmer() can't go past 30, so the target stays 30.
cooler() lowers it to 29.

Example 3:

Input:
    setTarget(4)
    getTarget()
    cooler()
    getTarget()
    setTarget(10)
    warmer()
    getTarget()
Output: 10 10 11
Explanation: 4 is below 10, so the target becomes 10, and cooler() can't go below 10. setTarget(10) keeps 10,
since 10 itself is allowed, and warmer() raises it to 11.

 

Constraints:

  • -1000 <= t <= 1000
  • A test case has from 1 to 1000 operations, and at least one of them is getTarget.
STEP 1 OF 8
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in JavaScript, explained

    Problem overview

    A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could assign to the target directly, nothing would stop it from setting 95. So the target is a private field, written #target, and only the thermostat's own methods can change it. Every one of them keeps it between 10 and 30.

    • new Thermostat() starts at 20.
    • setTarget(t) sets the target to t, but a value below 10 becomes 10 and a value above 30 becomes 30.
    • warmer() raises the target by 1 and cooler() lowers it by 1, but neither takes it outside 10 to 30.
    • getTarget() returns the target. It is the only way for other code to read it.

    A new thermostat reports 20. Two calls to warmer() make it 22, and cooler() brings it to 21. After setTarget(35) it reports 30, and warmer() leaves it at 30.

    Approach

    The rule "stay between 10 and 30" has to hold in every method that changes the target. Writing the check three times invites mistakes, so it lives in one place, setTarget. warmer and cooler compute the new value and pass it to setTarget, which clamps it. At 30, warmer() asks for 31, and setTarget turns that back into 30. All methods run in O(1) time.

    Step by step

    1. Declare the private field. At the top of the class, write #target;. The # is part of the name. Code outside the class can't use it.
    2. Start at 20. In the constructor, assign 20 to this.#target.
    3. Too low. In setTarget, an if for t < 10 sets the target to 10. The value isn't refused: the target becomes the lowest one allowed.
    4. Too high. An else if for t > 30 sets it to 30.
    5. Everything else. The else branch stores t. The values 10 and 30 end up here, because they are allowed.
    6. Warmer and cooler. Call this.setTarget(this.#target + 1) and this.setTarget(this.#target - 1).
    7. Read it. getTarget() returns this.#target.

    The solution in JavaScript

    class Thermostat {
        #target;
    
        constructor() {
            this.#target = 20;
        }
    
        setTarget(t) {
            if (t < 10) {
                this.#target = 10;
            } else if (t > 30) {
                this.#target = 30;
            } else {
                this.#target = t;
            }
        }
    
        warmer() {
            this.setTarget(this.#target + 1);
        }
    
        cooler() {
            this.setTarget(this.#target - 1);
        }
    
        getTarget() {
            return this.#target;
        }
    }
    

    Using the class

    let t = new Thermostat();
    console.log(t.getTarget());
    t.warmer();
    t.warmer();
    console.log(t.getTarget());
    t.cooler();
    console.log(t.getTarget());
    
    t = new Thermostat();
    t.setTarget(35);
    console.log(t.getTarget());
    t.warmer();
    console.log(t.getTarget());
    t.cooler();
    console.log(t.getTarget());
    
    t = new Thermostat();
    t.setTarget(4);
    console.log(t.getTarget());
    t.cooler();
    console.log(t.getTarget());
    t.setTarget(10);
    t.warmer();
    console.log(t.getTarget());
    

    The three groups print:

    20
    22
    21
    30
    30
    29
    10
    10
    11
    

    What # protects

    In JavaScript, a name starting with # is truly private. It must be declared in the class body, and using t.#target outside the class is a SyntaxError, not just a bad habit. A plain property such as this.target could be assigned from anywhere. Outside the class, t.target and t._target are both undefined, because the field's name is not an ordinary property name.

    Assigning t.target = 95 from outside creates a new, unrelated property on the object. It doesn't touch the private field, and t.getTarget() still returns the real target.

    Common mistakes

    • Forgetting the declaration. A private field must be declared with #target; in the class body before this.#target is used.
    • Writing this.target instead of this.#target. That is a different, public property, and the class would no longer protect the value.
    • Checking the limits in only one place. If warmer does this.#target++ directly, nothing stops it from reaching 31. Going through setTarget keeps the rule in one place.
    • Off-by-one at the edges. 10 and 30 are both allowed, so the checks use < and >, not <= and >=.

    Testing the edges

    The interesting inputs are the ones at and beyond the limits. On one thermostat, calling setTarget with each of these values gives:

    setTarget(t)getTarget()
    -100010
    910
    1010
    3030
    3130
    100030

    The same holds for the step methods. From 29, warmer() gives 30, and a second warmer() still gives 30. From 11, cooler() gives 10, and a second cooler() still gives 10. The extreme inputs in the constraints, -1000 and 1000, need no special handling: they are just values below 10 and above 30.

    Why a getter and not a public field

    Reading the target through getTarget() looks like extra work, but it is the price of the guarantee. Code outside can see the value, but the only ways to change it are setTarget, warmer and cooler, and all three keep it in range. If the rule ever changes, say to 5 through 35, only the class changes, and no code that uses a thermostat has to be touched.

    Another way: clamping with Math.min and Math.max

    The three-branch if can be written in one line. Math.max(10, t) lifts a too-small value to 10, and wrapping it in Math.min(30, ...) cuts a too-large value down to 30:

    setTarget(t) {
        this.#target = Math.min(30, Math.max(10, t));
    }
    

    With this version, calling setTarget with -1000, 9, 10, 30, 31 and 1000 leaves the target at 10, 10, 10, 30, 30 and 30, the same as the table above. The lesson's if chain is easier to read while you learn, and the clamp is a common way to write the same rule once you know it.

    Keep going with JavaScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free