Environments & Scoping

An environment is the container that maps variable names to values, and scoping is the set of rules R follows to decide which environment a name is looked up in.

Learn Environments & Scoping in our free R course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free R course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

This lesson explains lexical scoping, how the global and function environments relate, the superassignment operator for reaching outward, why functions capture their defining environment as closures, and the new.env()/get/assign toolkit.

What You'll Learn in This Lesson

1️⃣ Local vs Global: Lexical Scoping

When a function runs, it gets its own environment. A variable assigned inside with is local — it shadows any global of the same name and vanishes when the function returns. The global variable is left untouched.

If a name isn't found locally, R doesn't fail — it searches outward to the environment where the function was defined.

2️⃣ Superassignment with <<-

The superassignment operator deliberately reaches outward : it searches enclosing environments for an existing variable and updates it in place, rather than creating a local copy. It's the tool for a function that needs to mutate state living in a wider scope.

3️⃣ Closures: Captured Environments

A closure is a function bundled with the environment it was defined in. Because that environment stays alive, each returned function "remembers" its own captured values — giving you private, persistent state without globals.

add5 and add10 are the same code but carry different captured environments — that's the whole idea of a closure.

4️⃣ The Environment Toolkit

Environments are real objects. new.env() makes an empty one, assign() and get() write and read bindings by name, exists() checks for a name, and environment() / globalenv() hand you references to them.

Your turn. Fill in the ___ blank, run it, and compare with the expected output.

The classic closure exercise. The account's balance lives in the enclosing environment and survives between calls — no global variable needed. deposit uses to update it.

📋 Quick Reference — Environments

Practice quiz

What kind of scoping does R use to resolve variable names?

  • Dynamic scoping (where the function is called)
  • Lexical scoping (where the function is defined)
  • Random scoping
  • No scoping at all

Answer: Lexical scoping (where the function is defined). R uses lexical (static) scoping: it looks where the function was defined.

Inside a function, what does a normal assignment x <- 5 affect?

  • A local variable in the function's environment
  • Always the global x
  • The base environment
  • Every environment at once

Answer: A local variable in the function's environment. Plain <- creates or updates a variable in the current (local) environment.

Which operator reaches OUTWARD to update an existing variable in an enclosing environment?

  • <-
  • =
  • <<-
  • ->

Answer: <<-. The superassignment operator <<- searches enclosing environments and updates the variable there.

If a name is not found locally, where does R look next under lexical scoping?

  • It immediately errors
  • In the calling function's environment
  • In a random environment
  • In the environment where the function was defined

Answer: In the environment where the function was defined. R searches outward through the enclosing (defining) environments.

What is a closure in R?

  • A function bundled with the environment it was defined in
  • A function with no arguments
  • A way to delete environments
  • A loop construct

Answer: A function bundled with the environment it was defined in. A closure captures its defining environment, giving it persistent private state.

Which function creates a new, empty environment?

  • env.new()
  • new.env()
  • make.env()
  • environment.create()

Answer: new.env(). new.env() returns a fresh empty environment object.

How do you write a binding named z into an environment e?

  • set("z", 42, e)
  • e.z <- 42
  • assign("z", 42, envir = e)
  • put(e, z, 42)

Answer: assign("z", 42, envir = e). assign("z", 42, envir = e) stores the binding in e; get() reads it back.

Reading a global variable inside a function (without assigning) does what?

  • Errors immediately
  • Modifies the global variable
  • Works fine because R searches outward for it
  • Creates a local copy automatically

Answer: Works fine because R searches outward for it. A function can read outer variables via lexical scoping; reading does not modify them.

Why does an inner function returned by make_adder(5) still remember n = 5?

  • Because n is global
  • Because of dynamic scoping
  • Because R copies n into a file
  • Because the closure keeps a live link to its defining environment

Answer: Because the closure keeps a live link to its defining environment. The captured environment holding n stays alive as long as the closure does.

Which function returns the environment a closure captured?

  • environment(f)
  • globalenv()
  • ls(f)
  • parent(f)

Answer: environment(f). environment(f) returns the environment associated with function f.