Variable Shadowing in Ruby: Don’t Call the Plumber!

Darragh O'Carroll M.D.
7 min readFeb 26, 2023
Photo by T K on Unsplash

If variable shadowing sounds mysterious to you, trust me you are not alone. Perhaps we are lucky the word shadowing connotes that this errorless bug can be hard to define, hard to find, and hard to weed out — especially if you don’t know it’s there.

What Is Variable Shadowing?

Variable shadowing occurs when the name of a local variable defined outside of a block is identicle to the name of that blocks parameter. A good way to spot it, or accidentally create it, is to know that for shadowing to occur there has to pipes ( | | ). Without pipes a shadow can’t be created — but don’t worry, you won’t need to call the plumber.

Example 1

Take the following code. We would like to output “RB101 is a terrific foundation!” five times. We are calling the times method on our local variable “a” and passing in the block contained by do…end on lines 4–6. The block has one parameter named “course”, which is the same name as our local variable “course” defined on line 2. This looks like it will work right?

When we run it, there is no error, but the output is this..

What happened here is variable shadowing. The name of our block parameter is same as our main scope local variable. Inside the block on lines 4–6 the integer 1, 2 … 5 was passed in as our argument instead of the variable “course”. Darn, but don’t call the plumber, it’s not the pipes that need fixing.

Solution

To solve variable shadowing either change the name of the local variable, or change the name of the parameter of the block. Below are examples of each.

The local variable was changed to “the_first_course”.
The name of the method parameter was changed to “_”, and this underscore is another way of saying we are not using any parameters.

The output of the above solutions will be what we want, which is the following.

Example 2

Say we have the names of four aspiring software engineers, Joe, Billy, Shawn, and Carter contained in an array called “list”. Three of them are enrolled in a software bootcamp, but only Shawn has enrolled in Launch School. We would like to iterate through this list of students with the each method and output to the screen where they are enrolled. What will the following code output?

Again, this code runs without errors. On line 6 the each method is invoked on the list array. The “if” conditional on line 7 is satisfied when we iterate through “Shawn”, and we should see the following output.

But thats not what this code outputs? Instead we get this…

WHAT happend to Shawn?! Did he un-enroll? Let’s break down what happened when we iterated through the list array. The parameter “name” did what we expected, it pointed to each successive index in the array starting with index 0, which is Joe. We thought our local variable course was passed in as the argument to our block, but because of local variable shadowing — it didn’t. When inside the block, “course” is not the main scoped local variable, but the name of the blocks parameter. This parameter “course” has yet to be defined, it has not been assigned an argument, or hasn't had a default value set — so therefore it has a value of nil. On line 8 when the puts method is invoked on course using string interpolation, and because course has a value of nil, there is no output.

Solution

After you spot it, we don’t have to remove the pipes so don’t call the plumber! All you have to do is either change the name of the main scoped variable, or in this case define the default value of the parameter. See below for an example of both.

Change the name of the main scoped local variable to “the_best_course”.
Set the parameter to a default of “Launchchool!”

What’s Similar to Variable Shadowing, but not shadowing?

There are many ways we can get bugs in our code that do not throw errors but still have unintended consequences. A common way is to use a non-mutating method when in fact, a muting method should have been used.

Example 3

In this example we don’t need to ouput where our aspiring software engineers are enrolled, but want to update our list, just to keep track of where everyone is enrolled.

Again we will use the each method to iterate through our list, and it’s only Shawn who is enrolled in Launch School.

When the if conditional is satified on line 6 we aim to add “: Launch School” to our list array at index 2, and all the other indexes should have “: Bootcamp” added to them. But the following is the contents of “list” when outputted on line 13.

Solution

What happened is not variable shadowing, but that concatenation is a non-mutating method. In order to change our list array permanently we have to use a mutating method such as << .

change + to <<

The result is the list array now contains where each student is enrolled.

Example 4

In this final example we want to applaud Shawn for making the correct choice. Take a look at the following code and predict will be the output from line 10.

On line 6 the local variable “student_name” is initialized to Shawn, and on line 8 it is invoked by the congratulate_student method. The key here to differentiate this from variable shadowing is

  1. There are no pipes
  2. The local variable shares the same name as the methods argument, not it’s parameter.

When the “student_name” variable is passed into the method, it is passed by reference, and the parameter “name” now points to the same object. On line 2 we really want to let Shawn know he made the correct choice, so we shout his name with all capital letters using the .upcase method. On line 3 we add the rest of our statement with the mutating << method, and on line 10 we expect “SHAWN made the correct choice!” to be our program’s output. So why does line 10 only output “Shawn”?

It appears we have a locally scoped variable that is not not accessible by our method, which is the same issue we see in variable shadowing. But this is not variable shadowing, this has more to do with variables acting as pointers and the reasignment that occurs on line 2. The reassignment occurs on line 2 when we capitalize shawns name with the non-mutating .upcase method, which is basically telling the method level “name” to point to a new object that is an all uppercase version of “name”. This new object is no longer the same one that “student_name” points to, so when the << method is invoked it’s added to this new method level object. Try outputting the object_id method on the “name” variable after the reassigment that occurs on line 2 — it will differ from the “student_name” object_id.

Solution

One solution is to avoid reassignment on line 2 and invoke the upcase method on student_name before it is passed to the congratulate_student method.

output

Another solution is to add a bang to the upcase method on the right side of line 2, which will mutate the object that “name” is pointing to. Then when moving left on line 2 “name” is reassigned, but is reassigned to an all uppercase mutated version of “name” at the same space in memory, and remains the same object throughout the method.

output

Shawn made the correct choice, and so did you!

--

--

Darragh O'Carroll M.D.

Emergency Medicine and Disaster Response physician, specializing in distilling complex medical topics to media digestible by all non-medical persons.