Start learning free

LESSON 254 OF 573 · FUNCTIONS & CLASSES

Water Bottle in TypeScript: A Class That Keeps Its Limits

Practice O(1) Read the full explanation

A water bottle holds at most a fixed amount of water: you can't pour in more than fits, and you can't drink more than is in it. Write a class WaterBottle that keeps track of how much water is in one bottle as you fill it and drink from it. Amounts are whole milliliters.

Implement the class WaterBottle:

  • WaterBottle(capacity) creates an empty bottle that holds at most capacity.
  • fill(amount) pours in amount, but only as much as fits: the water never goes above the capacity. It returns how much actually went in.
  • drink(amount) drinks amount, but only as much as is in the bottle: the water never goes below 0. It returns how much was actually drunk.
  • level() returns how much water is in the bottle now.

The input is a bottle's capacity and a list of operations, applied in order to one new WaterBottle. The output lists what the fill, drink and level calls return, in order. The starter code spells these names the way your language does (in Go and Rust, WaterBottle is a struct with methods). Give the fields names that differ from the methods' names: in several languages a field named level would clash with the method level().

 

Example 1:

Input:
    WaterBottle(500)
    fill(200)
    drink(50)
    level()
Output: 200 50 150
Explanation: The empty bottle has room for 500, so all 200 go in. Drinking 50 of them leaves 150.

Example 2:

Input:
    WaterBottle(300)
    fill(250)
    fill(100)
    level()
    fill(10)
Output: 250 50 300 0
Explanation: After the first fill, only 50 more fit: fill(100) pours in 50 and returns 50, and the bottle
is full. A full bottle takes nothing more, so fill(10) returns 0.

Example 3:

Input:
    WaterBottle(100)
    drink(10)
    fill(60)
    drink(100)
    level()
    drink(5)
Output: 0 60 60 0 0
Explanation: The bottle starts empty, so the first drink gets nothing. drink(100) can only drink
the 60 that are there, which empties the bottle again.

 

Constraints:

  • 1 <= capacity <= 104
  • 0 <= amount <= 104
  • At most 250 operations on one WaterBottle.

Note:

Each method should run in O(1) time: the bottle only needs to remember its capacity and how much water it holds.

STEP 1 OF 10
    Ctrl + Enter to check

    TYPESCRIPT · FUNCTIONS & CLASSES

    Water Bottle in TypeScript, explained

    Problem overview

    WaterBottle tracks how much water is in one bottle. It is created empty with a capacity. fill(amount) pours in only as much as fits and returns how much actually went in. drink(amount) drinks only what is there and returns how much was actually drunk. level() returns the water now in the bottle. Amounts are whole milliliters.

    With new WaterBottle(300), fill(250) returns 250. A second fill(100) returns only 50, because only 50 more fit, and the bottle is then full. A full bottle takes nothing more, so fill(10) returns 0.

    Approach

    The bottle needs two numbers: the capacity, which never changes, and the water, which starts at 0. The rule for each operation is "take the smaller of what was asked and what is possible": for fill the possible amount is the room left, capacity - water; for drink it is the water in the bottle. Taking the smaller value keeps the level between 0 and the capacity without any extra checks, and it is also exactly the number to return. Every method runs in constant time.

    Step by step

    1. Fields. capacity has no starting value, because the constructor sets it. water starts at 0 so that every new bottle is empty. It is named water because a field called level would clash with the method level().
    2. Constructor. Store the parameter in this.capacity. The water needs nothing here.
    3. fill. Compute added as Math.min(amount, this.capacity - this.water), add it to the water, and return it. Adding added rather than amount is what keeps the water within the capacity.
    4. drink. The same shape: drunk is the smaller of amount and this.water, it is subtracted, and returned.
    5. level. Return this.water.

    The solution in TypeScript

    class WaterBottle {
        capacity: number;
        water: number = 0;
    
        constructor(capacity: number) {
            this.capacity = capacity;
        }
    
        fill(amount: number): number {
            let added: number = Math.min(amount, this.capacity - this.water);
            this.water += added;
            return added;
        }
    
        drink(amount: number): number {
            let drunk: number = Math.min(amount, this.water);
            this.water -= drunk;
            return drunk;
        }
    
        level(): number {
            return this.water;
        }
    }
    
    • Math.min takes two numbers and returns the smaller.
    • Same parameter and field name. In the constructor, this.capacity is the field and capacity is the parameter.
    • Returning the real amount. The caller learns what actually happened, not what was requested.

    Using the class

    const w = new WaterBottle(300);
    console.log(w.fill(250));
    console.log(w.fill(100));
    console.log(w.level());
    console.log(w.fill(10));
    console.log(w.drink(1000));
    console.log(w.level());
    console.log(w.drink(5));
    
    250
    50
    300
    0
    300
    0
    0
    

    Asking to drink 1000 from a bottle holding 300 returns 300 and leaves it empty. Drinking from an empty bottle returns 0.

    Tracing a bottle

    Take new WaterBottle(100) and follow the water:

    CallReturnsWater after
    drink(10)00
    fill(60)6060
    drink(100)600
    drink(5)00

    The first drink gets nothing because the bottle starts empty. The second drink asks for 100 but only 60 are there, so it returns 60 and the bottle is empty again.

    The returned value is useful on its own. A caller that pours 100 ml into a nearly full bottle can compare the result with what it asked for, and learn that part of the water did not fit. That is why both methods return the amount that really moved instead of nothing.

    Testing

    • On a bottle of capacity 100, fill(100) returns 100, then fill(0) returns 0, drink(0) returns 0 and level() returns 100.
    • On a bottle of capacity 500, fill(200) returns 200, drink(50) returns 50 and level() returns 150.

    Another way: explicit if statements

    Math.min is short, but the same rule can be written with an if that handles the "does not fit" case on its own:

    class WaterBottle {
        capacity: number;
        water: number = 0;
        constructor(capacity: number) { this.capacity = capacity; }
        fill(amount: number): number {
            const room = this.capacity - this.water;
            if (amount > room) { this.water = this.capacity; return room; }
            this.water += amount;
            return amount;
        }
        drink(amount: number): number {
            if (amount > this.water) { const d = this.water; this.water = 0; return d; }
            this.water -= amount;
            return amount;
        }
        level(): number { return this.water; }
    }
    const w = new WaterBottle(300);
    console.log(w.fill(250), w.fill(100), w.level(), w.fill(10), w.drink(1000), w.drink(5), w.level());
    

    This prints 250 50 300 0 300 0 0, the same answers as the lesson's version. It has two branches in each method, so there are two places where a mistake can hide. The Math.min form has one path through each method, which is why the lesson prefers it.

    Edge cases and common mistakes

    • Adding amount instead of added. The level could exceed the capacity.
    • Returning amount. fill(100) on a bottle with 50 ml of room must return 50, not 100.
    • Zero amounts. fill(0) and drink(0) work without special code: the smaller value is 0.
    • A full or empty bottle. The room left is 0 when full and the water is 0 when empty, so the same formulas return 0.

    Key points

    • A class can protect its own limits: every change to water goes through fill and drink, and each takes the smaller of the request and what is possible.
    • Returning the amount that really moved lets the caller see how much of the request was honored.
    • The room left is capacity - water, and the water that can be drunk is water itself.
    • A field and a method can't share a name, so the level is stored in water and reported by level().
    • Every method runs in constant time, because the bottle remembers only two numbers.

    The same pattern, clamping a change to what is possible, shows up whenever a value must stay within bounds, such as a health bar, a volume control or a stock count.

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