R
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Data Structures
-
Section 4: Data Manipulation
-
Section 5: Visualization and Statistics
-
Section 6: Advanced R
-
Section 7: Practical Projects
-
Section 8: Interview Practice
-
Section 9: More Practice Exercises
-
Section 10: Shiny Apps in Depth
-
Section 11: More Data Wrangling
-
Section 12: Tidyverse Deep Dive
-
Section 13: Statistical Modeling Deep Dive
-
Section 14: Machine Learning in R
-
Section 15: R Visualization Deep Dive
-
Section 16: R Package Development Deep Dive
-
Section 17: R for Reproducible Research
-
Section 18: R and Databases
-
Section 19: R Performance Optimization
-
Section 20: Bioinformatics and Specialized R
-
Section 21: More Shiny Practice
-
Section 22: More Practice Exercises
-
Section 23: R Data Structures Deep Dive
-
Section 24: More Interview and Review
-
Section 25: R for Business Analytics
-
Section 26: R Text Mining and NLP
-
Section 27: R Spatial Data Analysis
-
Section 28: R Deep Learning
-
Section 29: Advanced Statistical Techniques
-
Section 30: R Object Systems Deep Dive
-
Section 31: R Environments and Metaprogramming
-
Section 32: R for Finance
-
Section 33: R for Clinical and Health Data
-
Section 34: More Shiny Advanced Practice
-
Section 35: R Data Cleaning Deep Dive
-
Section 36: R Reporting Automation
-
Section 37: More Practical Projects Round 2
-
Section 38: R Ecosystem and Career
-
Section 39: More Visualization Practice
-
Section 40: R for Experimentation
-
Section 41: R for Genomics and Bioinformatics Deep Dive
-
Section 42: R for Marketing Analytics
-
Section 43: R Data Import/Export Deep Dive
-
Section 44: R String Processing Deep Dive
-
Section 45: R for Actuarial and Insurance Analytics
-
Section 46: R Testing and Quality Assurance Deep Dive
-
Section 47: R Version Control and Collaboration
-
Section 48: R Advanced Functional Programming
-
Section 49: R for Supply Chain and Operations
-
Section 50: More Practice Exercises Round 3
-
Section 51: R Dashboards and BI Integration
-
Section 52: R Data Governance and Ethics
-
Section 53: More Modeling Practice
-
Section 54: R Final Capstone Projects
-
Section 55: R for Sports Analytics
-
Section 56: More Interview Practice Round 2
-
Section 57: R Networking and APIs
-
Section 58: R for Environmental Science
-
Section 59: R Notebook and Documentation Practices
-
Section 60: More Data Wrangling Mastery
-
Section 61: R for A/B Testing at Scale
-
Section 62: R Package Ecosystem Deep Dive
33: Environments and Closures in R
Imagine you're working in a large corporate office. You have your own desk where you keep your stapler, your notebook, and your coffee mug. When you need a pen, you first look on your desk. If it's not there, you don't just give up; you ask your direct supervisor. If your supervisor doesn't have one, they ask their boss, and so on, all the way up to the CEO. The CEO's office is the "Global Environment"—everyone eventually checks there if they can't find what they need anywhere else.
In R, this is exactly how environments and lexical scoping work. An environment isn't just a place where variables live; it's a package that contains both a set of bindings (the variables) and a pointer to a "parent" environment. When you call a variable, R searches the current environment, then the parent, then the grandparent, until it hits the Global Environment or throws an error.
The Chain of Command for Variables
Most of the time, you're working in the Global Environment, so this feels invisible. But once you start writing functions, R creates a new, temporary environment for that function. I like to think of this as the function "renting" a desk for the duration of its execution. Once the function returns a value, that desk is cleared and the environment is usually deleted.
# A simple look at how R searches
parent_env <- new.env()
parent_env$secret_code <- 42
child_env <- new.env(parent = parent_env)
child_env$my_name <- "Dev"
# This finds it in the child environment
print(get("my_name", envir = child_env))
# This doesn't exist in child_env, so R looks at parent_env
print(get("secret_code", envir = child_env))
Notice how child_env doesn't actually contain secret_code, but it can still "see" it. That's the power of the parent pointer.
Functions with a Memory
Now, here is where things get interesting: Closures. A closure is simply a function that "remembers" the environment in which it was created. If you return a function from inside another function, the inner function carries its birth-environment with it, even after the outer function has finished running.
It's like a manager giving you a specialized toolkit before they retire. Even though the manager is gone, you still have the tools they gave you, and those tools are tied to the specific settings the manager configured for you.
I use this pattern all the time when I want to create "factory" functions—functions that build other functions with specific behaviors without polluting my global workspace with a dozen different variables.
make_multiplier <- function(x) {
# This environment is created when make_multiplier is called
function(y) {
# This inner function 'closes over' the variable x
return(x * y)
}
}
# Create a function that always doubles
double <- make_multiplier(2)
# Create a function that always triples
triple <- make_multiplier(3)
print(double(10)) # 20
print(triple(10)) # 30
In the example above, double and triple are closures. They both use the same code, but they point to different environments. double points to an environment where x is 2, and triple points to one where x is 3. This is how we achieve "state" in R without relying on messy global variables.
Creating Persistent State
You can take this a step further by using the <<- operator. While <- creates a variable in the current local environment, <<- tells R to look up the chain of environments until it finds a matching variable to update, or creates it in the global environment if it finds nothing. In a closure, this allows you to create a variable that persists across multiple calls to the function.
make_counter <- function() {
count <- 0
function() {
count <<- count + 1 # Update 'count' in the parent environment
return(count)
}
}
my_counter <- make_counter()
print(my_counter()) # 1
print(my_counter()) # 2
print(my_counter()) # 3
If you tried to call print(count) in your console right now, you'd get an error. That's the beauty of closures: the count variable is "private." It's locked inside the environment that my_counter carries around, protected from the rest of your script.
📋 Practical Task
Building a Private Secret-Key Generator
Your goal is to create a system that generates "signer" functions. Each signer should be initialized with a unique secret key. The signer function should then take a message (a string) and return a combined "signed" string, but the secret key itself must remain inaccessible from the Global Environment.
Requirements:
- Create a function called
make_signerthat takes one argument:secret_key. make_signershould return an inner function that takes one argument:message.- The inner function should return a string in the format:
"[Message]: [SecretKey]". - Verify your work by creating two different signers (e.g.,
alice_signerandbob_signer) with different keys and ensuring they produce different signatures for the same message. - Try to print the
secret_keydirectly from the global environment to prove it is hidden.
There are no comments for now.