Ruby Basics: Variables, Types, and Operators
Every Ruby program works with data. Before you can build anything useful, you need to understand how to store, access, and transform information. This tutorial covers the foundation every Ruby developer needs: variables, data types, and operators.
Intro context
These Ruby basics show up in every script, gem, and Rails app you will write. Once you understand how Ruby stores values and applies operators, the rest of the language becomes much easier to read. You no longer have to guess whether a value is a string, a symbol, or a number, because the code itself starts to signal that information.
That matters most when you are reading code written by someone else. A variable name can tell you the purpose, but the type tells you what operations are safe. When both are clear, you can move through a file quickly and spend less time tracing simple expressions.
Key takeaways
- Variables hold values, and Ruby chooses the type for you at runtime.
- Strings, numbers, booleans, symbols, arrays, and hashes solve different data problems.
- Operators let you compare values, combine conditions, and do arithmetic.
- The simplest Ruby code is often the clearest, so keep data choices obvious.
Variables in Ruby
Variables are named containers that hold values. Ruby uses dynamic typing, meaning you don’t need to declare a variable’s type explicitly.
# Simple variable assignment
name = "Alice"
age = 25
price = 19.99
puts name # Output: Alice
puts age # Output: 25
The examples above use simple names like name and age, but as programs grow, consistent naming becomes essential. Ruby follows community-wide conventions that make code predictable across projects and teams.
Variable naming conventions
Ruby has specific conventions for naming variables:
# snake_case for variables (all lowercase, underscores between words)
user_name = "Bob"
total_price = 100.50
is_active = true
# Constants start with an uppercase letter
MAX_SIZE = 100
API_KEY = "secret123"
Ruby uses snake_case for variable names and method names, and CamelCase for class and module names. Constants begin with an uppercase letter and are typically written in SCREAMING_SNAKE_CASE. These conventions are not enforced by the interpreter, but following them makes your code instantly recognizable to other Ruby developers.
Variable scope
Ruby variables have different scopes based on their naming:
# Local variables: lowercase or underscore
local_var = "I am local"
# Instance variables: start with @
@instance_var = "I belong to this object"
# Class variables: start with @@
@@class_var = "I am shared across the class"
# Global variables: start with $
$global_var = "I am available everywhere"
For beginner Ruby code, stick with local variables. You’ll learn about the others as you advance.
Variable scope determines where a variable can be accessed. Local variables are confined to the block or method where they are created. Instance variables belong to a single object and are visible throughout its methods. Class variables are shared across all instances of a class, and global variables are accessible from anywhere — though their use is discouraged in most Ruby code because they make programs harder to reason about.
Ruby’s data types
Ruby has several built-in data types. Here’s what you need to know:
Numbers
Ruby handles integers and floating-point numbers:
# Integers (no limit in Ruby)
count = 42
negative = -10
large_number = 1_000_000 # Underscores improve readability
# Floating-point numbers
price = 19.99
pi = 3.14159
# Basic arithmetic
sum = 10 + 5 # => 15
difference = 10 - 5 # => 5
product = 10 * 5 # => 50
quotient = 10 / 5 # => 2
remainder = 10 % 3 # => 1 (modulo)
power = 2 ** 8 # => 256 (exponentiation)
Ruby treats numbers as objects, which means you can call methods directly on numeric literals. Integer division truncates toward zero, and the modulo operator % returns the remainder. Ruby integers have arbitrary precision — they grow to whatever size the calculation requires, so you never need to worry about overflow.
Strings
Strings hold text. Ruby provides many powerful string methods:
# Creating strings
greeting = "Hello, World!"
single_quoted = 'Single quotes treat everything literally'
# String interpolation (double quotes only)
name = "Ruby"
message = "Welcome to #{name}!"
# => "Welcome to Ruby!"
# Common string methods
"hello".upcase # => "HELLO"
"HELLO".downcase # => "hello"
"hello".capitalize # => "Hello"
" spaced ".strip # => "spaced"
# String concatenation
first = "Hello"
second = "World"
combined = first + " " + second # => "Hello World"
combined2 = "#{first} #{second}" # => "Hello World"
Strings in Ruby are mutable sequences of characters. Double-quoted strings support interpolation with #{}, which embeds any Ruby expression directly into the text. Single-quoted strings treat everything literally and are slightly faster to parse. Ruby also provides a large set of string methods — upcase, downcase, strip, gsub, and many others — that let you manipulate text without writing manual loops.
Booleans
Boolean values represent true or false:
is_active = true
is_deleted = false
# Ruby treats nil and false as falsy, everything else is truthy
# This is important for conditionals
Boolean logic in Ruby is straightforward: true and false are the only boolean values, and only nil and false are falsy. Every other value — including 0, empty strings, and empty arrays — is truthy. That rule simplifies many conditionals, but it can also surprise developers coming from languages where zero or empty collections are falsy.
Symbols
Symbols are immutable strings often used as keys or identifiers:
# Symbols are denoted with a colon
status = :active
action = :read
# Symbols are ideal for hash keys
user = { name: "Alice", status: :active }
# Same as: user = { :name => "Alice", :status => :active }
Symbols are lightweight, immutable identifiers. Ruby uses them extensively for hash keys, method names, and status flags because comparing two symbols is faster than comparing two strings. Think of a symbol as a name that never changes — once created, the same symbol always refers to the same object in memory.
Arrays
Arrays store ordered collections of items:
# Creating arrays
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, true]
# Accessing elements (zero-indexed)
fruits[0] # => "apple"
fruits[-1] # => "cherry" (last element)
fruits[1..2] # => ["banana", "cherry"]
# Common array methods
fruits.length # => 3
fruits.push("date") # Add to end
fruits.pop # Remove from end
fruits.include?("apple") # => true
Arrays are ordered, zero-indexed collections. Use them when the position of each item matters — for example, when you need the first element or want to iterate in a specific order. Common operations include push to add to the end, pop to remove from the end, and bracket notation to access elements by index.
Hashes
Hashes store key-value pairs:
# Creating hashes
person = { name: "Alice", age: 30, city: "London" }
# Alternative syntax
person2 = { :name => "Bob", :age => 25 }
# Accessing values
person[:name] # => "Alice"
person[:age] = 31 # Update value
# Common hash methods
person.keys # => [:name, :age, :city]
person.values # => ["Alice", 30, "London"]
person.has_key?(:name) # => true
Hashes are the workhorse of structured data in Ruby. They let you look up values by name instead of by position, which makes the code self-documenting. When you see person[:city], you know immediately that the code is fetching a city from a person record. Combine hashes with symbols for keys, and you get a clean, fast lookup pattern that Ruby uses everywhere.
Operators in ruby
Operators let you perform operations on values:
Arithmetic operators
a = 10
b = 3
a + b # => 13 (addition)
a - b # => 7 (subtraction)
a * b # => 30 (multiplication)
a / b # => 3 (integer division in Ruby 2.x)
a.to_f / b # => 3.333... (float division)
a % b # => 1 (modulo - remainder)
a ** b # => 1000 (exponentiation)
Arithmetic in Ruby works the way you would expect from most programming languages, with one important exception: integer division. When both operands are integers, Ruby performs integer division and drops the remainder. If you need a decimal result, convert at least one operand to a float with .to_f before dividing.
Comparison operators
x = 5
y = 10
x == y # => false (equal)
x != y # => true (not equal)
x > y # => false
x < y # => true
x >= y # => false
x <= y # => true
Comparison operators return a boolean value, which makes them natural building blocks for conditionals and loop guards. The equality operator == checks whether two values are the same, while != checks for difference. Ruby also supports combined comparisons like >= and <=, which test two conditions in one operator.
Logical operators
a = true
b = false
a && b # => false (AND)
a || b # => true (OR)
!a # => false (NOT)
# Ruby also has keywords
a and b # => false
a or b # => true
not a # => false
Logical operators let you combine conditions into compound expressions. The && and and both perform logical AND, but they differ in precedence — && binds more tightly, so it is usually the safer choice in assignments and method arguments. The keyword forms and and or are best reserved for control flow like do_something and return.
Assignment operators
x = 5
x += 3 # => 8 (same as x = x + 3)
x -= 2 # => 6 (same as x = x - 2)
x *= 2 # => 12 (same as x = x * 2)
x /= 3 # => 4 (same as x = x / 3)
Assignment operators are shorthand that make numeric updates easier to read. When you see x += 3, you know the value of x is being increased by three without needing a separate line. Ruby provides these for addition, subtraction, multiplication, division, and several other operations.
The spaceship operator
Ruby has a unique comparison operator that returns -1, 0, or 1:
5 <=> 10 # => -1 (left is less than right)
10 <=> 10 # => 0 (equal)
10 <=> 5 # => 1 (left is greater than right)
# Useful for sorting
[3, 1, 2].sort { |a, b| b <=> a } # => [3, 2, 1] - descending
When to use what
- Local variables for temporary data in methods and scripts
- Symbols for hash keys and identifiers that won’t change
- Strings for user-facing text and data
- Integers for counting and exact arithmetic
- Floats for decimal calculations (money, measurements)
- Arrays for ordered collections
- Hashes for key-value data and lookups
When you are choosing between these types, think about the shape of the data first and the syntax second. A number is a count, a string is text, a symbol is a stable label, and a hash is a lookup table. If the data still feels awkward after you name it, that is usually a sign that the type choice needs another pass.
Ruby also rewards small, honest names. If a variable stores a price, call it price. If it stores a boolean, pick a name that sounds like a yes-or-no question. That small discipline makes conditionals easier to read later, especially once several values are moving through the same method.
Common mistakes
One of the easiest mistakes for beginners is mixing up strings and symbols when they are used as hash keys. Ruby treats them as different values, so user[:name] and user["name"] do not mean the same thing unless you wrote the hash that way on purpose. When in doubt, pick one style and stay consistent across the file or project.
Another common slip is assuming integer division will keep decimal precision. In Ruby, 10 / 3 returns 3, not 3.333..., because both values are integers. If you want a decimal result, convert one side to a float first. That tiny detail shows up often in totals, percentages, and measurements.
It is also worth watching for accidental truthiness checks. Only nil and false are falsy in Ruby. Empty strings, empty arrays, and zero are still truthy, so a condition like if value may behave differently than a newcomer expects. Knowing that rule makes conditionals easier to predict and easier to debug.
Frequently asked questions
Should I use symbols or strings for hash keys?
Use symbols when the key is part of your own Ruby code and is unlikely to change. Use strings when the key comes from external data, such as JSON, form input, or a file format that already uses text. The important part is consistency, not dogma.
When should I use a float instead of an integer?
Use integers for counts, indexes, and exact values. Use floats for measurements and calculations where fractional values matter. If you are working with money, be careful, because floating-point math can introduce rounding surprises. In that case, many Ruby programs use integers for cents or a decimal library instead.
Why does Ruby have so many value types?
Ruby keeps these types separate because each one solves a different problem. Arrays model ordered lists, hashes model lookups, strings model text, and symbols model stable identifiers. Once you know the role each type plays, it becomes much easier to choose the right one without overthinking it.
Conclusion
Understanding variables, types, and operators is essential for writing any Ruby code. These building blocks let you store data, perform calculations, and make decisions in your programs.
The most important habit to build is choosing a type that matches the shape of the data. If the data is a label, use a string or symbol. If it is a count, use an integer. If it is a collection, use an array or hash. That habit keeps later code simpler because the data already carries its own meaning.
When you are reading other people’s Ruby, these same ideas help you understand the code faster. A variable name tells you the role, the type tells you the shape, and the operators tell you how the data changes. That is enough to follow a surprising amount of Ruby code without getting lost.
Forward link
The next Ruby basics topic is control flow, where you learn how Ruby decides which branch to run and when to repeat work. If you want to keep building the language foundation after this page, move on to Control Flow: if, unless, loops and then return to Methods in Ruby to see how these data types are used inside real routines.
In the next tutorial, we’ll explore control flow — how to make your programs decide between different paths using conditionals and loops.
Next Tutorial: Control Flow: if, unless, loops