The && and and operators might both perform logical AND operations, but they are not interchangeable. In fact, they have different precedences:

foo = 3 && foo  # foo => nil
foo = 3 and foo # foo => 3

The above example demonstrates how the assignment operation (=) is performed after the && operation and before the and operation. But why?

Different purposes

In Ruby, the && operator is intended for logical operations, but the and operator is intended to alter control flow. Let's highlight this by adding parentheses to show the order of operations that occurred in the intro example:

foo = (3 && foo)
(foo = 3) and (foo)

In the first expression, the purpose of the && is to confirm that the operands on both the left and right sides are truthy. With the right-side foo undefined, the parenthetical expression evaluates to nil. This is then assigned to foo by the = operator.

However, in the second expression, the and ensures that the left-side operation completes before the right-side operation begins. Its purpose is to compose (i.e. chain) a string of commands that should run serially until one command in the chain fails:

command1 and command2 and command3 and command4
resource = fetch_resource and parse_resource(resource)

If a command in the sequence fails (e.g. the resource fails to be fetched), the and short-circuits and doesn't attempt any commands that follow (e.g. parsing the resource). This is how the and manages control flow.

That said, if a chain consists of only two commands, it's more idiomatic Ruby to use the if modifier instead:

cmd2 if cmd1
puts "fetch succeeded" if resource = fetch_resource

It's not just and

Just as and is different from &&, so are or and || different, and ! and not too.

In the case of or, the operator short-circuits after the first command in the chain to successfully complete. It might be used like this:

foo = fetch_from_cache or fetch_from_database or create_new_resource

Here, the fetch_from_database and create_new_resource commands are not called if fetch_from_cache returns a truthy value.

When only two commands need to be chained by or, it's more idiomatic Ruby to use the unless modifier instead:

foo = fetch_from_database unless fetch_from_cache

Now that we've walked through examples for and and or, the formulation of examples distinguishing not from ! is left as an exercise to the reader. (I had to do it! I'm married to a mathematician!)

Precedence matters

The reasoning is now understood, but there's one last important thing to note.

Even though the and and or operators have the same precedence and are evaluated in expressions from left to right, the && and || operators are different! Use parentheses when necessary to clarify or enforce the desired order of operations.

 true || true  && false  # => true
(true || true) && false  # => false
 true or true and false  # => false

For reference, here is a subset of the Ruby operators sorted in order of precedence from highest to lowest:

Operator Description
! logical NOT
&& logical AND
|| logical OR
not negation
and, or composition
if, unless modifiers

Now that that's all sorted, it's time for me to get back to a programming language that uses either one of and or && but not both...