Start learning free

LESSON 560 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat in TypeScript: A Private Field with Limits

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

    TYPESCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in TypeScript, explained

    Problem overview

    A thermostat keeps a target temperature that must always stay between 10 and 30. If any code could assign the target directly, nothing would stop 95. So the target is a private field, and only the thermostat's own methods can change it.

    • new Thermostat() starts at 20.
    • setTarget(t) sets the target, clamping values below 10 to 10 and above 30 to 30.
    • warmer() and cooler() move it by 1 but never outside 10 to 30.
    • getTarget() is the only way to read it.

    Approach

    Put the rule in one place. setTarget clamps its argument. warmer and cooler then call setTarget with the target plus or minus 1, so at 30 the value 31 becomes 30 again, and at 10 the value 9 becomes 10. If the rule were copied into three methods, a later change could easily miss one. Every method runs in constant time.

    Step by step

    1. Private field. private target: number; The private keyword makes the compiler reject thermostat.target outside the class.
    2. Constructor sets this.target = 20.
    3. setTarget, low case. If t < 10, the target becomes 10.
    4. High case. Else if t > 30, it becomes 30.
    5. Otherwise the target becomes t. The values 10 and 30 are allowed, so they land here.
    6. warmer calls this.setTarget(this.target + 1).
    7. cooler calls this.setTarget(this.target - 1).
    8. getTarget returns this.target.

    The solution in TypeScript

    class Thermostat {
        private target: number;
    
        constructor() {
            this.target = 20;
        }
    
        setTarget(t: number): void {
            if (t < 10) {
                this.target = 10;
            } else if (t > 30) {
                this.target = 30;
            } else {
                this.target = t;
            }
        }
    
        warmer(): void {
            this.setTarget(this.target + 1);
        }
    
        cooler(): void {
            this.setTarget(this.target - 1);
        }
    
        getTarget(): number {
            return this.target;
        }
    }
    
    • Methods can call each other. Inside the class, this.setTarget(...) is allowed, and so is reading this.target, even though it is private.
    • private is a compile-time check. TypeScript erases it when it produces JavaScript, so the property still exists at run time. A learner who wants real run-time privacy can use a #target field instead.

    Using the class

    const h = new Thermostat();
    console.log(h.getTarget());
    h.warmer();
    h.warmer();
    console.log(h.getTarget());
    h.cooler();
    console.log(h.getTarget());
    h.setTarget(35);
    console.log(h.getTarget());
    h.warmer();
    console.log(h.getTarget());
    h.setTarget(4);
    console.log(h.getTarget());
    h.cooler();
    console.log(h.getTarget());
    h.setTarget(10);
    h.warmer();
    console.log(h.getTarget());
    
    20
    22
    21
    30
    30
    10
    10
    11
    

    Writing h.target = 95; outside the class makes the compiler report that the property is private and only accessible within the class Thermostat. Since a type error doesn't stop tsc from writing JavaScript, the assignment would still run, and getTarget() would then return 95. The protection comes from fixing the error, not from the run time.

    Why keep the rule in one method

    With the rule in setTarget only, there is exactly one place to read when you want to know what values are legal, and exactly one place to change if the limits become 5 and 35. The other methods are built on it and inherit the rule for free. This is the real benefit of encapsulation: the field is private so that the rule cannot be bypassed, and the rule lives in one method so that it cannot drift.

    Testing

    Push the target against both limits:

    • After setTarget(30), three warmer() calls leave the target at 30 each time.
    • After setTarget(10), a cooler() call leaves it at 10.
    • setTarget(-1000) gives 10 and setTarget(1000) gives 30, the extremes of the constraints.

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

    The three-way if says the rule in words. The same rule fits on one line, by taking the larger of 10 and t and then the smaller of that and 30:

    class Thermostat {
        private target: number;
        constructor() { this.target = 20; }
        setTarget(t: number): void { this.target = Math.min(30, Math.max(10, t)); }
        warmer(): void { this.setTarget(this.target + 1); }
        cooler(): void { this.setTarget(this.target - 1); }
        getTarget(): number { return this.target; }
    }
    const h = new Thermostat();
    h.setTarget(35);
    console.log(h.getTarget());
    h.setTarget(4);
    console.log(h.getTarget());
    h.setTarget(22);
    console.log(h.getTarget());
    h.warmer();
    console.log(h.getTarget());
    

    This prints 30, 10, 22 and 23. Both forms behave the same. Use whichever you can read at a glance.

    Another way: a # field

    JavaScript has its own privacy, written #target. Unlike private, it is enforced when the program runs. With a #target field, the compiled code has no property called target at all, so assigning h.target = 95 through an untyped reference creates an unrelated property and getTarget() still returns 20. The lesson uses private, which is the usual TypeScript choice.

    Edge cases and common mistakes

    • Using <= or >=. 10 and 30 are valid targets. Clamping them is harmless, but refusing them would be wrong.
    • Refusing instead of clamping. setTarget(4) sets 10; it doesn't leave the old value.
    • Copying the rule. Writing this.target++ in warmer skips the limit and lets the target reach 31.
    • Making getTarget change something. It must only read.

    Extra edge cases

    • Repeated presses at a limit. Calling warmer() three times from 30 keeps the target at 30 each time, and cooler() from 10 keeps it at 10.
    • Unusual order of calls. setTarget(35) followed by cooler() gives 29, not 34, because the 35 was clamped to 30 first.
    • Reading before any change. A new thermostat returns 20 from getTarget(), since the constructor sets it.

    Key points

    • Make a field private when its value must obey a rule, and offer methods that keep the rule.
    • Put the rule in one method and let the others call it: warmer and cooler reuse setTarget.
    • Clamping means moving an out-of-range value to the nearest allowed one, instead of rejecting it.
    • A getter such as getTarget() lets other code read a private field without being able to change it.
    • TypeScript's private is checked by the compiler, while a # field is also enforced at run time.

    All five methods run in constant time: each one compares a number or changes one number.

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