Topic 49 of 119 5. Methods in Java Interactive Tutorial

Scope in Java: Variable Lifetime and Access Boundaries

14 mins read Last updated: 22 Aug 2026 25,106 views

Direct Answer & Core Concept

Scope is the region of source code where a name is visible. If you write a variable name somewhere the compiler cannot see its declaration, that is a compile error, and the error usually names the problem exactly. Java keeps scope rules simple because blocks are the only structure that opens a new one, so { } is the whole story for locals. The four scopes you will meet are nested inside one another. Block scope covers a variable from its declaration to the end of its enclosing braces. Method scope is just block scope for parameters and locals living inside a method body. Class scope covers fields, whether declared static or not. And an instance scope is a field lifetime belonging to one object. The word people use loosely here is lifetime, and it is worth separating from scope, because they answer different questions. Scope is a source-level concept decided when you write the code: where can I type this name? Lifetime is about how long the value exists while the program runs. The scope of a local variable is a pair of braces, and its lifetime is a single run through that block. The scope of a field is the whole class, but its lifetime is as long as its object is reachable. Two behaviours follow from this that beginners rarely predict. A local variable declared in a loop body is recreated every iteration, so nothing carries over between turns. And a local declared inside a loop body is unavailable after the loop, because the block has already closed. Both surprises come from the same rule, and both are covered below alongside variable declarations.

Detailed Explanation & Mechanics

1) The Four Scopes

Each scope contains the one below it. A field is visible throughout its class; a local is visible only inside its block.

ScopeWhere the name is visibleTypical members
ClassEverywhere in the class, including other methodsFields, methods, nested types
InstanceThe same as class, but tied to one objectNon-static fields
MethodThe method body it is declared inParameters and locals
BlockFrom the declaration to the closing braceLoop variables, if locals, try-with-resources
Main.java
Run Online
public class ScopeDemo {
    private static int classScope = 100;
    // class scope, one copy
    private int instanceScope = 50;
    // class scope, one per object
    public void executeScopeTest() {
        int methodScope = 25;
        // method scope, this invocation
        if (methodScope > THRESHOLD) {
            int blockScope = 10;
            // block scope, only inside these braces
            System.out.println(blockScope + methodScope);
        } // System.out.println(blockScope);   // compile error, the block has closed
    }
}

Class scope and instance scope are not really two separate rules. Both fields are visible everywhere in the class. The difference is how many copies exist and how long each one lives, which is a lifetime question rather than a visibility one.

2) Scope and Lifetime Are Different Questions

Scope asks where may I type this name? Lifetime asks how long does the value exist? Keeping them apart makes both easier to reason about.


Diagram contrasting compile-time scope visibility with runtime variable lifetime across stack and heap
Scope is decided when you write the code. Lifetime is decided while the program runs. They often line up, but not always.

Kind of variableScopeLifetime
Local variableIts blockOne run through that block
Method parameterThe method bodyThat single invocation
Loop variableThe loop bodyRecreated each iteration
Instance fieldThe whole classAs long as its object is reachable
Static fieldThe whole classAs long as the class is loaded

Locals live on the stack while the method runs, and the stack frame is discarded when it returns, so the value goes with it. Objects live on the heap and survive their creator, which is why a method can hand an object back and it still works. The variable you typed may be long gone while the object it points at lives on, and that is exactly what happens whenever a method returns a new instance.

3) Declaration Point Matters

Block scope starts at the declaration, not at the opening brace. Before that line the name does not exist, which surprises people who assume the whole block is fair game.

Main.java
Run Online
public static void demoDeclarationPoint() {
    // System.out.println(later);   // compile error, not declared yet
    int later = 99;
    System.out.println(later);
    // fine from here to the end of the block
}

The same rule explains why you cannot use a loop variable after the loop. The body of a for loop is its own block, and it closes before the loop finishes.

4) The Loop Body Is a Separate Scope

Each iteration of a loop body runs in a fresh scope. Two consequences follow, and both are worth internalising because they are so often tested in interviews.


Diagram showing each loop iteration creating a fresh block scope with a new variable that is discarded afterward
A fresh block per iteration. Nothing declared inside one turn survives into the next.

First, a variable declared in the body cannot be read after the loop, because the body block has already closed.

