Global vs Local Scope
Distinguish between .GlobalEnv, function environments, and closures.
Global vs Local Scope is a free R Academy lesson on CoddyKit. This is lesson 3 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, and your progress syncs across the web and the CoddyKit app. The R Academy course includes 4 lessons in total.
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') # FALSElocal() 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 shadowfunction() 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 bindingassign('x', val, envir=e)for explicit environment-targeted assignmentlocal({...})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')Learn R with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 43
- Lessons
- 159
Frequently Asked Questions
Is the “Global vs Local Scope” lesson free?
Yes — any 3 lessons of the R Academy learning path, including “Global vs Local Scope”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The R Academy course includes 4 lessons in total.
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, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.
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
- What Is an Environment in R?
- Lexical Scoping Rules
- Global vs Local Scope
- Creating and Inspecting Environments