Variables and Data Types
Reviewed & published by Brayan K
By the end of this lesson you'll store text, numbers, and true/false values in Lua, know its handful of types, join strings with .., measure them with #, convert between types on purpose, and understand why local should be your default — the bedrock of every Lua script.
Part of the free Lua course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
- Declare variables and tell local apart from global (and why local wins)
- Name Lua's 8 types and use the 5 you'll meet daily
- Understand dynamic typing — the value has a type, the name doesn't
- Join text with the .. operator and avoid the + trap
- Measure length with #, and convert with tostring / tonumber
- Recognise nil and handle 'missing value' safely
💡 Real-World Analogy
A global variable is like writing on a shared whiteboard in a busy office — anyone in any room can read it, and anyone can wipe it or scribble over it. A local variable is a sticky note inside your own notebook: only you, in this room, can see it, and it's thrown away when you leave. Most bugs in beginner Lua come from scribbling on the shared whiteboard by accident, so you'll reach for the notebook (local) almost every time.
1. local vs Global Variables
In Lua you make a variable just by assigning to a name: score = 100. But here's the surprise that catches everyone — that variable is global by default. To keep it tidy and safe you put local in front: local score = 100. A global is visible to your whole program and every library; a local only lives inside its current block. Read this worked example, run it, and watch the local vanish outside its block.
-- A variable is a NAMED box that holds one value.
-- In Lua you create one by just assigning to a name:
score = 100 -- this is a GLOBAL variable (avoid this!)
local lives = 3 -- this is a LOCAL variable (almost always what you want)
-- Why does it matter? A global is visible to EVERY part of your program,
-- so a typo or a clash can quietly break code far away. A local only
-- lives inside the block it was declared in, so it stays out of the way.
print(score) -- prints: 100
print(lives) -- prints: 3
-- Locals disappear at the end of their block. 'do ... end' is just a block.
do
local secret = 42 -- only exists between 'do' and 'end'
print(secret) -- prints: 42
end
print(secret) -- prints: nil (secret is gone — it was local to the block)The #1 Lua Gotcha
Because a bare assignment creates a global, a typo silently makes a new variable instead of erroring. Write socre = 100 when you meant score, and Lua happily creates socre — your real value never changes and nothing warns you. Defaulting to local turns this whole class of bug into something you can actually spot.
2. The Eight Types (and Dynamic Typing)
Lua keeps things small: there are exactly 8 types — nil, boolean, number, string, table, function, userdata, and thread. Day to day you'll use the first five. Note that one number type covers both whole numbers and decimals. Lua is dynamically typed: a variable name carries no type at all — only the value inside it does — so the same name can hold a number now and a string a moment later. Use type(x) any time you want to ask what a value is.
-- Lua has 8 types. As a beginner you mostly meet the first five.
-- 'type(x)' tells you the type of any value as a string.
local nothing = nil -- nil = "no value / does not exist"
local ok = true -- boolean = true or false
local age = 25 -- number = ints and decimals (one type)
local pi = 3.14159 -- number too
local name = "Ada" -- string = text, in "double" or 'single' quotes
local scores = {10, 20, 30} -- table = Lua's one container type
print(type(nothing)) -- prints: nil
print(type(ok)) -- prints: boolean
print(type(age)) -- prints: number
print(type(name)) -- prints: string
print(type(scores)) -- prints: table
-- DYNAMIC TYPING: a variable has no fixed type — only the VALUE does.
-- The same name can hold a number now and a string later.
local x = 10
print(type(x)) -- prints: number
x = "now I'm text"
print(type(x)) -- prints: string📊 Lua's 8 Types at a Glance
| Type | Holds | Example |
|---|---|---|
| nil | No value / absence | local x = nil |
| boolean | true or false | local ok = true |
| number | Ints and decimals | local n = 3.14 |
| string | Text | local s = "hi" |
| table | Lua's only container | local t = {1, 2} |
| function | Reusable behaviour | local f = print |
| userdata | Data from C code | (advanced) |
| thread | A coroutine | (advanced) |
The greyed rows (function, userdata, thread) come later — you'll meet function and table properly in the next lesson.
Your turn. The program below is almost finished — fill in the three blanks marked ___ using the hints, then run it and compare with the expected output.
-- 🎯 YOUR TURN — replace each ___ then run the code.
-- 1) Make a LOCAL string called "player" set to any name
local player = ___ -- 👉 text in "quotes", e.g. "Sam"
-- 2) Make a LOCAL number called "level" set to a whole number
local level = ___ -- 👉 a number, no quotes, e.g. 5
-- 3) Build a message by concatenating with .. (two dots)
-- Hint: "text" .. variable .. "more text"
local message = "Player " .. ___ .. " is on level " .. ___ -- 👉 use player, then level
print(message)
-- ✅ Expected output:
-- Player Sam is on level 53. Strings: Joining, Length, and Converting
To glue text together you use the concatenation operator .. (two dots) — not +, which is only for number maths. To find how long a string is, put the length operator # in front of it: #"Lua" is 3. When you need to move between text and numbers deliberately, tostring(value) turns anything into text and tonumber(text) turns text into a number (or gives nil if it isn't one). Numbers used inside .. convert to text for you automatically.
-- Join (concatenate) strings with two dots: ..
local first = "Grace"
local last = "Hopper"
local full = first .. " " .. last
print(full) -- prints: Grace Hopper
-- '..' joins; it is NOT '+'. Using '+' on text is an error (see below).
-- Lua will auto-convert a NUMBER to text inside '..':
local lvl = 7
print("Level " .. lvl) -- prints: Level 7
-- # is the LENGTH operator. On a string it counts the bytes (characters).
print(#full) -- prints: 12 (G-r-a-c-e-space-H-o-p-p-e-r)
print(#"Lua") -- prints: 3
-- Convert ON PURPOSE with tostring() and tonumber():
local n = 42
print(tostring(n) .. "!") -- prints: 42! (number -> string)
print(tonumber("3.5") + 0.5) -- prints: 4.0 (string -> number)
print(tonumber("hello")) -- prints: nil (not a number -> nil)Now you try. Text typed by a user always arrives as a string, so before doing maths you convert it. Fill in the two blanks:
-- 🎯 YOUR TURN — input from a user always arrives as TEXT.
local quantityText = "3" -- this is a STRING, not a number
local word = "dragon"
-- 1) Turn quantityText into a real number with tonumber(...)
local quantity = ___ -- 👉 tonumber(quantityText)
-- 2) Get the length of "word" with the # operator
local letters = ___ -- 👉 #word
-- 3) Concatenate: numbers auto-convert, so just join with ..
print("You have " .. quantity .. " items")
print("The word has " .. letters .. " letters")
-- ✅ Expected output:
-- You have 3 items
-- The word has 6 lettersPro Tips
- 💡 Default to local. Put it on every variable unless you genuinely need a global. Your future self will thank you.
- 💡 .. joins, + adds. If you mean text, use ..; if you see an arithmetic error on a string, that's the cause.
- 💡 Convert on purpose. Reach for tonumber the moment a value comes from user input, a file, or the network — it's text until you say otherwise.
- 💡 Multi-line strings use double square brackets: [[ ... ]]. They keep newlines and whitespace exactly, great for templates.
Common Errors (and the fix)
- Forgetting local makes a global: health = 50 quietly pollutes the global namespace, and a typo creates a stray global. Fix: write local health = 50.
- "attempt to perform arithmetic on a string value": you joined text with +, e.g. "Hi " + name. Fix: use .. → "Hi " .. name.
- "attempt to compare number with string": Lua won't compare across types, e.g. 10 < "20". Fix: convert first — 10 < tonumber("20").
- "attempt to perform arithmetic on a nil value": you used a variable that was never set (or a tonumber that returned nil) in maths, like nil + 1. Fix: check the value exists before doing arithmetic.
📋 Quick Reference
| Task | Code | Result |
|---|---|---|
| Local variable | local x = 10 | scoped to block |
| Check a type | type("hi") | "string" |
| Join text | "a" .. "b" | "ab" |
| String length | #"hello" | 5 |
| Text → number | tonumber("42") | 42 |
| Number → text | tostring(42) | "42" |
| No value | local x = nil | nil |
Mini-Challenge: Character Sheet
No blanks this time — just a brief and an outline to keep you on track. Build it, run it, and check your output against the example in the comments. This is the kind of tiny program real games and tools are stitched together from.
-- 🎯 MINI-CHALLENGE: Character sheet
-- 1. Create LOCAL variables: name (string), class (string),
-- hp (number), and alive (boolean set to true).
-- 2. Print a 3-line sheet using .. to join text and values:
-- Name: <name> the <class>
-- HP: <hp>
-- Alive: <alive>
-- 3. BONUS: print the length of the name with #name on its own line.
--
-- ✅ Example output (name="Mara", class="Mage", hp=30):
-- Name: Mara the Mage
-- HP: 30
-- Alive: true
-- Name length: 4
-- your code here🎉 Lesson Complete!
- ✅ Variables are global by default — write local to keep them scoped and safe
- ✅ A forgotten local (or a typo) silently creates a global
- ✅ Lua has 8 types; you'll mostly use nil, boolean, number, string, table
- ✅ Lua is dynamically typed — the value has a type, the variable name doesn't
- ✅ Join text with .., measure it with #, convert with tostring / tonumber
- ✅ nil means "no value"; arithmetic on nil errors, so check first
Practice quiz
In Lua, a variable declared without local is...
- Global
- Constant
- Local to the file
- A syntax error
Answer: Global. Variables are global by default; local makes them block-scoped.
How many types does Lua have?
- 5
- 8
- 12
- 3
Answer: 8. Lua has 8 types: nil, boolean, number, string, table, function, userdata, thread.
What does type("Lua") return?
- text
- char
- string
- str
Answer: string. The string type is reported as "string" by type().
What does nil represent in Lua?
- Zero
- An empty string
- False
- No value / absence
Answer: No value / absence. nil means there is no value present.
Which operator gives the length of a string?
- #
- len()
- length
- $
Answer: #. The # operator returns a string's length (or a table's array length).
Does Lua have one number type for both integers and decimals?
- No, separate int and float keywords
- Yes, a single number type covers both
- Only integers exist
- Only decimals exist
Answer: Yes, a single number type covers both. Lua's number type covers whole numbers and decimals alike.
What does tonumber("hello") return?
- 0
- An error
- nil
- "hello"
Answer: nil. tonumber returns nil when the text isn't a valid number.
Lua is dynamically typed, which means...
- Types never change
- Variables have fixed types
- You must annotate every type
- The value has a type, not the variable name
Answer: The value has a type, not the variable name. A name can hold a number now and a string later; only the value has a type.
Which function converts a number to text?
- tostring(n)
- toString(n)
- str(n)
- string(n)
Answer: tostring(n). tostring() converts any value to its string form.
Why is the bug socre = 100 (meant: score) hard to spot?
- It errors loudly at runtime
- It silently creates a new global variable
- It crashes the program
- Lua auto-corrects it
Answer: It silently creates a new global variable. A typo without local just creates an unintended global; nothing warns you.
Continue this course
- Previous: Introduction to Lua
- Next: Functions and Tables — Define functions and use Lua's versatile table structure as arrays and objects
Frequently asked questions
Why should I almost always use local instead of global?
A local variable only exists inside the block where you declare it, so it can't accidentally clash with code elsewhere, and the Lua compiler can access it faster. Globals live in one shared table that the whole program (and every library) can see and overwrite, so a single typo can silently break distant code. The rule of thumb: write 'local' in front of every variable unless you have a specific reason not to.
What happens if I forget the word local?
Lua does not error — it creates a GLOBAL variable instead. That is why typos are dangerous: writing 'socre = 100' instead of 'score = 100' makes a brand-new global called socre, and your real score variable is never updated. Nothing warns you. Putting 'local' on declarations makes such mistakes far easier to catch.
How many data types does Lua have?
Eight: nil, boolean, number, string, table, function, userdata, and thread. Beginners mainly use the first five. 'function' values let you store and pass behaviour; 'userdata' wraps data from C libraries; 'thread' is for coroutines. You can always check any value's type at runtime with type(value).
Why do I join strings with .. and not +?
In Lua the .. operator means 'concatenate' (stick text together), while + is reserved strictly for number arithmetic. Writing "Hi " + name throws an 'attempt to perform arithmetic on a string value' error. Use "Hi " .. name instead. Numbers used inside .. are converted to text automatically.
What is nil and when do I see it?
nil means 'no value here'. An unset variable, a missing table key, and a tonumber() call on non-numeric text all give back nil. Reading a variable that was never assigned returns nil rather than crashing — but doing arithmetic on nil (like nil + 1) is an error, so check for nil before using a value you are unsure about.