skip to content

In Ruby, why does `ready = true and false` leave ready set to true, and when are and, or and not appropriate?

level: middleimportance: should knowfreq 55%

answer

  1. and/or bind looser than =
  2. && binds tighter than ||
  3. and and or share one level
  4. not calls the ! method
  5. control flow: x or raise

basics

~20 s

and, or and not bind more loosely than =, so ready = true and false parses as (ready = true) and false. Use && || ! for boolean expressions and reserve and/or for control flow such as find_bill(id) or raise.

solid answer

~40 s

Ruby has two sets of logical operators with very different precedence. `!` binds tightest, `&&` binds tighter than `||`, and both bind tighter than assignment. `not` sits below assignment, and `and` and `or` are lower still, at one shared level, evaluated left to right. So `ready = true and false` assigns `true` and then evaluates `and false` as a separate expression, and `x = false or true` leaves `x` false. Likewise `a or b and c` means `(a or b) and c`, whereas `a || b && c` means `a || (b && c)`. The idiomatic use of `and`/`or` is control flow, where the low precedence helps: `bill = find_bill(id) or raise NotFound`. RuboCop's `Style/AndOr` flags them in conditionals by default, and `Style/Not` prefers `!`.

code

ruby · 15 lines
ruby
ready = true and false
ready                      # => true

x = false or true
x                          # => false

ready = true && false
ready                      # => false

-2 ** 2                    # => -4
(-2) ** 2                  # => 4
2 ** 3 ** 2                # => 512

def find_bill(id) = nil
bill = find_bill(7) or puts("no bill 7")   # prints: no bill 7

go deeper

for a junior

Recall that and, or and not bind looser than =, so they are not drop-in replacements for &&, || and !.

for a middle

Walk through the precedence table: ! above everything, && above ||, assignment next, then not, then and/or at one level, grouped left to right.

for a senior

Enforce one convention in review - && and || for values, and/or only for statement-level control flow - and parenthesise mixed expressions.

for a principal

Decide the team's Style/AndOr setting once and let the linter enforce it, so precedence debates stop recurring in reviews.

Ruby borrowed both C-style logical operators (`&&`, `||`, `!`) and English keywords (`and`, `or`, `not`). They look like synonyms, but they sit at very different places in the **precedence table**, and that is the whole interview question. ## The relevant slice of the precedence table From highest to lowest, as the Ruby syntax documentation lists it: | Level | Operators | |---|---| | highest | `!`, `~`, unary `+` | | | `**` | | | unary `-` | | | `*`, `/`, `%` | | | `+`, `-` | | | comparisons `>`, `>=`, `<`, `<=`, then `<=>`, `==`, `!=` | | | `&&` | | | `\|\|` | | | `?:` (ternary) | | | `=`, `+=`, `\|\|=` and other assignments | | | `defined?` | | | `not` | | lowest of these | `or`, `and` (same level) | Key facts to take from it: - `&&` binds **tighter** than `||`, as in most languages. - `and` and `or` have the **same** precedence and associate left to right. - All three keywords bind **more loosely than assignment**. - `!` binds tighter than almost everything, while `not` binds looser than assignment. ## The classic traps 1. `ready = true and false` is `(ready = true) and false`. `ready` is `true`; the expression's value, `false`, is discarded. 2. `x = false or true` is `(x = false) or true`. `x` is `false`. 3. `a or b and c` is `(a or b) and c`, while `a || b && c` is `a || (b && c)`. With `a` true and `c` false the two give different answers. 4. `total = price || 0 and tip` assigns `price || 0` and then evaluates `and tip` separately. In every case the fix is either `&&`/`||` or explicit parentheses. ## Where and/or earn their place The low precedence is useful when the right-hand side is an **action** rather than a value: ```ruby bill = find_bill(id) or raise ArgumentError, "no bill #{id}" validate(order) and submit(order) ``` The first line assigns first and raises only if the assignment produced `nil` or `false`. Swapping in `||` does not work as written: `raise ArgumentError, "..."` is a command call with unparenthesised arguments, which cannot sit on the right of `||`, so the line becomes a syntax error unless you write `raise(ArgumentError, ...)`. This "do this, or else do that" reading is the style guide's intended use. RuboCop encodes it in `Style/AndOr`, whose default `EnforcedStyle: conditionals` flags `and`/`or` inside `if`, `while` and `until` conditions but allows them as control flow; setting it to `always` bans them entirely. ## not and ! `!` and `not` both negate, and both call the same method: `BasicObject#!`, which a class can in principle redefine. The difference is again precedence. `!ready && paid` negates only `ready`; `not ready && paid` negates `ready && paid`, because `not` binds more loosely than `&&`. `Style/Not` prefers `!`. ## Exponent and unary minus The same table explains a numeric trap: `**` binds tighter than unary minus, so `-2 ** 2` is `-(2 ** 2)`, which is `-4`, while `(-2) ** 2` is `4`. `**` is also **right-associative**: `2 ** 3 ** 2` is `2 ** 9`, which is `512`, not `64`. ## Values, not just booleans All four binary logical operators return one of their **operands**, not a fresh boolean: `nil || "guest"` returns `"guest"`, and `user && user.name` returns either the falsy `user` or the name. That is what makes `x = a || b` a defaulting idiom - and why confusing `||` with `or` in that line silently changes which value is stored. ## Why the design exists The keywords are meant to read like English control flow: "find the bill or raise", "validate and then submit". Their low precedence is what lets a whole assignment or method call sit on the left without parentheses. The trouble starts only when they are used as if they were `&&` and `||` inside a value-producing expression. Keeping each family to its own job removes the ambiguity entirely. ## Review checklist - In expressions that produce a value, use `&&`, `||` and `!`. - In statement-level control flow, `and`/`or` are acceptable, but never mix them with `&&`/`||` in one expression. - Parenthesise whenever a reader might hesitate; you cannot change operator precedence, only override it with parentheses.

  • In Ruby, how is `a or b and c` grouped, and how does that differ from `a || b && c`?
    `and` and `or` share one precedence level and group left to right, so `a or b and c` is `(a or b) and c`. `&&` binds tighter than `||`, so `a || b && c` is `a || (b && c)`. With `a` truthy and `c` false, the first yields `false` and the second yields `a`.
  • Does redefining ! on a class change how && and || treat its instances?
    No. `!` and `not` call `BasicObject#!`, so an override changes them, but `&&`, `||`, `and` and `or` are not methods: they test truthiness directly, where only `nil` and `false` are falsy. Redefining `!` therefore makes `!obj` and `obj ? ... : ...` disagree, which is why nobody should do it.

saying these in an interview costs you the question

  • and and && are exact synonyms in Ruby; only the spelling differs.
  • or binds tighter than assignment, so x = false or true sets x to true.
  • and binds tighter than or, just as && binds tighter than ||.
  • not x && y negates only x, the same as !x && y.
  • -2 ** 2 is 4 in Ruby because the minus belongs to the literal.