Explain flow scoping for instanceof pattern variables. Where is the binding variable in scope, and why does it work after && but not after ||?
answer
- In scope where match is guaranteed-true
- && right side: left was true → usable
- || right side: left was false → not bound
- Negated early-return → bound after the if
- Same engine as definite assignment
basics
~20 sThe pattern variable is in scope only where the compiler is sure the match was true. After && the match must hold for the right side to run, so it's usable there; after || the right side runs precisely when the match failed, so it isn't.
solid answer
~50 sFlow scoping means a pattern variable is in scope exactly at the points where the compiler can prove the instanceof test was true. It is not purely lexical — it follows control flow. In `obj instanceof String s && s.length() > 0`, the right operand of `&&` only executes when the left was true, so `s` is definitely a String there and is in scope. With `||` it's the opposite: `obj instanceof String s || s.isEmpty()` is illegal, because the right operand of `||` runs only when the left was *false* — meaning the match failed and `s` was never bound. Flow scoping also extends to places where the false case exits early: after `if (!(obj instanceof String s)) return;`, the rest of the method runs only when the match held, so `s` is in scope there. This is the same dataflow reasoning the compiler uses for definite assignment.
code
java · 15 linesObject obj = fetch();
// (1) && : s is in scope on the right operand and in the body
if (obj instanceof String s && !s.isBlank()) {
System.out.println(s.length());
}
// (2) || : ILLEGAL - s is not bound when the right operand runs
// if (obj instanceof String s || s.isEmpty()) { } // does not compile
// (3) negated early-return : s is in scope after the if
if (!(obj instanceof String s)) {
return;
}
System.out.println(s.length()); // s definitely bound herego deeper
Knows the variable is usable in the if-body and after &&, and that || doesn't work.
Can explain flow scoping in terms of short-circuit evaluation and the guaranteed-true rule, including the negated early-return pattern.
Connects flow scoping to the compiler's definite-assignment/reachability analysis and reasons about non-trivial control flow (throw/break/continue exits).
Can articulate the language-design rationale (scope = provably-matched region), how it composes with switch/record patterns, and edge cases like reassignment and shadowing.
## What flow scoping is Most variables in Java have **lexical scope**: they are visible inside the `{ }` block where they are declared, period. A **pattern variable** introduced by `instanceof` is different — it has **flow scoping**. It is in scope only at the program points where the compiler can *prove*, by analyzing control flow, that the instanceof match succeeded (and therefore the variable was definitely assigned). If the compiler cannot prove the match held, the variable is simply not in scope there and referencing it is a compile error. Think of it as: "the binding exists wherever the test is guaranteed-true." ## The `&&` case (in scope) The `&&` operator short-circuits: its right operand is evaluated **only if the left operand was true**. ```java if (obj instanceof String s && s.length() > 5) { ... } ``` When Java reaches `s.length()`, the left side `obj instanceof String s` must have been true — otherwise `&&` would have short-circuited and skipped the right side. So at that point `s` is guaranteed to be a bound String, and it's in scope. It is also in scope in the `if` body, for the same reason. ## The `||` case (NOT in scope) The `||` operator short-circuits the opposite way: its right operand is evaluated **only if the left operand was false**. ```java if (obj instanceof String s || s.isEmpty()) { ... } // COMPILE ERROR ``` When Java would reach `s.isEmpty()`, the left side was *false* — meaning `obj` was not a String and `s` was never bound. The variable doesn't exist there, so the compiler rejects it. This is not an arbitrary rule; it's a direct consequence of "the binding exists only where the match is guaranteed true." ## The negated early-return case (in scope after the if) Flow scoping also reaches *past* an `if` when the false branch leaves the enclosing scope: ```java static int len(Object obj) { if (!(obj instanceof String s)) { return -1; // bail out when NOT a String } return s.length(); // s is in scope here! } ``` The only way to reach the line after the `if` is for `!(... instanceof String s)` to be false — i.e. the match succeeded. So `s` is definitely bound from that point on, and it's in scope for the rest of the method. This is the idiomatic 'guard clause' style for patterns. The same applies with `throw`, `break`, or `continue` in the false branch. ## Why the compiler can do this Flow scoping uses the same **definite-assignment / reachability analysis** the Java compiler already performs (the analysis that, e.g., rejects reading a local before it's assigned). The compiler tracks, at each program point, whether the pattern variable is "definitely matched." ## A consequence: pattern variables can shadow / coexist Because a pattern variable's scope is precisely the matched region, you can reuse the same name in mutually exclusive branches, and a variable that is out of scope in one path doesn't conflict with one in another. ## Terms recap - **Lexical scope**: visibility tied to enclosing braces. - **Flow scoping**: visibility tied to where the match is provably true. - **Short-circuit**: `&&` skips the right side if left is false; `||` skips the right side if left is true. - **Definite assignment**: the compiler's proof that a variable has a value before it's read.
- Is `obj instanceof String s || s.isEmpty()` ever valid?No. After `||`, the right side runs only when the left match failed, so `s` was never bound; the compiler rejects the reference. You'd need `&&` (or restructure the logic) for `s` to be usable.
- Can a pattern variable be in scope after the closing brace of an if statement?Yes — if every path through the if's body where the match was false leaves the scope (return/throw/break/continue). Then reaching the code after the if implies the match held, so the variable is in scope.
saying these in an interview costs you the question
- Saying the variable is in scope everywhere in the enclosing block (it's flow-scoped, not lexical)
- Claiming `||` works the same as `&&`
- Forgetting that `if (!(obj instanceof T t)) return;` makes t usable afterward
- Thinking it's a runtime feature rather than compile-time analysis