Main.java
Run Online
public static void bodyNotVisibleAfter() {
    for (int i = 0;
    i < LIMIT;
    i++) {
        String label = "row " + i;
        // new String each iteration
        System.out.println(label);
    } // System.out.println(label);   // compile error, out of scope
}

Second, if you want a value to carry over between iterations, declare it outside the loop. That is also the honest signal, since a value that persists is by definition not part of one iteration.

Main.java
Run Online
public static void carriedBetweenIterations() {
    int carried = 0;
    // declared outside, so it survives the loop
    for (int i = 0;
    i < LIMIT;
    i++) {
        carried = carried + i;
    } System.out.println(carried);
    // compiles, and holds the running total
}

This is the difference between a local that is reassigned and one that is re-declared. Inside the body, int carried = NO_VALUE; throws the previous value away every iteration, so the accumulation silently does nothing.

5) Loop Variable Scope Across Loops

The loop variable declared in the header belongs to the loop and cannot be reused afterward, even if you declare a second loop with the same name. Separate blocks, separate scopes.

Main.java
Run Online
public static void separateLoopScopes() {
    for (int i = 0;
    i < LIMIT;
    i++) {
        System.out.println(i);
    } for (int i = 0;
    i < LIMIT;
    i++) {
        // a brand new i, not a redeclaration error
        System.out.println(i);
    } // System.out.println(i);          // compile error, both loops have closed
}

Two loops each using i is perfectly legal. Reading i after them is not. The while loop behaves the same way, except it declares nothing for you, so the variable must be declared before the loop to stay visible inside it.

6) Shadowing

Shadowing happens when a local shares a name with something already in an enclosing scope. The inner declaration hides the outer one for the rest of that block, and both remain legal.

Main.java
Run Online
public class ShadowingDemo {
    private int value = 10;
    // field
    public static int value = 20;
    // static field, same name
    public void shadowParameter(int value) {
        // The parameter wins here, so the fields are unreachable by bare name
        System.out.println(value);
        // 5, the parameter
        System.out.println(this.value);
        // 10, the instance field
        System.out.println(ShadowingDemo.value);
        // 20, the static field
    } public static void shadowLocal() {
        int count = 1;
        if (count > ZERO) {
            int inner = 2;
            int count = 3;
            // compile error, count is already declared above
            // Java forbids this, unlike JavaScript
        }
    }
}

The asymmetry is the important part. A local may shadow a field, because a field lives in a different namespace and both remain reachable via this.field or ClassName.field. A local may not shadow another local in the same block, because that would be a genuine duplicate declaration with no way to reach the first. This is stricter than some other languages, and it is a compile error rather than a silent overwrite.


Diagram of nested scopes where an inner local shadows an outer field and the field stays reachable through this
Shadowing hides the outer name but does not remove it. this.field still reaches the field.

7) Fields, this, and Visibility

Because a field has class scope, a method can refer to it without qualification. Inside an instance method this is implied, which is why value and this.value mean the same thing unless a local shadows the name. That is covered in the this keyword.

Scope decides whether a name compiles. It does not decide whether other code may use it. That is what access modifiers do, and the two are frequently confused.

ModifierReachable from
privateOnly the declaring class
default, no keywordSame package
protectedSame package, plus subclasses anywhere
publicEverywhere the class is visible

A private field still has class scope. Every method in the class can read it, and code in another class cannot, no matter how it tries.

8) Lambda Parameters and try-with-resources

Two places where beginners expect a new kind of scope are worth calling out, since both behave like block scope.

A lambda parameter is scoped to the lambda body only. The lambda expression gets its own parameter list, and those names end with the expression.

Main.java
Run Online
private static final int PROBE_VALUE = 15;
public static void lambdaScope() {
    int threshold = 10;
    java.util.function.IntPredicate check = value -> value > threshold;
    // threshold is visible inside the lambda because it is effectively final
    System.out.println(check.test(PROBE_VALUE));
    // System.out.println(check);   // compile error, the lambda itself is gone
}

A resource declared in try-with-resources is scoped to the try block, and it is closed automatically at the end of that block.

Main.java
Run Online
public static void resourceScope(String path) {
    try(java.io.BufferedReader reader = openReader(path)) {
        System.out.println(reader.readLine());
    } catch (java.io.IOException error) {
        System.out.println(error.getMessage());
    } // reader is closed and out of scope here
}

