Scope: where variables live
On This Lesson 11 topics
Lesson Overview
Two questions come up the first time you split a program into methods. The first: "I declared total in main; why can my other method not see it?" The second: "My method changed its parameter, so why is the variable in main still the same?" Both have the same root. Variables live in places, and a method works with its own private copies.
In the recipe-card picture: each time a cook follows a card, they get their own work surface. Nobody else's chopped onions end up there, and when the cook finishes the card, the surface is cleared. Scope is the rule about which surface a variable belongs to. Pass-by-value is the rule about what happens when you hand an ingredient to another card: you hand over a copy.
This lesson builds on Lesson 2 (parameters and arguments) and Module 9 (variables and types). The next lesson shows how Java actually stores those private work surfaces while methods run.
Learning Objectives
By the end of this lesson you will be able to:
- Define the scope of a local variable and a parameter.
- Predict whether a variable is visible at a given line, using the nearest enclosing braces.
- Explain why each method call gets fresh local variables.
- Explain that Java passes arguments by value and predict the output when a method changes a primitive parameter.
- Explain what is copied when the argument is a reference, and what that does and does not allow.
- Use a return value to get a changed result back to the caller.
Written Explanation
Scope: the braces rule
The scope of a variable is the region of code where its name can be used. For a local variable the rule is simple: from its declaration to the closing brace } of the block that contains it. The Java Language Specification says a local variable declared in a block is in scope for the rest of that block, and that a method parameter is in scope in the whole body of its method.
static void demo(int n) { // n is in scope in the whole method
int a = 1; // a: from here to the end of demo
for (int i = 0; i < n; i++) { // i: only inside the for
int square = i * i; // square: only inside this loop body
}
// i and square no longer exist here
}Consequences:
- A variable declared in a method is local to that method. Another method cannot see it, even if the names match. Two methods can each have their own
count, and they are unrelated. - A variable declared inside a loop or an
ifblock lives only in that block, assquaredoes above. - You cannot declare a second variable of the same name while the first is still in scope. That includes a parameter: declaring
int nin the body of a method that already has a parameternis an error.
Real compiler messages for these mistakes (JDK 21):
error: variable n is already defined in method f(int)
error: cannot find symbol
System.out.println(i); // i was declared inside the for
symbol: variable i
location: class Err
error: variable total is already defined in method main(String[])If you see "cannot find symbol" for a variable you are sure you declared, check the braces: you are probably using it outside its block.
Each call gets fresh variables
Local variables are created when the method runs and discarded when it returns. Calling the method again starts from scratch. This is why a counter inside a method never counts across calls:
static int counter() {
int count = 0;
count++;
return count;
}Calling counter() twice returns 1 both times: each call makes a new count that starts at 0. To carry a value from one call to the next, you must pass it in and return it, or (much later in the course) store it somewhere that outlives the method. Lesson 5 shows where each call's variables are kept.
Pass-by-value: methods get copies
When you call addOne(n), Java evaluates the argument and copies the value into the parameter. The parameter is a separate variable. Changing it changes only the copy.
The Oracle tutorial states it directly: primitive arguments are passed by value, and a change to the parameter inside the method is lost when the method returns. In its example the parameter is set to 10, yet the caller's variable still prints 3.
Recipe-card view: you give the second cook a photocopy of your shopping list. They can scribble all over the copy; your list is untouched.
This is the reason a method like this cannot change its caller's number:
static void addOne(int x) {
x = x + 1; // changes the copy only
}If the caller needs the new value, return it and let the caller assign it: n = addOneAndReturn(n);. That is the same compute-and-return habit from Lesson 2.
For the same reason you cannot write a method that swaps two int variables of its caller: it swaps its two copies and returns, and nothing outside changes.
What about references?
Values of type String and the other non-primitive types you will meet (arrays, objects) are held through references: the variable holds a pointer to the thing, not the thing itself. Java passes references by value too. What is copied is the reference, so the parameter and the caller's variable both point to the same object.
Two consequences, matching the Oracle tutorial:
- If the method uses the reference to change the object it points to, the caller sees the change, because there is only one object.
- If the method assigns a new object to the parameter, only the copy of the reference is redirected. The caller's variable still points to the original object.
String is immutable (its contents never change), so a method cannot modify the caller's String at all: operations such as toUpperCase() return a new String and leave the original alone. Arrays arrive in Module 15; the short example below is a preview so you can see both consequences. If it feels unfamiliar, skim it and come back after that module.
Examples
Example 1: the copy that changes (simple)
public class AddOne {
static void addOne(int x) {
x = x + 1;
System.out.println("inside: " + x);
}
static int addOneAndReturn(int x) {
return x + 1;
}
public static void main(String[] args) {
int n = 5;
addOne(n);
System.out.println("after: " + n);
n = addOneAndReturn(n);
System.out.println("assigned: " + n);
}
}Output (JDK 21):
inside: 6
after: 5
assigned: 6addOne increases its own x to 6, but n in main stays 5. Only when main assigns the returned value does n change.
Example 2: scope inside and across methods (practical)
public class Scope {
static int counter() {
int count = 0;
count++;
return count;
}
public static void main(String[] args) {
System.out.println(counter());
System.out.println(counter());
int total = 0;
for (int i = 1; i <= 3; i++) {
int square = i * i;
total += square;
}
System.out.println(total);
}
}Output:
1
1
14counter() prints 1 twice because every call has a fresh count. In main, total is declared outside the loop, so it survives all three iterations and ends at 1 + 4 + 9 = 14. square and i exist only inside the loop. Rule of thumb: declare a variable in the smallest block that needs it, but outside any loop whose result you need afterwards.
Example 3: swap, strings and a reference preview (realistic)
public class Swap {
static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("inside swap: a=" + a + " b=" + b);
}
static void shout(String text) {
text = text.toUpperCase();
System.out.println("inside shout: " + text);
}
static void fill(int[] data) {
data[0] = 99; // changes the shared object
}
static void replace(int[] data) {
data = new int[] {7, 7, 7}; // redirects only the copy
}
public static void main(String[] args) {
int x = 1, y = 2;
swap(x, y);
System.out.println("x=" + x + " y=" + y);
String s = "hello";
shout(s);
System.out.println(s);
int[] numbers = {1, 2, 3};
fill(numbers);
System.out.println(numbers[0]);
replace(numbers);
System.out.println(numbers[0] + " " + numbers.length);
}
}Output:
inside swap: a=2 b=1
x=1 y=2
inside shout: HELLO
hello
99
99 3swap really swaps its copies, but x and y are untouched. shout makes a new upper-case String that only the parameter holds; s is still hello. In the array preview, fill changes the one shared array, so numbers[0] becomes 99; replace points its own parameter at a new array and the caller's array is unchanged (still length 3, still starting with 99). The same mechanism gives both results: a copy of the value is passed, and for references the value is the address.
Backend Insight
Scope and pass-by-value are what keep a server's concurrent work safe. A web server may run the same method for many users at the same moment. Because each call has its own local variables, one user's total cannot leak into another user's calculation. Problems begin when code shares something that is not local: an object, or a variable that outlives the method. Much of later concurrency advice amounts to "prefer local variables and returned values".
Pass-by-value also shapes how you design APIs. A method that is meant to give back a new result should return it, rather than hope to modify its caller's variable. When a method does modify an object it receives (as fill did), that is a side effect and should be obvious from the method's name and documentation, because the caller's data changes from a distance.
Common Mistakes
Mistake 1: Expecting a method to change the caller's primitive
static void doubleIt(int x) { x = x * 2; }
...
doubleIt(n); // n unchangedReturn the result and assign it: n = doubleIt(n); after changing the method to return int.
Mistake 2: Using a variable outside its block
A variable declared in a loop, if or method vanishes at the closing brace. "cannot find symbol" is the compiler's way of saying the name is out of scope. Move the declaration to the outer block if you need it afterwards.
Mistake 3: Redeclaring a name still in scope
int total = 0; followed by int total = 1; in the same block, or a local named like a parameter, gives already defined. Assign instead (total = 1;) or pick a new name.
Mistake 4: Expecting values to persist between calls
A local int count = 0; resets on each call. Pass the value in and return it, or compute it in the caller.
Mistake 5: Saying "Java passes objects by reference"
Java passes everything by value. For objects, the value copied is the reference. That is why changing the object is visible to the caller, but reassigning the parameter is not.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. In one sentence, what is the scope of a variable declared inside a for loop body?
Show solution A1
A1. It exists from its declaration to the end of the loop body's block, and cannot be used after the closing brace.
A2. What does this print, and why?
static void reset(int v) { v = 0; }
public static void main(String[] args) {
int score = 42;
reset(score);
System.out.println(score);
}Show solution A2
A2. It prints 42. The method sets its own copy v to 0; the argument's value was copied, so score is unchanged.
A3. Is it legal for two different methods to each declare a local variable named count? Why?
Show solution A3
A3. Yes. Each is local to its own method, so they are unrelated variables that never clash.
Level B: Application
B1. Find and fix the scope error:
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
int doubled = i * 2;
}
System.out.println(doubled);
}Show solution B1
B1. doubled exists only inside the loop body, so println cannot see it (cannot find symbol). If you just want to print it each time, move the println inside the loop; if you need the last value afterwards, declare int doubled = 0; before the loop and assign doubled = i * 2; inside.
B2. Change reset from A2 so that main ends up with score equal to 0, without removing the method. Show both the method and the call.
Show solution B2
B2.
static int reset(int v) { return 0; }
// in main:
score = reset(score);B3. Predict the output (work it out by hand before running):
static int bump(int n) { n = n + 10; return n; }
public static void main(String[] args) {
int a = 1;
int b = bump(a);
System.out.println(a + " " + b);
}Show solution B3
B3. 1 11. bump changes its copy to 11 and returns it, which is stored in b, while a is still 1.
Level C: Challenge
C1. Write static int applyDiscount(int price, int percent) that returns the discounted price in whole currency units (use integer arithmetic, keep it simple). In main, call it so that price ends with the new value, and explain why the method could not have updated price by itself.
Show solution C1
C1. One possible answer (integer percent, truncating):
static int applyDiscount(int price, int percent) {
return price - price * percent / 100;
}
// in main:
int price = 200;
price = applyDiscount(price, 15); // 170price in main is a separate variable; the method only received a copy of its value, so it can only communicate back through its return value.
C2. Explain, in your own words and using the "photocopy" picture, why a method that receives a String cannot turn the caller's string into upper case, but a method that receives an array (preview) can change the caller's first element.
Show solution C2
C2. A String is immutable and the method only gets a copy of the reference, so toUpperCase produces a new string that the parameter can hold, but the caller's variable still refers to the old one. For an array, the photocopy is of the address: both lists point to the same array, and writing to element 0 through the copy changes the one real array the caller also sees.
Mini Challenge
Write a tiny "counter" program that needs a value to persist across calls without any global storage. Create static int nextTicket(int last) that returns last + 1. In main, keep a variable ticket, call ticket = nextTicket(ticket) four times and print it after each call. Then add a version, static void brokenNext(int last), that does last = last + 1; and calls it once; print ticket afterwards and write one sentence explaining the difference.
Check your work: the good version prints 1, 2, 3, 4; the broken one leaves ticket unchanged; and your explanation mentions the copy.
Summary & Next Lesson
Key takeaways
- A local variable's scope runs from its declaration to the end of its enclosing block; a parameter's scope is the whole method body.
- Each call creates fresh local variables, so nothing in a local variable survives between calls.
- Java passes every argument by value. For primitives the copy is the number; for references the copy is the reference.
- A method cannot change its caller's primitive variable. Return the new value and assign it.
- Changing an object through a copied reference is visible to the caller; reassigning the parameter is not.
Next lesson
You have seen that every call gets private variables. The next lesson, "The call stack", shows where Java keeps them: a pile of frames, one per active call, that grows as methods call methods and shrinks as they return.
Self-check
- Where does a local variable's scope end?
- After
addOne(n)(which doesx = x + 1on its parameter), what isn? - How can a method give a changed number back to its caller?
- What is copied when you pass a
Stringvariable to a method? - Why can
int count = 0;inside a method not count calls?
Show answers
- At the closing brace of the block in which it is declared.
- Unchanged, because the method changed only its own copy.
- It returns it, and the caller assigns the returned value.
- A copy of the reference (the address of the string), not a copy of the text itself.
- Each call creates a new
countthat starts at 0 and disappears when the method returns.
References
- Oracle, Passing Information to a Method or a Constructor. Verified: primitive arguments are passed by value and a change to the parameter is lost on return; reference arguments are also passed by value, changes through the reference to the object persist, and reassigning the parameter has no effect on the caller.
- Oracle, The Java Language Specification, SE 21, Chapter 6 (Names), scope of a declaration. Verified: a local variable's scope is the rest of its block, a method parameter's scope is the whole method body, and a parameter's name cannot be redeclared as a local variable.
- The immutability of
Stringis stated from general Java knowledge (the course covers it in Module 15); I did not fetch a page for it. All outputs and error messages come from compiling and running the code with JDK 21 (javac 21.0.10).
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.