---
url: /programming-fundamentals/strings-numbers-nil.md
description: Text, numbers, and nil, plus equality and conversion pitfalls to avoid.
---

# Strings, Numbers, and Nil

*Written: {{ $frontmatter.date }}*

## Learning objectives

* You write [strings](https://create.roblox.com/docs/luau/strings) with quotes, join them, and use escapes.
* You read [numbers](https://create.roblox.com/docs/luau/numbers) as doubles, including `0x`, `0b`, and underscores.
* You explain [nil](https://create.roblox.com/docs/luau/nil) as non-existence.
* You predict when `==` surprises you and compare the safe way.

## Prerequisites

* Read [First Script](../getting-started/first-script.md). You can run a Script and read output.
* Read [Variables and Values](variables-and-values.md). You create variables and print them.
* Read [Operators and Logic](operators-and-logic.md). You join with `..` and convert with `tonumber` and `tostring`.
* Read [Conditionals and Loops](conditionals-and-loops.md). You branch with `if`.
* Read [Functions and Scope](functions-and-scope.md). You return values from functions.
* Read [Tables, Part 1](tables-part-1.md). You read table keys and positions.

## Strings hold text

A string is a run of characters between quotes: letters, digits, spaces, and symbols. Double quotes and single quotes both work. Pick one style per script and stick with it. This course uses double quotes.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local greeting = "Hello!"
local quoted = 'She said hi'
print(greeting)
print(quoted)
```

```txt
Hello!
She said hi
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores `"Hello!"` in `greeting`.
3. Line 3 stores `'She said hi'` in `quoted`. Single quotes work the same.
4. Lines 4 and 5 print both strings.

You already joined strings with `..` in [Operators and Logic](operators-and-logic.md). Joining never adds spaces. Put the spaces inside your quotes.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local firstName = "Maya"
local line = "Hi, " .. firstName .. "!"
print(line)
print(#line)
```

```txt
Hi, Maya!
9
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores `"Maya"` in `firstName`.
3. Line 3 joins three pieces into `"Hi, Maya!"`. The space and comma come from inside `"Hi, "`.
4. Line 4 prints `Hi, Maya!`.
5. Line 5 prints the character count, `9`.

Escapes let quotes and special marks live inside strings. A backslash plus a character stands for one hard-to-type character: `\"` is a quote inside a double-quoted string, `\\` is a single backslash, `\n` starts a new line, and `\t` inserts a tab.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local said = "She said \"hi\""
local lines = "one\ntwo"
print(said)
print(lines)
```

```txt
She said "hi"
one
two
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores `She said "hi"`. Each `\"` becomes one real quote character without ending the string.
3. Line 3 stores two lines in one string. `\n` becomes a line break.
4. Line 4 prints `She said "hi"` on one line.
5. Line 5 prints `one` and `two` on separate lines.

## Numbers are doubles with handy spellings

Every Luau number is a double: a 64-bit decimal-friendly value. There are no separate integer and decimal types. `7` and `7.5` are both numbers.

Luau offers alternate spellings for whole values. `0x` starts a hexadecimal (base-16) literal, like `0xFF` for `255`. `0b` starts a binary (base-2) literal, like `0b1010` for `10`. Underscores split long digits: `1_000_000` equals `1000000`.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local red = 0xFF
local switches = 0b1010
local crowd = 1_000_000
print(red)
print(switches)
print(crowd)
```

```txt
255
10
1000000
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores hex `0xFF`, which equals decimal `255`.
3. Line 3 stores binary `0b1010`, which equals decimal `10`.
4. Line 4 stores `1_000_000`. The underscores vanish. The value is `1000000`.
5. Lines 5 to 7 print `255`, `10`, `1000000`.

Decimals bring tiny fuzz. `0.1 + 0.2` prints `0.30000000000000004`, not a clean `0.3`. This is normal for doubles in every language. For money or scores, round for display with `tostring` and formatting, or compare with a small tolerance instead of exact `==`.

## Nil means nothing is there

A variable holds `nil` when it was never assigned, when a function returns nothing, when a table key was never created, or when you deleted it. Reading any of those gives `nil`. It is not zero. It is not an empty string. It is the absence of a value.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local nickname = nil
local player = { name = "Maya" }
print(nickname)
print(player.title)
```

```txt
nil
nil
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 declares `nickname` holding `nil`, meaning no nickname chosen.
3. Line 3 builds a table with only a `name` key.
4. Line 4 prints `nil` for the empty variable.
5. Line 5 prints `nil` for the missing `title` key. Missing keys are `nil`, not errors.

Because only `nil` and `false` are falsy, you can guard with `if value ~= nil then` before using something that might not exist. And you delete a dictionary key by assigning `nil`, as you practiced in [Tables, Part 1](tables-part-1.md).

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local player = { name = "Maya" }
if player.title ~= nil then
	print(player.title)
else
	print("no title yet")
end
```

```txt
no title yet
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 builds a table with no `title` key.
3. Line 3 compares `player.title` against `nil`. Since the key is missing, the test is false.
4. Line 6 runs the `else` branch and prints `no title yet`.

## Compare with ==, carefully

`==` asks "are these the same value?" For strings, numbers, and [booleans](https://create.roblox.com/docs/luau/booleans), it compares content. For tables, it compares identity: two tables are equal only when they are the very same table, even if their contents match character for character.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local first = { "a", "b" }
local second = { "a", "b" }
local same = first
print(first == second)
print(first == same)
```

```txt
false
true
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 builds one table and stores it in `first`.
3. Line 3 builds a second, separate table with identical contents in `second`.
4. Line 4 copies the reference, so `same` points at the exact table `first` points at.
5. Line 5 prints `false`. Same shape, different tables.
6. Line 6 prints `true`. Same table, so equal.

Two more `==` traps catch beginners. First, `"5" == 5` is `false` because a string is never equal to a number. Convert with `tonumber` first. Second, `0.1 + 0.2 == 0.3` is `false` because of double fuzz. Compare the gap instead.

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local typed = "5"
print(typed == 5)
print(tonumber(typed) == 5)
local total = 0.1 + 0.2
print(total == 0.3)
print(math.abs(total - 0.3) < 0.0001)
```

```txt
false
true
false
true
```

Walkthrough:

1. Line 1 turns on strict mode.
2. Line 2 stores the string `"5"`.
3. Line 3 prints `false`. Text `"5"` and number `5` are different values.
4. Line 4 converts first, then compares, printing `true`.
5. Line 5 adds the fuzzy decimals.
6. Line 6 prints `false`. The sum is `0.30000000000000004`, not exactly `0.3`.
7. Line 7 measures the gap with `math.abs` and asks whether it is tiny. This prints `true` and is the safe way to compare decimals.

## Common mistakes

**Forgetting quotes around text.**

Run this to see it fail: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local city = Paris
print(city)
```

```txt
nil
```

`Paris` with no quotes is read as a variable. That variable does not exist, so strict mode flags it and it arrives as `nil`. Fix it with quotes. Run the fix the same way.

```luau
--!strict
local city = "Paris"
print(city)
```

```txt
Paris
```

**Expecting `..` to add spaces.**

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local line = "Hi," .. "Maya"
print(line)
```

```txt
Hi,Maya
```

This prints `Hi,Maya` with no space. Fix it by putting the space inside a quote. Run the fix the same way.

```luau
--!strict
local line = "Hi, " .. "Maya"
print(line)
```

```txt
Hi, Maya
```

**Comparing a string to a number.**

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local typed = "5"
if typed == 5 then
	print("five")
end
```

```txt
```

This never prints because `"5"` and `5` differ. An empty output is the clue: the condition was false. Fix it by converting first. Run the fix the same way.

```luau
--!strict
local typed = "5"
if tonumber(typed) == 5 then
	print("five")
end
```

```txt
five
```

**Calling two same-shaped tables equal.**

Run this: put it in a Script in ServerScriptService and press Play.

```luau
--!strict
local first = { "a", "b" }
local second = { "a", "b" }
if first == second then
	print("match")
end
```

```txt
```

This never prints because they are different tables. An empty output is the clue. Fix it by comparing contents entry by entry. Run the fix the same way.

```luau
--!strict
local first = { "a", "b" }
local second = { "a", "b" }
if first[1] == second[1] and first[2] == second[2] then
	print("match")
end
```

```txt
match
```

## Exercises

Run every solution below in a Script in ServerScriptService and press Play. Try each exercise yourself before you open its solution.

1. Store your first name. Print `"Hi, "` joined with your name and `"!"`, then print the length of the joined string.

::: details Solution 1

```luau
--!strict
local firstName = "Maya"
local line = "Hi, " .. firstName .. "!"
print(line)
print(#line)
```

```txt
Hi, Maya!
9
```

:::

2. Store `0x10` and `0b10` in two variables. Print both. Both should print decimal values.

::: details Solution 2

```luau
--!strict
local hex = 0x10
local bits = 0b10
print(hex)
print(bits)
```

```txt
16
2
```

Hex `0x10` is sixteen. Binary `0b10` is two.

:::

3. Build `{ title = "Rookie" }`. Print `title`. Delete it with `nil`. Print it again to show it is gone.

::: details Solution 3

```luau
--!strict
local badge = { title = "Rookie" }
print(badge.title)
badge.title = nil
print(badge.title)
```

```txt
Rookie
nil
```

:::

4. A player typed `"9"`. Convert it, add `1`, and check that the result equals `10`. Print the sum and the boolean. Then show that `0.1 + 0.2 == 0.3` is `false` but the tolerance check is `true`.

::: details Solution 4

```luau
--!strict
local typed = "9"
local asNumber = tonumber(typed)
local total = asNumber + 1
print(total)
print(total == 10)
local fuzzy = 0.1 + 0.2
print(fuzzy == 0.3)
print(math.abs(fuzzy - 0.3) < 0.0001)
```

```txt
10
true
false
true
```

Line 3 converts text to a number before math. Line 8 shows exact decimal comparison failing. Line 9 shows the tolerance check passing.

:::

## Summary

You wrote strings with quotes, joined them with `..`, and embedded quotes and line breaks with escapes. You read numbers as doubles with `0x`, `0b`, and underscore spellings, and you treated decimals with tolerance. You read `nil` as non-existence, guarded missing keys, and compared safely: convert before `==`, and never expect two separate tables to be equal.

Next: [Debugging Basics](debugging-basics.md).