9) Common Mistakes

MistakeSymptomFix
Using a variable before its declarationCompile errorScope starts at the declaration, not the brace.
Reading a loop body local after the loopCompile errorDeclare it before the loop if it must survive.
Re-declaring a local in the same blockCompile errorRename one, or reuse the existing variable.
Re-declaring the loop variable nameFine, if in a separate loopEach loop has its own scope, so no conflict.
Shadowing a field with a localLegal but confusingUse this.field, or rename the local.
Assuming a field is private to a methodWrong visibility reasoningScope and access modifiers are separate rules.
Accumulating into a body localTotal stays at the last valueDeclare the accumulator before the loop.
Expecting a field to be per-invocationStale value across callsFields persist; locals are recreated each time.


Infographic of eight common Java scope mistakes with their symptoms and corrections
Eight scope mistakes. Seven of them are compile errors, so they cannot reach production.

Frequently Asked Questions

What is scope in Java?


Scope is the region of source code where a name is visible. Java has class scope for fields and block scope for locals, and block scope begins at the declaration rather than at the opening brace. Referring to a name outside its scope is a compile error, not a runtime failure.

What is the difference between scope and lifetime?


Scope is a source-level concept decided when the code is written: where the name may be used. Lifetime is a runtime concept describing how long the value exists. A local has block scope and a lifetime of one run through that block, while a field has class scope but lives as long as its object is reachable.

Can a variable be used before it is declared in its block?


No. Block scope starts at the declaration, so the name does not exist on any earlier line inside those braces. Java follows what is called the law of Demeter in this respect, allowing forward reference only for a field referenced through a simple name from a method body or initialiser, not from a constructor call or a local initialiser.

Why can I not use a variable declared in a loop body after the loop?


The loop body is its own block, and it closes on every iteration. Variables declared inside are discarded at the end of each pass, so the name is out of scope once the loop finishes. Declare the variable before the loop if it needs to persist across iterations.

What is variable shadowing in Java?


It is when a local shares a name with a field or an outer local, hiding it for the rest of the inner block. A local may shadow a field, which stays reachable through this.field or ClassName.field. A local may not shadow another local in the same block, since that is a duplicate declaration and a compile error.

Can I use the same variable name in two different for loops?


Yes, because each loop body is a separate scope. The second loop declares its own variable and there is no conflict. Trying to read that name after either loop is what fails, since both blocks have closed by then.

Is scope the same as visibility across classes?


No. Scope decides whether a name compiles within one file. Access modifiers decide whether other classes may use it, so a private field still has full class scope while being unreachable from anywhere else. The two rules are independent and both must be satisfied.

Where are lambda parameters and try-with-resources variables scoped?


A lambda parameter is scoped to the lambda body, and the lambda object itself is a local of the enclosing block. A try-with-resources variable is scoped to its try block and closed automatically at the end of it. Both follow the usual block rules.

Runnable Code Example: Scope in Java

