R Assignment Operators: <- vs = vs <<- (Complete Guide)
R has three assignment operators: <- (the standard), = (also works but with edge cases), and <<- (assigns in the parent environment). Choose <- for almost everything; = only inside function calls; <<- only when you really need parent-scope.
x <- 5 # standard: assign x in current scope x = 5 # also works at script level (avoid in functions) x <<- 5 # assign in parent scope (rare; use carefully) assign("x", 5) # programmatic assignment (string name) mean(x = c(1, 2, 3)) # = is for FUNCTION ARGUMENT here, not assignment x <- y <- 5 # chained: assign 5 to both x and y -> # right arrow (rare): 5 -> x same as x <- 5
Need explanation? Read on for examples and pitfalls.
What R's assignment operators do in one sentence
<- is the canonical assignment operator; = works for most assignments but is reserved for function arguments by convention; <<- assigns in a PARENT environment, not the current one.
In modern R style guides (tidyverse, Google), <- is the recommended assignment operator. = is reserved for naming function arguments. <<- is for closures and is generally avoided in everyday code because it changes scope behavior.
The reason R has three operators (and a fourth right-pointing variant ->) is historical. Early S, R's predecessor, only had <-. The = was added later for compatibility with most other languages where = means assignment. R kept both because changing <- would have broken decades of existing code. The <<- operator was always for the special case of closures and function-level scope manipulation.
Knowing all three matters because you will encounter all three in real codebases. A package written in 2005 will use <- consistently; a package written in 2020 might use = for top-level assignment. Both are valid, but mixing them inside a single function body is a code-smell that makes the function harder to read.
Syntax
All three operators have the same shape: variable, operator, value.
There is also a right-arrow form: 5 -> x, equivalent to x <- 5. Rarely used; mostly for end-of-pipe assignment.
<-. This is faster than typing each character. Both VSCode and Positron offer similar shortcuts. The keyboard shortcut is the reason <- is barely slower to type than = in practice.Six common patterns
1. Standard assignment with <-
This is the form 99% of R code uses. Read as "the variable on the left gets the value on the right."
2. = for function arguments
Inside a function call, = names an argument: na.rm = TRUE means "the argument named na.rm gets the value TRUE". This is NOT assignment in the lexical sense; it is parameter binding.
3. = for variable assignment (works but discouraged)
= works for top-level assignment in modern R, but old code and most style guides prefer <-. The reason: = is overloaded with parameter binding inside function calls, which can confuse parsing in some edge cases.
4. <<- for parent-scope assignment
<<- assigns in the ENCLOSING (parent) environment, not the local one. Useful for closures (functions that "remember" state). Without <<-, count <- count + 1 would only update a local copy, leaving the outer count unchanged.
5. Chained assignment
a <- b <- c <- 100 is read right-to-left: 100 is assigned to c, then to b, then to a. All three end up with 100.
6. Programmatic assignment via assign()
assign("name", value) lets you set a variable whose name is computed at runtime. Useful when looping to create variables var1, var2, etc., though usually a list or data frame is cleaner.
<- is preferred for assignment because = has a SECOND meaning inside function calls. mean(x = 1:5) does not assign to x; it tells mean to use 1:5 as its x parameter. Mixing = for both purposes inside function definitions is legal but confusing. Using <- consistently for assignment leaves = exclusively for parameter binding.This single convention prevents a common reading error. When you see mean(x <- 1:5), you know x was just defined and mean is being called on it (a side effect). When you see mean(x = 1:5), you know x is the parameter name. Mixing the two inside one expression destroys this signal.
Most large R codebases (CRAN packages, Bioconductor, the tidyverse) follow this convention strictly. Adopting it early will make your code read like everyone else's, and other readers' code will read like yours.
R's three assignment operators compared
Each operator has a specific scope and a specific use case. The table below summarizes when to reach for which.
| Operator | Direction | Scope | Use case |
|---|---|---|---|
<- |
left arrow | Current environment | Standard assignment (recommended) |
= |
equals | Current environment | Function arguments (recommended); top-level assignment (works but discouraged) |
<<- |
super arrow | Parent environment | Closures, sometimes globals (use sparingly) |
-> |
right arrow | Current environment | End-of-pipe assignment (rare) |
assign() |
function | Specified environment | Programmatic name assignment |
When to use which:
- Use
<-for almost all assignment. - Use
=only inside function calls for argument naming. - Use
<<-only for closures or when you really need parent-scope. - Use
assign()when the variable name is built dynamically.
Common pitfalls
Pitfall 1: = inside if/while conditions. In some languages, if (x = 5) is a typo (should be ==). In R, this is an error because = cannot be used as assignment inside expressions like if(). Use == for equality comparison.
Pitfall 2: <<- creates global variables silently. If no parent environment has the variable defined, <<- walks up to the global environment and creates it there. Easy to leak state into the global namespace by accident. Use <<- only inside closures where parent scope is clear.
x = 5 at the top of a function definition WORKS but is style-violating. Inside a function, x <- 5 and x = 5 both create a local x = 5. But mixing = and <- inside the function body is confusing because = LOOKS like a function argument when reading code. Pick <- and stick with it inside function bodies.Pitfall 3: chained assignment with mixed types. x <- y <- "hello" works fine, but x <- y <- if (condition) 5 else "no" may evaluate the condition twice in some environments. Avoid complex expressions in chained assignment.
When to actually reach for <<-
The <<- operator is most defensible inside CLOSURES, where you intentionally maintain state between calls. The running-total example below is the canonical use case. Outside closures, <<- usually signals a code smell: you are reaching out of scope to mutate something that should have been a function argument or a list element.
A second legitimate use is inside local({ ... }) blocks where you want to update something defined in an outer environment, but a function with explicit return is almost always cleaner.
A third use is interactive package development where you want to push a value back to the global environment without restarting R. Here assign("x", value, envir = .GlobalEnv) is more explicit than <<- because it spells out the target environment.
If you find yourself using <<- inside a regular (non-closure) function for "state", refactor: pass the state as an argument and return it. Functions that mutate enclosing scope are hard to test, hard to reason about, and surprise readers.
A note on = for top-level assignment
x = 5 works at the script level. The R parser treats it as assignment if used in statement position (not inside a function call). Some R style guides allow it; tidyverse and Google explicitly prefer <-. If you write code that other people read, use <-. If you write throwaway scripts and prefer the shorter form, = is fine.
The one place = is clearly preferred is inside function calls for naming arguments: mean(x = 1:5, na.rm = TRUE). Using <- there would assign 1:5 to a variable named x AND pass that value as the first positional argument, which is technically legal but stylistically wrong.
Try it yourself
Try it: Write a closure (function that returns a function) that uses <<- to keep a running TOTAL across calls. Each call adds the input to the total and returns the new total.
Click to reveal solution
Explanation: total <<- total + x updates total in the ENCLOSING scope (running_total's body), not in the inner function's local scope. Without <<-, each call would create a new local total = 0 + x and the running sum would never accumulate.
Related R operators
After mastering assignment operators, look at:
==: equality comparison (different from=)assign()andget(): programmatic name accesslocal(): explicitly create a fresh environment<-vs:=(in data.table): data.table uses:=for in-place column assignmentwith()andwithin(): alternative to creating local copies
For functional programming patterns where assignment in parent scope feels needed, consider passing state as function arguments or using R6 classes.
FAQ
What is the difference between <- and = in R?
<- is the standard assignment operator. = works for top-level assignment but is reserved by convention for function arguments. Most R style guides recommend <- for assignment because it leaves = unambiguously for argument naming inside function calls.
Why does R use <- instead of = for assignment?
Historical reasons. Early S (R's predecessor) used <- to look like a left arrow. The = was added later for compatibility with most other languages. R retained <- as the canonical operator and = as a secondary option to avoid ambiguity with argument binding inside function calls.
What does <<- do in R?
<<- assigns in the PARENT environment, not the current one. Inside a function, x <<- 5 does NOT create a local x = 5; it updates an x somewhere in the enclosing scope, walking up to the global environment if needed. Used for closures and (occasionally) controlled global state.
Is = and <- always interchangeable in R?
No. They behave the same at script level for variable assignment, but = is also used to name function arguments (mean(x = 1:5) means "argument x equals 1:5"). Inside a function call, = is parameter binding, not assignment. <- always means assignment.
Should I use = or <- in R for assignment?
Use <-. Modern style guides (tidyverse, Google) recommend it for variable assignment. Reserve = for naming function arguments. The keyboard shortcut Alt+- in RStudio makes <- quick to type.