R Environment and Scope
An environment is a container where R stores variable names and their values. Scope defines which variables are visible from which locations in your code. Understanding how R finds variables — by searching environments in a specific order — prevents many subtle bugs.
What Is an Environment?
Think of environments as nested rooms:
┌─────────────────────────────────────┐
│ Global Environment (.GlobalEnv) │
│ x <- 10 │
│ my_func <- function() { ... } │
│ │
│ ┌──────────────────────────────┐ │
│ │ Function Environment │ │
│ │ (created when func runs) │ │
│ │ y <- 20 (local only) │ │
│ └──────────────────────────────┘ │
└─────────────────────────────────────┘
↓ parent
┌─────────────────────────────────────┐
│ Base Environment │
│ (built-in R functions: mean, etc.) │
└─────────────────────────────────────┘
Global vs Local Scope
x <- 100 # global variable
my_func <- function() {
y <- 200 # local variable (only inside function)
cat("Inside function — x:", x, "y:", y, "\n")
}
my_func()
# Inside function — x: 100 y: 200
cat("Outside function — x:", x, "\n")
# Outside function — x: 100
# cat("y:", y) → ERROR: object 'y' not found
Variable Lookup Chain
When R looks for a variable inside a function: 1. Check the function's own environment 2. Check the parent environment (where function was defined) 3. Check the global environment 4. Check package environments 5. Check the base environment 6. ERROR: object not found This chain is called the lexical scoping rule.
Local Variable Shadows Global
x <- "global"
demo <- function() {
x <- "local" # creates a NEW local x — global x unchanged
cat("Inside:", x, "\n")
}
demo()
# Inside: local
cat("Outside:", x, "\n")
# Outside: global ← global x is unchanged
The <<- Operator — Global Assignment from Inside a Function
counter <- 0
increment <- function() {
counter <<- counter + 1 # modifies the GLOBAL counter
}
increment()
increment()
increment()
print(counter) # 3
Use <<- cautiously. Functions that modify global state are harder to test and debug. Prefer returning values and reassigning outside the function when possible.
Environment Functions
environment() # current environment
globalenv() # the global environment object
baseenv() # the base environment
emptyenv() # the empty environment (top of chain)
parent.env(e) # parent of environment e
ls(envir=globalenv()) # list variables in global env
exists("x") # check if variable exists
get("x") # get value of variable by name (string)
assign("x", 42) # assign by name (string)
Creating Isolated Environments
# Create a new environment my_env <- new.env() # Assign variables in that environment my_env$score <- 95 my_env$name <- "Asha" # Access them my_env$score # 95 ls(my_env) # "name" "score" # Evaluate expression in a specific environment eval(quote(score + 5), envir=my_env) # 100
Function Closures
# A closure captures its enclosing environment
make_adder <- function(n) {
function(x) x + n # inner function captures 'n'
}
add5 <- make_adder(5)
add10 <- make_adder(10)
add5(3) # 8
add10(3) # 13
# add5 remembers n=5 even after make_adder() finished running
Checking Variable Environments
x <- 42
f <- function() { x }
environment(f) # the environment where f was defined
environmentName(globalenv()) # "R_GlobalEnv"
Scope and environments underpin every R program. Most bugs related to "variable not found" or "wrong value being used" come from misunderstanding which environment R is looking in. Functions, closures, and R6 classes all use environments as their core mechanism — understanding this topic unlocks a much deeper level of R mastery.