Java 17+
Main.java
Run Online
// Demonstrates the four scopes, lifetime, and shadowing.
public class ScopeDemo {
    // CLASS SCOPE. One copy, living as long as the class is loaded.
    private static int totalRuns = 0;
    // CLASS SCOPE. One copy per object, living as long as the object is reachable.
    private final String label;
    private static final int THRESHOLD = 20;
    private static final int LIMIT = 3;
    private static final int STEP = 1;
    private static final int START = 0;
    private static final int NO_VALUE = 0;
    private static final int ROW_BASE = 10;
    private static final int THRESHOLD_MULTIPLIER = 3;
    private static final int BASE_AMOUNT = 100;
    private static final int BONUS_AMOUNT = 25;
    public ScopeDemo(String label) {
        this.label = label;
    } // METHOD SCOPE. Parameters and locals exist for this invocation only.
    public void runWithShadowing(int label) {
        totalRuns = totalRuns + STEP;
        // The parameter shadows the field, so the field needs this.
        System.out.println("parameter label: " + label);
        System.out.println("field label:     " + this.label);
    } // BLOCK SCOPE. The local below lives only inside these braces.
    public int blockScopedTotal(int seed) {
        int total = seed;
        if (total > NO_VALUE) {
            int bonus = BONUS_AMOUNT;
            // new block, new variable
            total = total + bonus;
            System.out.println("inside the block, bonus is " + bonus);
        } // System.out.println(bonus);   // compile error, the block has closed
        return total;
    } // EACH ITERATION GETS A FRESH SCOPE, so nothing carries over.
    public static void freshScopeEachIteration() {
        for (int i = START;
        i < LIMIT;
        i += STEP) {
            int doubled = i * THRESHOLD_MULTIPLIER;
            System.out.println("iteration " + i + " doubled is " + doubled);
        } // System.out.println(doubled);   // compile error, out of scope
        // System.out.println(i);         // compile error, both loops closed
    } // DECLARED OUTSIDE, so the accumulator survives the loop.
    public static int carriedBetweenIterations() {
        int carried = NO_VALUE;
        // outside the loop on purpose
        for (int i = START;
        i < LIMIT;
        i += STEP) {
            carried = carried + i;
        } return carried;
        // compiles, holds the running total
    } // A field persists across calls, unlike a local.
    public static void fieldOutlivesTheMethod() {
        totalRuns = totalRuns + STEP;
        int localThatDies = BASE_AMOUNT;
        System.out.println("total runs so far: " + totalRuns);
        System.out.println("local value now:  " + localThatDies);
        // localThatDies is gone when this method returns
    } // A lambda parameter is scoped to the lambda body only.
    public static void lambdaScope() {
        int threshold = THRESHOLD;
        java.util.function.IntPredicate check = value -> value > threshold;
        System.out.println("15 above threshold: " + check.test(BASE_AMOUNT / 5));
        // System.out.println(value);   // compile error, the lambda has ended
    } // A try-with-resources variable is scoped to the try block.
    public static void resourceScope() {
        try(java.io.StringReader reader = new java.io.StringReader("scoped text");
        java.io.BufferedReader buffered = new java.io.BufferedReader(reader)) {
            System.out.println("first line: " + buffered.readLine());
        } catch (java.io.IOException error) {
            System.out.println("read failed: " + error.getMessage());
        } // reader and buffered are closed and out of scope here
    } public static void main(String[] args) {
        ScopeDemo demo = new ScopeDemo("field-label");
        // The parameter below shadows the field of the same name
        demo.runWithShadowing(ROW_BASE);
        // BLOCK SCOPE
        System.out.println("block total: " + demo.blockScopedTotal(NO_VALUE));
        // FRESH SCOPE EACH ITERATION
        freshScopeEachIteration();
        // CARRIED ACROSS ITERATIONS
        System.out.println("carried total: " + carriedBetweenIterations());
        // FIELD PERSISTS ACROSS CALLS
        fieldOutlivesTheMethod();
        fieldOutlivesTheMethod();
        // LAMBDA AND RESOURCE SCOPES
        lambdaScope();
        resourceScope();
        // THESE ARE ALL COMPILE ERRORS AND ARE SHOWN AS COMMENTS
        //
        // using a name before its declaration in the same block
        // public static void usedTooEarly() {
        //     System.out.println(later);
        //     int later = 99;
        // }
        //
        // shadowing a local with another local in the same block
        // public static void duplicateLocal() {
        //     int count = 1;
        //     int count = 2;      // already declared in this block
        // }
        //
        // reaching a field from a static method through this
        // public static void thisInStatic() {
        //     System.out.println(this.label);
        // }
    }
}/*
    ACTUAL OUTPUT:

        parameter label: 10
        field label:     field-label
        total runs so far: 1
        inside the block, bonus is 25
        block total: 25
        iteration 0 doubled is 0
        iteration 1 doubled is 3
        iteration 2 doubled is 6
        carried total: 3
        total runs so far: 2
        total runs so far: 3
        local value now:  100
        15 above threshold: true
        first line: scoped text

    THE SEVEN THINGS TO REMEMBER
    1) BLOCK SCOPE STARTS AT THE DECLARATION, not at the opening brace, so
       a name does not exist on any earlier line inside those braces.
    2) SCOPE AND LIFETIME ANSWER DIFFERENT QUESTIONS. Scope is where the
       name may be typed. Lifetime is how long the value exists.
    3) EACH LOOP ITERATION GETS A FRESH SCOPE. A local declared in the
       body is discarded at the end of each pass and is unavailable
       after the loop.
    4) TO ACCUMULATE ACROSS ITERATIONS, DECLARE THE VARIABLE BEFORE THE
       LOOP. Re-declaring it inside the body throws the old value away
       every pass, so the total silently stays wrong.
    5) TWO LOOPS MAY EACH USE THE SAME NAME, because separate blocks mean
       separate scopes. Reading the name after them is what fails.
    6) A LOCAL MAY SHADOW A FIELD, which stays reachable through this.
       A LOCAL MAY NOT SHADOW ANOTHER LOCAL IN THE SAME BLOCK, which is a
       compile error rather than a silent overwrite.
    7) SCOPE IS NOT VISIBILITY. A private field still has full class
       scope; the access modifier decides whether other classes may
       use it. Both rules must be satisfied independently.
*/

