Local Variable Scope in Ruby

Josiah Fordahl
4 min readFeb 12, 2021

How local variables interact with method invocations, blocks, and method definitions.

What types of variables does Ruby have?

For the purpose of this article, a variable is just a label. It gives you, the programmer, the ability to name an object in your code.

Ruby has several types of variables, all defined by where they are accessible within your code.

  • Global Variables: “$global” ← (How the variable is declared.)
$variable = “I am available throughout your entire app”
  • Class Variables: “@@class” ← (How the variable is declared.)
@@class_variable = “I am accessible by instances of your class, as well as the class itself.”
  • Instance Variables: “@instance”← (How the variable is declared.
@instance_variable = “I am available throughout the current instance of this class.”
  • Constant Variable: “CONSTANT” ← (How the variable is declared.)
MY_CONSTANT = “Available throughout your app”
  • Local Variables: “local” ← (How the variable is declared.)
local_variable = “I must be passed around to cross scope boundaries, otherwise I obey all scope boundaries.”
  • Block Variables (a type of local variable): “|var1, var2|” ← (How the variable is declared.)
    Block variables are always scoped local to the block. This means scope is determined by the block so anything in between the “do..end” for multiline blocks, or the “{..}” (Curly Braces) for single line blocks.
  • * Block variables are also considered a form of local variables.*
def random_method do |var1,var2|  #inner scope starts after "do"
p ...
end # inner scope closed
# block scope is inaccessible here - var1 and var2 are not accessible and will generate an error if you try to access them.

What is variable scope?

Scope describes which variables and methods are accessible from any given point in code. Essentially this means where the ruby interpreter is while executing a given program. If you freeze frame that interpreter at any given point, what can it see? That is its scope. There will always be in scope and out of scope variables or methods at any given freeze frame of a program that is being run.

The scope of you variable is where in your program the variable is available for use. Where you create or initialize the variable determines where it will be accessible (the variable’s scope) by the program.

Inner scope can access variables initialized in an outer scope, but not vice versa.

The creation of new scope by the block only happens when the do/end or {..} is following a method invocation. Without a method invocation the do/end or {..} is not considered a block and therefore does not create a new inner scope for variables.

What is a local variable?

Local variables are local to the current scope, usually this means the current method. When current scope ends, local variables cease to exist.

In the code snippet below, we can see the second method (animal_noises) try to access a local variable declared in the first method (name_cat). Because “name” is local to name_cat, animal_noises cannot access “name” because it no longer exists.

How local variables interact with method definitions:

On line 7: # Here (verbiage) acts as a variable which in a method, we call an argument or parameter.

The (verbiage = “hi”) bit sets a default parameter. This is useful if you need a default value printed.

In this case we could simply print line 13 from above, without any custom parameter and it would default to printing “hi”. Line 12 prints our custom argument “goodbye” while line 13 prints our default value because we haven’t added a custom argument/parameter.

Line 12: greetings("goodbye")  # => "goodbye"
Line 13: greetings() # => "hi"

Alternatively, you can invoke greetings on line 13 without parenthesis:

Line 13: greetings   # => "hi" 

Generally, the latter syntax without parenthesis is convention in Ruby, when not passing in custom arguments. Having set a default parameter as (verbiage = “hi”) even if we pass no parameter

Variables as Pointers:

Variables point to objects. You can think of them as a label, but not as the object themselves. To demonstrate this, look at the following:

a = 17 
a.object_id => 42
puts a => 17
b = a
b.object_id => 42
puts b => 17

In the above case, the number 17 is the object, “a” and “b” are just variables pointing to the same object, and that is why they share the same object id.

Why does this matter? If you unintentionally reassign variables you can find your program behaving in unexpected ways. Consider the following:

a = 25
b = a
puts a => 25
puts a.object_id => 45
puts b => 25
puts b.object_id => 45
a = "hello"
puts a => "hello"
puts a.object_id => 36
puts b => 25
puts b.object_id => 45

In the above we reassign “a” as a string “hello”. When we print a we get that updated string object, including a new object id. The variable “b” is however still pointing at the original integer object.

--

--