Part of Learn Lua with NeoVim

Learn Lua in Neovim: Booleans, Nil & Comparisons — The Truthy Gotcha | Episode 5

Sandy LaneSandy Lane

Video: Learn Lua in Neovim: Booleans, Nil & Comparisons — The Truthy Gotcha | Episode 5 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Lua Booleans, Nil, and Comparisons: The Truthy Gotcha

Comparison operators return booleans. == and ~= for equality. Only false and nil are falsy0, "", and empty tables are all truthy. The opposite of most languages.

Lua's truthiness rules are simple but different from C, Python, and JavaScript. If you've programmed in those, this episode is the one that catches you off guard.

Comparison operators

print(10 > 5)    -- true
print(10 < 5)    -- false

print(10 == 10)  -- true
print(10 ~= 10)  -- false (~= is "not equal")

Six comparison operators:

Operator Meaning
== equal
~= not equal
< less than
> greater than
<= less than or equal
>= greater than or equal

Note ~= instead of !=. (!= is a syntax error.)

Each returns a booleantrue or false.

A real comparison

local age = 18
print(age >= 18)   -- true
print(age <= 17)   -- false

Use these in if statements (next episode) to make decisions.

The truthy gotcha

if 0 then
  print("0 is truthy!")
end

if "" then
  print("empty string is truthy!")
end

if nil then
  print("this never prints")
else
  print("nil is falsy")
end

Output:

0 is truthy!
empty string is truthy!
nil is falsy

In Lua, only false and nil are falsy. Everything else is truthy.

That includes:

  • 0 — truthy
  • "" (empty string) — truthy
  • {} (empty table) — truthy
  • Any function reference — truthy

This trips up programmers from C, Python, and JavaScript, where 0 and "" are falsy. In Lua, treat 0 and "" as values, not as "falsy markers."

Why does this matter?

local count = 0
if count then
  print("we have a count")  -- runs! count is 0, but truthy.
end

If you want "0 means no count," check explicitly:

if count > 0 then
  print("we have items")
end

Or for "did this variable get set":

if count ~= nil then
  print("count exists")
end

The Lua idiom for "if a variable is set" is if x then — relying on nil being the only "unset" sentinel. That's why globals default to nil and reading an undeclared variable returns nil rather than erroring.

Equality is strict

print(1 == "1")        -- false (different types)
print(1 == 1.0)        -- true (number == number, integers and floats compare)
print(nil == false)    -- false (different types)

Lua doesn't auto-convert types for ==. JavaScript's == infamously converts (1 == "1" is true in JS). Lua doesn't — different types are never equal.

The exception: integers and floats. 1 == 1.0 is true because they're both number type, just different representations.

String comparison: lexicographic

print("apple" < "banana")   -- true
print("Apple" < "apple")    -- true (uppercase comes first in ASCII)
print("10" < "2")           -- true (string compare, '1' < '2')

Strings compare byte-by-byte. Uppercase letters have lower ASCII codes than lowercase. Numbers-as-strings sort lexically, not numerically — "10" < "2" because '1' < '2'.

For natural sorting (numeric within strings), you'd convert and compare numerically.

Boolean operators (preview)

print(true and false)    -- false
print(true or false)     -- true
print(not true)          -- false

and, or, not — full episode on these next time. They're more interesting than they look because of short-circuit evaluation and the truthy/falsy rules.

Common stumbles

Treating 0 as falsy. if count is true even when count = 0. Use if count > 0 if you mean "non-zero."

Treating "" as falsy. if s is true even when s = "". Use if s ~= "" for "non-empty string."

Using != for inequality. It's a syntax error in Lua. Use ~=.

Comparing different types with ==. Always returns false, even when the values "look equal" (1 == "1" is false). Convert explicitly.

Forgetting nil propagates. nil > 5 is an error, not false. Comparisons require both sides to be the right type. nil ~= 5 is true, however — ~= and == work on any types.

A safer pattern

local function is_set(x)
  return x ~= nil
end

if is_set(count) and count > 0 then
  print("count exists and is positive")
end

Two checks: "is it set" and "what value does it have." Combining the truthy/falsy rules with explicit comparisons keeps the intent clear.

What's next

Episode 6: comments and user input. Single-line --, multi-line --[[ ... ]], and reading from the user with io.read() plus tonumber().

Recap

Six comparison operators, all returning booleans: == ~= < > <= >=. Only false and nil are falsy0, "", and {} are all truthy. Lua doesn't auto-convert types; 1 ~= "1". String comparison is lexicographic by byte. Use ~=, not !=, for "not equal."

Next episode: comments and io.read().

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.