Common Java Technical Interview Questions & Answers

Interview Prep
Q1: What is scope in Java and how many kinds are there?\nA1: Scope is the region of source code where a name is visible, and Java keeps it simple because blocks are the only construct that opens a new one. There are class scope for fields and block scope for locals, with parameters and loop variables being special cases of block scope inside a method body.\nQ2: How does scope differ from variable lifetime?\nA2: Scope is a source-level concept decided when the code is written, meaning where the name may be used, while lifetime is a runtime concept describing how long the value exists. A local has block scope but a lifetime of one run through that block, whereas a field has class scope and lives as long as its object is reachable.\nQ3: Can a variable be used before its declaration within the same block?\nA3: No, because block scope begins at the declaration rather than at the opening brace, so the name does not exist on any earlier line. Java allows a forward reference to a field through a simple name from a method body or instance initialiser, but not from a constructor call or a local initialiser, since that would read the field before it was assigned.\nQ4: Why is a variable declared inside a loop body unavailable after the loop?\nA4: The loop body is its own block, and that block closes on every iteration, so variables declared inside are discarded at the end of each pass. Two consequences follow: the name is out of scope once the loop finishes, and nothing declared inside one iteration carries over into the next.\nQ5: What is variable shadowing and is it always an error in Java?\nA5: Shadowing is when a local shares a name with a field or an outer local, hiding it for the rest of the inner block. It is legal for a field, which stays reachable through this.field or ClassName.field, but a local may not shadow another local in the same block, since that is a duplicate declaration and a compile error.\nQ6: Can the same variable name be used in two separate for loops?\nA6: Yes, because each loop body is a separate scope and the second loop declares its own variable rather than conflicting with the first. What fails is reading that name after the loops, since both blocks have closed by that point. The same applies to while loops, though those declare nothing for you.\nQ7: How do I keep a value across loop iterations?\nA7: Declare it before the loop rather than inside the body. Declaring it inside creates a fresh variable each iteration that discards the previous value, so an accumulator assigned there silently keeps only the last contribution. Declaring it outside also communicates the intent, since a value that persists is by definition not part of one iteration.\nQ8: Is scope the same thing as access visibility?\nA8: No. Scope determines whether a name compiles within a single file, while access modifiers such as private, protected, and public determine whether other classes may use the member. A private field still has full class scope, and both rules must be satisfied independently for a reference to compile.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Declare variables in the smallest block that works, which keeps the surface area for accidental use as small as possible.\nDeclare an accumulator before a loop rather than inside the body, otherwise it is silently re-created each iteration and the total stays wrong.\nAvoid shadowing fields with locals; rename one of them instead of relying on this.field at every use site.\nNever declare two locals with the same name in one block, since Java rejects it outright and a rename is the only fix.\nReuse a loop variable name across separate loops freely, as each body is its own scope, but never read it after the loop closes.\nName a variable for the narrowest scope it needs, rather than promoting a temporary to a field for convenience.\nKeep fields for state that genuinely belongs to the object across its whole life, not for values a single method computes.\nTreat scope and access modifiers as separate decisions, and check both when a member will not compile.\nPrefer a local over a field for anything that is only needed inside one method, which avoids accidental sharing between instances.\nDeclare a variable close to its first use rather than at the top of the method, so readers meet it at the point it matters.\nRemember that a field outlives the method that set it while a local does not, which explains most stale-value bugs.\nUse the narrowest lambda body that stays readable, since every captured variable becomes a requirement on its finality.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.