skip to content

Definite Assignment

The compiler proves every local is assigned on every path before it is read, which is why a variable assigned only inside an if will not compile. Interviewers pair it with final to discuss blank finals assigned in a constructor.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

What is definite assignment in Java, and why does the compiler reject reading a local variable that might be unassigned?

level: juniorimportance: must knowfreq 62%

answer

  1. Locals have no default; fields do
  2. DA = assigned on EVERY path before a read
  3. Compile-time, structural, conservative
  4. Constant expressions (true/false) are special-cased
  5. Only reads are constrained, not assignments

basics

~20 s

Definite assignment is a compile-time check that every local variable is given a value before you read it. If the compiler can find any path where the variable might not be set yet, it refuses to compile and reports a "variable might not have been initialized" error.

solid answer

~40 s

Definite assignment is a static analysis the Java compiler runs to guarantee that every local variable has a definitely-assigned value at the point it is read. Unlike fields, locals get no default value, so reading an unset local would expose garbage; the language forbids it at compile time instead. The compiler tracks, for each program point, whether a variable is "definitely assigned" along every possible execution path leading there. If any path could reach a read without an assignment, you get a compile error. This makes a whole class of uninitialized-variable bugs impossible. The analysis is conservative and path-based: it works on the structure of the code (if/else, loops, try/catch, switch), not on actual runtime values, so it can occasionally reject code that would in practice always assign.

go deeper

for a junior

Knows locals must be initialized before use and that the compiler enforces it, recognizes the "might not have been initialized" error.

for a middle

Can explain the every-path requirement, distinguishes locals from fields, and knows the check is compile-time and conservative.

for a senior

Articulates that the analysis is structural/path-based per JLS, special-cases constant expressions, and applies to blank finals; can predict when the compiler will reject seemingly-fine code.

for a principal

Frames it as a safety invariant of the type/flow system, contrasts with languages lacking it, and can discuss the trade-off between conservatism and false rejections, plus its interaction with final and exceptions.

## What problem this solves In Java a **local variable** (a variable declared inside a method, constructor, or block) does **not** receive a default value. Compare this with **fields** (variables declared at class level), which are automatically initialized to `0`, `false`, `null`, etc. If locals also silently defaulted, you could accidentally read a variable you forgot to set and get meaningless data. To prevent that entire bug category, the Java Language Specification (JLS, the official rulebook for the language) requires that a local variable be **definitely assigned** before any access that reads its value. ## What "definitely assigned" means At every point in the program, the compiler classifies a variable as either *definitely assigned* (DA) or *not*. A variable is DA at a given point if **every possible execution path** that reaches that point has already executed an assignment to it. "Every path" is the key phrase: it is not enough for *some* branch to assign the variable — *all* branches that can reach the read must. ```java int x; if (cond) { x = 1; } System.out.println(x); // ERROR: if cond is false, x is unassigned here ``` The `else` path (cond false) reaches the `println` without assigning `x`, so `x` is **not** definitely assigned there, and the compiler rejects it. ```java int x; if (cond) { x = 1; } else { x = 2; } System.out.println(x); // OK: every path assigns x ``` Now both branches assign `x`, so after the `if/else` it is definitely assigned. ## It is static and conservative The analysis runs at **compile time** and looks only at the *shape* of the code — the control-flow structure — never at the runtime values of variables. So it is **conservative**: it may reject code that, by human reasoning, would always assign the variable. ```java int x; if (true) { // a true constant: the compiler DOES special-case this x = 1; } System.out.println(x); // OK only because `true` is a constant expression ``` The compiler does evaluate **constant expressions** (`true`, `false`, `1 == 1`) when deciding reachability, but it will not reason about an ordinary boolean variable whose value it cannot know statically. ## Reading vs. assigning The rule only constrains a variable's **use as a value (a read)**. Assigning a value is always fine; declaring is fine. So: ```java int x; // declaration only — fine x = compute(); // assignment — fine int y = x; // read of x — requires x be definitely assigned (it is) ``` ## Where it applies Definite assignment governs **local variables** and **blank `final` fields/parameters** (finals with no initializer). It does **not** apply to ordinary non-final fields, because those have language-defined defaults. The rules cover all control structures — `if`, `while`, `for`, `do`, `switch`, `try/catch/finally`, the conditional operator `?:`, `&&`/`||`/`!`, `break`/`continue`/`return`/`throw` — each with its own JLS rule for how DA flows through it. ## Why it matters This check turns a common runtime mistake (using an uninitialized value) into an immediate compile-time error, with zero runtime cost. It is one of the reasons Java programs rarely exhibit the "reading garbage memory" bugs familiar from languages where locals are not checked.

  • Do fields need definite assignment too?
    Ordinary (non-final) fields don't — they get default values (0/false/null). Blank final fields and blank final locals/parameters DO require definite assignment before use, and final fields additionally require definite *unassignment* before assignment.
  • Why doesn't the compiler just default locals to 0/null like fields?
    It's a deliberate design choice: forcing explicit initialization catches "forgot to set it" bugs at compile time. A silent default would hide those bugs and make code intent ambiguous.

It's like a checklist at airport security: every passenger (every code path) must show a boarding pass (an assignment) before they reach the gate (the read). It is not enough that most passengers have one.

saying these in an interview costs you the question

  • Saying locals default to 0/null like fields — they don't, that's the whole point
  • Claiming the check inspects runtime values — it is purely static/structural
  • Thinking an assignment in just one if-branch is enough
  • Confusing definite assignment (must be set before read) with definite unassignment (final must NOT be set yet)

context

open as a page

How does definite assignment interact with final variables and blank final fields?

level: middleimportance: should knowfreq 48%

basics

~20 s

A final variable can be assigned exactly once. The compiler uses two flow checks: it makes sure a final is definitely assigned before you read it, and definitely UN-assigned (not yet set) before each assignment. Together these guarantee "assigned once, never reassigned."

open as a page

How does definite-assignment analysis handle loops, try/catch/finally, and statements that complete abruptly (return/throw/break)?

level: seniorimportance: should knowfreq 34%

basics

~20 s

The compiler models control flow. A loop body might run zero times, so assignments inside it usually don't count as guaranteed. A catch block could be entered after an assignment in the try only partly ran, so the try's assignments aren't trusted in catch. Code that always returns, throws, or breaks doesn't need to reach the rest, so those paths are excluded.

open as a page

Definite-assignment analysis sometimes rejects code that could never actually read an unassigned variable. Why is the language designed to be conservative this way, and what are the trade-offs?

level: principalimportance: nice to knowfreq 15%

basics

~20 s

The check looks only at code structure, not at what values variables hold at runtime. Deciding the exact set of reachable states is impossible in general, so the language uses a simpler, conservative rule that may reject a few safe programs but never accepts an unsafe one. You add an explicit initializer to satisfy it.

open as a page