Marbles, and Buckets, and Bubbles… Oh My!

One metaphor I’ve found effective in understanding scope is sorting colored marbles into buckets of their matching color.

Imagine you come across a pile of marbles, and notice that all the marbles are colored red, blue, or green. Let’s sort all the marbles, dropping the red ones into a red bucket, green into a green bucket, and blue into a blue bucket. After sorting, when you later need a green marble, you already know the green bucket is where to go to get it.

In this metaphor, the marbles are the variables in our program. The buckets are scopes (functions and blocks), which we just conceptually assign individual colors for our discussion purposes. The color of each marble is thus determined by which color scope we find the marble originally created in.

Let’s annotate the running program example from Chapter 1 with scope color labels:

  1. // outer/global scope: RED
  2. var students = [
  3. { id: 14, name: "Kyle" },
  4. { id: 73, name: "Suzy" },
  5. { id: 112, name: "Frank" },
  6. { id: 6, name: "Sarah" }
  7. ];
  8. function getStudentName(studentID) {
  9. // function scope: BLUE
  10. for (let student of students) {
  11. // loop scope: GREEN
  12. if (student.id == studentID) {
  13. return student.name;
  14. }
  15. }
  16. }
  17. var nextStudent = getStudentName(73);
  18. console.log(nextStudent); // Suzy

We’ve designated three scope colors with code comments: RED (outermost global scope), BLUE (scope of function getStudentName(..)), and GREEN (scope of/inside the for loop). But it still may be difficult to recognize the boundaries of these scope buckets when looking at a code listing.

Figure 2 helps visualize the boundaries of the scopes by drawing colored bubbles (aka, buckets) around each:

Colored Scope Bubbles

Fig. 2: Colored Scope Bubbles

  1. Bubble 1 (RED) encompasses the global scope, which holds three identifiers/variables: students (line 1), getStudentName (line 8), and nextStudent (line 16).

  2. Bubble 2 (BLUE) encompasses the scope of the function getStudentName(..) (line 8), which holds just one identifier/variable: the parameter studentID (line 8).

  3. Bubble 3 (GREEN) encompasses the scope of the for-loop (line 9), which holds just one identifier/variable: student (line 9).

NOTE:
Technically, the parameter studentID is not exactly in the BLUE(2) scope. We’ll unwind that confusion in “Implied Scopes” in Appendix A. For now, it’s close enough to label studentID a BLUE(2) marble.

Scope bubbles are determined during compilation based on where the functions/blocks of scope are written, the nesting inside each other, and so on. Each scope bubble is entirely contained within its parent scope bubble—a scope is never partially in two different outer scopes.

Each marble (variable/identifier) is colored based on which bubble (bucket) it’s declared in, not the color of the scope it may be accessed from (e.g., students on line 9 and studentID on line 10).

NOTE:
Remember we asserted in Chapter 1 that id, name, and log are all properties, not variables; in other words, they’re not marbles in buckets, so they don’t get colored based on any the rules we’re discussing in this book. To understand how such property accesses are handled, see the third book in the series, Objects & Classes.

As the JS engine processes a program (during compilation), and finds a declaration for a variable, it essentially asks, “Which color scope (bubble or bucket) am I currently in?” The variable is designated as that same color, meaning it belongs to that bucket/bubble.

The GREEN(3) bucket is wholly nested inside of the BLUE(2) bucket, and similarly the BLUE(2) bucket is wholly nested inside the RED(1) bucket. Scopes can nest inside each other as shown, to any depth of nesting as your program needs.

References (non-declarations) to variables/identifiers are allowed if there’s a matching declaration either in the current scope, or any scope above/outside the current scope, but not with declarations from lower/nested scopes.

An expression in the RED(1) bucket only has access to RED(1) marbles, not BLUE(2) or GREEN(3). An expression in the BLUE(2) bucket can reference either BLUE(2) or RED(1) marbles, not GREEN(3). And an expression in the GREEN(3) bucket has access to RED(1), BLUE(2), and GREEN(3) marbles.

We can conceptualize the process of determining these non-declaration marble colors during runtime as a lookup. Since the students variable reference in the for-loop statement on line 9 is not a declaration, it has no color. So we ask the current BLUE(2) scope bucket if it has a marble matching that name. Since it doesn’t, the lookup continues with the next outer/containing scope: RED(1). The RED(1) bucket has a marble of the name students, so the loop-statement’s students variable reference is determined to be a RED(1) marble.

The if (student.id == studentID) statement on line 10 is similarly determined to reference a GREEN(3) marble named student and a BLUE(2) marble studentID.

NOTE:
The JS engine doesn’t generally determine these marble colors during runtime; the “lookup” here is a rhetorical device to help you understand the concepts. During compilation, most or all variable references will match already-known scope buckets, so their color is already determined, and stored with each marble reference to avoid unnecessary lookups as the program runs. More on this nuance in Chapter 3.

The key take-aways from marbles & buckets (and bubbles!):

  • Variables are declared in specific scopes, which can be thought of as colored marbles from matching-color buckets.

  • Any variable reference that appears in the scope where it was declared, or appears in any deeper nested scopes, will be labeled a marble of that same color—unless an intervening scope “shadows” the variable declaration; see “Shadowing” in Chapter 3.

  • The determination of colored buckets, and the marbles they contain, happens during compilation. This information is used for variable (marble color) “lookups” during code execution.