Finding a way to obtain some volume of water using a large and small bucket
I found your conditions to be a little confusing. It was hard for me to understand the rules of what is going on by just a quick glance. In this simple case, you don't necessarily need to create more abstraction, just be more clear about presenting the rules.

One way to make your code more readable is to try and avoid an if/else maze if a series of ifs could suffice instead. There's a certain amount of personal preference to that, however, when there are many if/else's, your brain has to stack up and keep track of all the previous conditions. It's better to check a condition, then handle it immediately and return to a higher scope instead of nesting further. By not nesting further, you can "forget" about it and simply move on to the next condition.

A trivial example example:

function describeHeight(person) {
    var height = person.height;
    if (height &lt; 4) {
        console.log('short');
    }
    else if (height &gt;= 4 &amp;&amp; height &lt; 6) {
        console.log('average');
    }
    else if (height &gt;= 6 &amp;&amp; height &lt; 8) {
        console.log('tall');
    }
    else {
        console.log('giant!!!');
    }
}


Versus:

function describeHeight(person) {
    var height = person.height;
    if (height &gt;= 8) {
        console.log('giant!!!');
        return;
    }
    if (height &gt;= 6) {
        console.log('tall');
        return;
    }
    if (height &gt;= 4) {
        console.log('average');
        return;
    }
    console.log('short');
}


Not the best example, perhaps. But I feel like the second version is easier to read. See how the first example has more nesting, and the second has less?

In your example, I would try to simplify the rules. The logic may not be correct here, because I don't exactly understand your rules, but something like this may be easier to read:

while (step++ &lt;= maxSteps) {
    // Check if container has the appropriate amount of water.
    if (buckets.firstContainer.getCurrentAmount() === waterSize) {
        break;
    }

    // If first container is empty, then fill it.
    if (buckets.firstContainer.isEmpty()) {
        buckets.firstContainer.fill();
        continue;
    }

    // If first container is full, fill up the second container if there's room.
    if (buckets.firstContainer.isFull() &amp;&amp; buckets.secondContainer.hasCapacityAvailable()) {
        buckets.firstContainer.transferTo(buckets.secondContainer);
        continue;
    }

    // If second container is full, dump it out, and try to fill up the first container if there's room.
    if (buckets.secondContainer.isFull() &amp;&amp; buckets.firstContainer.hasCapacityAvailable()) {
        buckets.secondContainer.empty();
        buckets.firstContainer.transferTo(buckets.secondContainer);
        continue;
    }
}