0Pricing
R Academy · Lesson

Global vs Local Scope

Distinguish between .GlobalEnv, function environments, and closures.

Global vs Local Scope is a free R Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Local Variables in Functions

Variables assigned with <- or = inside a function body are local to that function. They are created in the function's own environment and disappear when the function returns. They never affect the calling scope.

x <- 'global'
f <- function() {
  x <- 'local'   # creates a new local binding, does not modify global x
  cat('Inside f: x =', x, '\n')
}
f()
cat('Outside f: x =', x, '\n')  # still 'global'

Function Parameters are Local

Function parameters are also local variables. Modifying a parameter inside the function does not affect the argument in the calling environment. R passes arguments by value (with copy-on-modify semantics).

double_it <- function(n) {
  n <- n * 2      # modifies local copy only
  cat('Inside:', n, '\n')
}
my_n <- 5
double_it(my_n)
cat('Outside:', my_n, '\n')  # still 5

<<- for Global Assignment

<<- (super-assignment) searches up the parent chain and assigns to the first match it finds. If no match exists anywhere, it creates the binding in the global environment. Use it sparingly — excessive use of <<- makes functions hard to reason about.

total <- 0
add_to_total <- function(x) {
  total <<- total + x
}
add_to_total(10)
add_to_total(25)
add_to_total(5)
cat('Total:', total, '\n')

<<- Searches Up the Chain

<<- does not blindly assign to the global environment — it walks up the parent chain and assigns to the first environment where the name already exists. If the variable exists in an enclosing function, that is where it is updated.

outer <- function() {
  n <- 0
  increment <- function() n <<- n + 1
  increment()
  increment()
  cat('n in outer:', n, '\n')  # n was in outer's env, updated there
}
outer()
# n does NOT appear in global env
cat('n in global:', exists('n', envir = globalenv()), '\n')

assign() for Explicit Environment Targeting

assign('name', value, envir = e) assigns to a specific environment directly. It is more explicit than <<- and is useful when you have an environment object and want to set a value in it precisely.

my_env <- new.env(parent = emptyenv())
assign('score', 95, envir = my_env)
assign('name', 'Alice', envir = my_env)
cat('score:', get('score', envir = my_env), '\n')
cat('name :', get('name',  envir = my_env), '\n')

local() — Isolated Code Blocks

local({...}) evaluates an expression in a fresh local environment. Variables created inside the block do not leak into the global environment. This is useful for temporary calculations that should not pollute the workspace.

result <- local({
  temp1 <- 100
  temp2 <- 200
  temp1 + temp2   # the last expression is returned
})
cat('result:', result, '\n')
cat('temp1 in global:', exists('temp1'), '\n')  # FALSE

local() vs. Function for Isolation

Both local() and an immediately invoked function provide scope isolation. local() is more concise for one-off isolation; a named function is better when you want the logic reusable or testable.

# Option A: local()
result_a <- local({
  x <- 7
  y <- 3
  x * y
})
# Option B: immediately invoked function
result_b <- (function() {
  x <- 7
  y <- 3
  x * y
})()
cat(result_a, result_b, '\n')
cat('x in global:', exists('x'), '\n')

Reading Global Variables from a Function

Functions can read variables from their enclosing scope (global or otherwise) without any special syntax. This is standard lexical scoping. However, relying heavily on global state makes functions harder to test — prefer explicit arguments when possible.

tax_rate <- 0.20   # global configuration
apply_tax <- function(price) {
  price * (1 + tax_rate)  # reads tax_rate from global
}
cat(apply_tax(100), '\n')
cat(apply_tax(250), '\n')

Variable Shadowing

When a local variable has the same name as a global one, the local binding shadows the global. Inside the function, the local version is used. The global is untouched. This can cause subtle bugs if done unintentionally.

mean <- 99   # shadows base R's mean() function
cat(mean, '\n')   # prints 99, not the function
# To call the real mean, use base:: prefix
cat(base::mean(c(1, 2, 3, 4, 5)), '\n')
rm(mean)   # remove the shadow

function() Environments

Every time a function is called, R creates a new environment for that call, with the function's enclosing environment as parent. Local variables from different calls do not interfere with each other, even recursive calls.

factorial_r <- function(n) {
  # Each recursive call has its own local n
  if (n <= 1) return(1)
  n * factorial_r(n - 1)
}
cat('5! =', factorial_r(5), '\n')
cat('7! =', factorial_r(7), '\n')

rm() to Remove Variables

rm('name') or rm(name) removes a binding from the current (or specified) environment. Use it to clean up temporary variables. Pass envir = e to remove from a specific environment.

x <- 42
y <- 'hello'
cat('Before rm:', ls(), '\n')
rm(x)
cat('After rm(x):', exists('x'), '\n')
rm('y')
cat('After rm(y):', exists('y'), '\n')

Quick Check

What does <<- do that regular <- does not?

Scope: Key Takeaways

Key takeaways for global vs local scope:

  • <- inside a function creates a local variable — global is unchanged
  • Function parameters are local; R passes by value (copy-on-modify)
  • <<- walks up the parent chain and modifies the first matching binding
  • assign('x', val, envir=e) for explicit environment-targeted assignment
  • local({...}) provides a throwaway scope without creating a named function
  • Avoid shadowing important names (like mean, c, T)
# Demonstrate local + <<- cleanly
running_sum <- 0
local({
  values <- c(10, 20, 30, 40)
  for (v in values) running_sum <<- running_sum + v
})
cat('Running sum:', running_sum, '\n')
cat('values in global:', exists('values'), '\n')

Frequently asked questions

Is the “Global vs Local Scope” lesson free?

Yes — the full text of “Global vs Local Scope” is free to read here on the web, and the R Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the R Academy course, upgrade to CoddyKit PRO.

What will I learn in “Global vs Local Scope”?

Distinguish between .GlobalEnv, function environments, and closures. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start R Academy?

No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Global vs Local Scope” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this R Academy lesson?

Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. What Is an Environment in R?
  2. Lexical Scoping Rules
  3. Global vs Local Scope
  4. Creating and Inspecting Environments
← Back to R Academy