THE BLOG

Variable Scope in Ruby and JavaScript

August 1, 2015

Influenced by the curricula of various “coding bootcamps,” including Dev Bootcamp’s, the first “real” programming language I set about teaching myself was Ruby all the way back in early 2015. JavaScript quickly followed a month later. (In fact, my own autodidactic path—HTML/CSS, SQL, Ruby, JavaScript—closely mirrored Dev Bootcamp’s “Phase 0” curriculum with the exception of SQL, which I needed to learn earlier in the sequence to complete a database project.)

Ruby is very expressive, deceptively simple, and has a very straightforward syntax that often resembles English; in short, it’s a great language for a beginner to learn. As for JavaScript, well, it feels more computery and the syntax is definitely more difficult than Ruby’s (but still much easier than Polish syntax). This doesn’t make any less fun to work with, especially since JavaScript gives one the power to do lots of things on the web due to its virtual omnipresence across browsers.

As I’ve arrived at the point in my formal program of study where we’re shifting our focus from Ruby to JavaScript, this seems a good time as any to summarize for the world some of the key differences in how variable scoping works in the two languages. This isn’t quite meant as a true beginner’s guide, so you should be familiar with the basics of variable declaration and assignment before proceeding.

What Is Variable Scope? Global and Local Variables

The term variable scope refers to where a variable can be accessed within a program. If a variable has global scope, it can be accessed and modified anywhere and everywhere within a program. In contrast to global variables, variables with local scope can only be accessed in certain parts of the program.

Both Ruby and JavaScript give developers the tools to declare global variables, and both languages have built-in protections to help ensure that variables only scope locally when needed. (Without local variables, if we had a variable called index in multiple sections of a program or even across different files compiled together, they would potentially interfere with each other.) But the means by which Ruby and JavaScript accomplish local scoping vary, sometimes in subtle ways.

Variable Scope in the “Main” Program and Defined Methods/Functions

Both Ruby and JavaScript have ways to define methods or functions that are globally accessible. In Ruby, the keyword def is used to define a method; while in JavaScript, the function keyword is used to declare methods. What is curious (and seldom mentioned in the articles about variable scope that I’ve come across) is that they handle variable scoping in a subtly different fashion.

Let’s take an extremely simple example of a Ruby program with a defined method:

x = 1 
def add_one(num)
  return num + x
end

puts add_one(5)

If we try to run this program, then this will happen:

simple.rb:3:in `add_one': undefined local variable or method `x' for main:Object (NameError)
  from simple.rb:6:in `<main>'

The variable x, even though is it defined in main (the top level), is not accessible within the defined method #add_one.

Now let’s look at an analogous program in JavaScript:

x = 1;     // or var x = 1;
function addOne(num) {
  return num + x;
}
console.log(addOne(5));

When run, this program will successfully return the answer we want, the number 6. Since declared functions are taken out of the normal top-down workflow in JavaScript, we can assign the variable x below the declared function and get the same result:

function addOne(num) {
  return num + x;
}
x = 1;     // or var x = 1;
console.log(addOne(5));

These simple examples demonstrate that declared functions in JavaScript can look for and grab variables in the top level (and only the top level), whereas defined methods in Ruby only have access to local variables that are explicitly passed into them or local variables that are created within them.

This also reveals to us that variables declared in the top level of JavaScript are automatically global, whereas in Ruby they are not.

Of course, there are plenty of workarounds available in Ruby if we want to work with variables in main (e.g. using variables that have a larger scope such as global variables or instance variables, using a code block instead of defined methods, etc.), but this nonetheless is interesting behavior to note, especially because it has a significant effect on what needs to be done in each language to protect variable values from inadvertent change.

Protecting Values by Using Local Variables

Let’s return to our simplistic program and expand it to show what happens when we have two variables with the same name in different places. Here’s the Ruby version first:

x = 100
def add_one(num)
  x = 1
  return num + x
end

puts x           #=> 100
puts add_one(5)  #=> 6
puts x           #=> 100

Now that we’ve added x = 1 within the #add_one method, it will execute properly. Furthermore, note that it is properly scoped “automatically” and won’t interfere with the separate x we’ve created in main.

Now let’s see what happens when we write the same program in JavaScript:

var x = 100;    // or x = 100;
function addOne(num) {
  x = 1;
  return num + x;
}

console.log(x);         // 100
console.log(addOne(5)); // 6
console.log(x);         // 1

The first time we query for x, we get 100. But then the second time we query for x, we get back 1. This has occurred because as soon as we invoke the function addOne, its reassignment of x to 1 will spill into the higher scope (in this case, the global scope).

In order to prevent this from happening, we need to use the keyword var to declare the variable, which limits a variable’s scope to the current level (and any levels that occur below it):

var x = 100;
function addOne(num) {
  var x = 1;            // <-- Here's the change...
  return num + x;
}

console.log(x);         // 100
console.log(addOne(5)); // 6
console.log(x);         // 100

It’s helpful to think about the JavaScript keyword var as primarily a way to limit a variable’s scope. It also does a few other things such as create the variable (without assigning it any value) outside the normal document flow, prevents reference errors when JavaScript is run in strict mode, and stops variables from being deleted. That being said, variables in the global scope should always be declared with var anyway, as there is no harm in doing so, and this will prevent potential complications down the line.

“Lower-Order” Functions

Finally, to complete this introduction to variable scoping in Ruby and JavaScript, let’s turn our attention to “lower-order” functions (or “callback” functions as they’re usually called in JavaScript), that is, functions that are called from inside other functions. These rules for variable scoping actually apply to all kinds of different code blocks that occur inside other larger code blocks (except for things like while or for loops).

To make sure we can clearly see what’s going on, let’s define a higher-order function called doMath that’s just as puerile as our addOne function above. This doMath will add one to a number and then multiple it by two. First, we’ll turn to Ruby and use a somewhat advanced feature called a Proc in order to mimic the JavaScript code, which will follow, more closely:

def do_math(num)
  x = 1
  y = 2
  add_one = Proc.new { |number| number + x }
  return add_one.call(num) * y
end

puts do_math(5)  #=> 12

Because the Proc code block occurs within the #do_math method, it has no problem accessing x, despite the fact x is never assigned within in. The scope of the local variable x in #do_math encompasses everything that occurs between the method declaration and end (that is, all of its lower levels).

The punctuation-mark heavy JavaScript equivalent exhibits the exact same behavior:

function doMath(num) {
  var x = 1;
  var y = 2;
  var addOne = function(number) { return number + x };
  return addOne(num) * y;
}
      
console.log(doMath(5));  // 12

Even though x, which is declared locally within the higher-order function doMath, isn’t a global variable, any functions/methods further down the food chain can still see it and make use of it.

However, if we were to declare the variable x locally within the lower-order addOne function, the reverse would not be true. That is, the higher-order (or “parent”) function doMath would not be able to access this x. To wit, running this...

function doMath(num) {
  var y = 2;
  var addOne = function(number) {
    var x = 1;
    return number + x;
    };
  console.log(x);    // <--here's the error
  return addOne(num) * y;
}
      
console.log(doMath(5));

...will raise the following error: ReferenceError: x is not defined at doMath (simple.js:7:14) A similar error would occur in Ruby if x were created within the Proc and then referenced from either do_math or main.

Finally, let’s quickly examine a situation where there’s a locally declared x in both levels of doMath:

function doMath(num) {
  var x = "I'm safe!";
  var y = 2;
  var addOne = function(number) {
    var x = 1;
    return number + x;
  };
  console.log(x);
  return addOne(num) * y;
}
      
console.log(doMath(5));
// I'm safe!
// 12

Because a separate x is declared in the function expression addOne, the x in doMath is safe from harm. (However, if the var in front of var x = 1; had been omitted, it would not have been safe.) Again, Ruby scoping works in a similar fashion here, only you would not need to bother writing var in front of the x in the Proc—this local-level scoping would happen automatically. However, as a word of caution, there are numerous methods in Ruby that take code blocks, such as #each and other Enumerables, that will not shield a variable assigned in a higher level from changes (unless the variable name is used as a block parameter, i.e. found between the pipes in a do...end structure).

All the examples in this section are indicative of a general rule that holds true in both Ruby and JavaScript: local variables scope downward and are accessible by all lower-order code blocks and functions. However, if a separate variable that has the same name is declared in a lower-order code block in JavaScript or put in the block parameter in Ruby, then they create a new scope for the variable name that cascades downwards through any additional levels and protects the identically named variable in the higher-order function from alteration. Make sense?

Further Reading

Despite how long it took me to compose this post, I’ve only managed to scratch the surface of variable behavior in Ruby and JavaScript. There are more types of variables to explore in Ruby, and JavaScript has an extremely powerful technique involving variables called closure that’s the basis for a bunch of neat things on the modern web like asynchronous execution and event handlers. Look for coverage of these topics in a future post, or, failing that, in hundreds of other places across the web.