Start learning free

LESSON 560 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Thermostat in Go: Unexported Fields and a Safe Range

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 change it, and every one of them keeps it between 10 and 30.

Go has no private keyword: as in the Access Modifiers lesson, a field whose name starts with a lowercase letter is unexported, hidden from code outside the package. Write:

  • The Thermostat struct, with one unexported field, target (an int).
  • NewThermostat(): returns 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.

The methods that change the target have a pointer receiver, (this *Thermostat), so the change reaches the thermostat itself and not a copy; getTarget only reads, so a value receiver is enough. The starter code already has these headers.

The input is a list of operations, applied in order to one new thermostat made by NewThermostat(). The output lists what the getTarget calls return, in order.

 

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

    GO · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Thermostat in Go, explained

    Problem overview

    A thermostat keeps a target temperature, and the target must always stay between 10 and 30 degrees. If any code could write to the target directly, nothing would stop it from setting 95. The solution is to hide the field and let only the thermostat's own methods change it, each of them keeping the rule.

    The lesson asks for a Thermostat struct with one field, target, and these functions and methods:

    • NewThermostat() returns a thermostat whose target is 20.
    • setTarget(t) sets the target to t, but a value below 10 gives 10 and a value above 30 gives 30.
    • warmer() raises the target by 1 and cooler() lowers it by 1, never leaving the range 10 to 30.
    • getTarget() returns the target. It is the only way for outside code to read it.

    Approach

    Go has no private keyword. Visibility depends on the first letter of a name: a lowercase first letter makes the field unexported, visible only inside its package. That is how target is hidden. Code in another package has to call getTarget to read it and setTarget, warmer or cooler to change it.

    The range rule is written once, in setTarget. A value that is too low is not refused: it becomes the lowest allowed target, 10. A value that is too high becomes 30. Everything else, including 10 and 30 themselves, is kept as it is.

    warmer and cooler do not repeat the rule. They call setTarget with the target plus or minus 1, and setTarget turns 31 back into 30 and 9 back into 10. If the range ever changes, there is one place to edit.

    All the methods run in constant time.

    Step by step

    1. Declare target as an int field with a lowercase name.
    2. Write the constructor. NewThermostat returns a thermostat with target set to 20.
    3. Handle values that are too low. In setTarget, an if for t < 10 sets this.target to 10.
    4. Handle values that are too high. An else if for t > 30 sets the target to 30.
    5. Keep every other value. The final else sets the target to t. The limits 10 and 30 land here and are kept.
    6. Write warmer, which calls this.setTarget(this.target + 1).
    7. Write cooler, which calls this.setTarget(this.target - 1).
    8. Write getTarget, which returns this.target.

    The solution in Go

    type Thermostat struct {
        target int
    }
    func NewThermostat() Thermostat {
        return Thermostat{target: 20}
    }
    func (this *Thermostat) setTarget(t int) {
        if t < 10 {
            this.target = 10
        } else if t > 30 {
            this.target = 30
        } else {
            this.target = t
        }
    }
    func (this *Thermostat) warmer() {
        this.setTarget(this.target + 1)
    }
    func (this *Thermostat) cooler() {
        this.setTarget(this.target - 1)
    }
    func (this Thermostat) getTarget() int {
        return this.target
    }
    

    Go details:

    • Pointer receivers for changes. setTarget, warmer and cooler use (this *Thermostat), so they change the thermostat itself. With a value receiver they would change a copy and the target would never move.
    • A value receiver for reading. getTarget only reads, so (this Thermostat) is enough.
    • Calling one method from another. Inside warmer, this is a pointer, so this.setTarget(...) calls the pointer method on the same thermostat.
    • The package is the boundary. Unexported does not mean private to the type, as in other languages. Any code in the same package can still touch target. The protection works against code outside the package.

    Clamping, not refusing

    There are two common ways to handle a bad value. One is to refuse it and leave the target unchanged; the other is to clamp it to the nearest allowed value. This lesson clamps. Setting 35 does not leave the old target in place, it gives 30. That is also why warmer at 30 can simply ask for 31 and let the clamp bring it back: the result is 30, which is exactly what "never goes above 30" means.

    The three branches cover every integer exactly once: below 10, above 30, and everything from 10 to 30 inclusive. Because the branches are chained with else, only one of them runs per call.

    Using it

    t := NewThermostat()
    fmt.Println(t.getTarget())
    t.warmer()
    t.warmer()
    fmt.Println(t.getTarget())
    t.cooler()
    fmt.Println(t.getTarget())
    t.setTarget(35)
    fmt.Println(t.getTarget())
    t.warmer()
    fmt.Println(t.getTarget())
    t.setTarget(4)
    t.cooler()
    fmt.Println(t.getTarget())
    t.setTarget(10)
    t.warmer()
    fmt.Println(t.getTarget())
    

    This prints:

    20
    22
    21
    30
    30
    10
    11
    

    A new thermostat starts at 20, two warmer() calls make 22 and cooler() brings it to 21. Setting 35 gives 30, and warmer() at 30 leaves it at 30. Setting 4 gives 10, cooler() at 10 leaves it there, and after setTarget(10) a warmer() reaches 11. Even setTarget(-1000) ends at 10.

    Edge cases and common mistakes

    • Using <= or >=. The limits 10 and 30 are allowed. Writing t <= 10 would still give 10 here, but the intent is clearer with strict comparisons.
    • Refusing instead of clamping. Out-of-range values are not ignored. 35 becomes 30, not a no-op.
    • Repeating the range in warmer and cooler. Copies of the rule drift apart. Going through setTarget keeps it in one place.
    • A value receiver on a changing method. The code compiles, but nothing changes.
    • An uppercase field name. Target would be exported, and outside code could set it to 95 and skip every check.
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free