Seven ideas the exam always hides inside one question.
Unit 1 is officially closed — C12 shut the door and LAB 2 built the family in Eclipse. But seven ideas from this unit keep coming back compressed into single exam lines: a range you must derive, a bit you must predict, a shift on a negative number, a reference that secretly shares an object, a variable that refuses to default, a stack that quietly overflows, and a field owned by no object at all. Today all seven — traced bit by bit, frame by frame, box by box.
Why one extra class? Because these seven are exactly the topics that get asked as "predict the output" one-liners — System.out.println(5 & 9);, -5 >> 1, b.cost = 13; — and a one-liner gives you nowhere to hide. Each of the seven gets the same treatment: the rule, tiny examples first, then the traps — with every intermediate step drawn on screen, never skipped.
This colour language never changes on this page. Learn it once, read every diagram for free.
PART 2 · WHAT TODAY DELIVERS
Seven compacted topics. One sitting.
This is a revision masterclass, not a first meeting — C2 introduced types, C9 introduced memory and static. Today compresses them to exam density and adds the three bit-level tools the syllabus assumes: two's complement, bitwise logic, and shifts.
BY THE END YOU CAN
- Derivethe range of ANY signed type from one formula — including a type that doesn't exist.
- Predict
a|b,a&b,a^band any shift — even on negative numbers — column by column, without guessing. - Tracewhat
b = areally copies for anintversus aCar— and prove it with one line of output. - Placeany variable into the right memory segment from its LOCATION alone — and predict which ones refuse to default.
- Reada recursive method as a stack of frames — and spot the missing base condition before the JVM does.
WHERE THIS SITS
TODAY'S ROUTE · 7 TOPICS · 17 STOPS
- T1 · Data types + the universal range formula — and the fictional
taptypeCORE - T1 · Two's complement — encode, decode, and the −8 that maps to itselfTRACE
- T2 · OR / AND / XOR — three truth tables, six worked examplesCORE
- T3 · Shifts
<<>>>>>+ negation~— incl. the −5 trapsTRACE - T4 · Value vs reference — the photocopy and the two-names personCORE
- T5 · The memory model — four segments, defaults, and the local that refusesCORE
- T6 · Recursion — frames that stack, a base that saves, a break that breaksTRACE
- T7 · Static — the field owned by the class, not the objectCORE
- Practice sets — 4–5 questions per topic, solutions locked until you tryNOTEBOOK
UNIT 1 ADD-ON · 17 PAGES · ALL CORE · EVERY EXAMPLE FROM THE MASTER PLAN IS ON THIS PAGE
TOPIC 1 · PART 3 · DATA TYPES & THE UNIVERSAL RANGE FORMULA
Don't memorize the range. Build it.
Java is statically typed — unlike Python, which guesses the type from the value, you must reserve memory up front. For whole numbers Java gives four choices, and the ONLY real difference between them is how much memory each one reserves.
S1 · THE MEMORY TABLE — WATCH THE DOUBLING: 1 → 2 → 4 → 8 BYTES
| byte | 8 | 1 byte · 8 bits | |||||||
| short | 8 | 8 | 2 bytes · 16 bits | ||||||
| int | 8 | 8 | 8 | 8 | 4 bytes · 32 bits | ||||
| long | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 bytes · 64 bits |
bits: 8 → 16 → 32 → 64 — the doubling is NOT a coincidence. It's how binary combinations scale.
THE FORMULA EVERY STUDENT MEMORIZES — AND FEW UNDERSTAND
Meet tap — a data type that does not exist. To de-mystify the formula we invent our own type: tap reserves just 4 bits (half a byte). Small enough to write EVERY possible pattern on one screen — that's the whole trick.
S2 + S3 · ALL 16 COMBINATIONS OF 4 BITS — THEN THE MSB SPLIT
MSB = 0 → POSITIVE (BLUE)
Eight patterns start with 0 → they keep their plain binary value: 0 to 7.
MSB = 1 → NEGATIVE (AMBER)
Eight patterns start with 1 → the designers reserved these for negative numbers. Their values need decoding — next part.
4 bits → 2⁴ = 16 possible combinations, 0000 to 1111. If we only wanted positives: 0000→0 … 1111→15, range 0 to 15 — and 15 = 2⁴ − 1. That's where the formula's right side comes from.
The MSB (Most Significant Bit) split is a HUMAN decision baked into hardware — not a mathematical necessity. Naming it lets you reason about ANY bit width, not just 4-bit toys.
If MSB 1 simply meant "negative", you'd expect 1000 to be −8 by direct conversion… but reading 1000 as plain binary gives +8, not −8. Something else must happen to the bits before conversion. That something is two's complement — next part.
TOPIC 1 · PART 4 · TWO'S COMPLEMENT
Two directions, one dance: flip & add, subtract & flip.
Two's complement is TWO distinct procedures — one to store a negative number, one to read a stored pattern back. Exams love mixing them up. We keep them apart with two cards.
① Write the binary of +N.
② Flip ALL bits (one's complement).
③ Add 1 (two's complement) → this is what memory stores.
① Confirm MSB = 1 (it IS negative).
② Subtract 1.
③ Flip ALL bits.
④ Convert to decimal, attach a minus sign.
S4 · WATCH ONE ENCODE — STORING −5 IN THE 4-BIT tap TYPE
S5 · EXAMPLE 1 — DECODE 1000 · THE FAMOUS TRAP
1000 is the MOST NEGATIVE value of a 4-bit type, and the boundary value of a signed range is always its own two's complement. Students hit this, assume they made an arithmetic slip, and redo it three times. You didn't slip. It cycles. Move on with confidence.
EXAMPLE 2 — DECODE 1001 · THE CLEAN CASE
TOPIC 1 · PART 5 · THE PROOF, THE CHALLENGE, THE TRAPS
The moment the formula stops being memorized.
S6 · EXAMPLE 3 — THE REGISTER AND THE ALGEBRA AGREE
WHAT THE 16 PATTERNS SHOWED
Examples 1 & 2 decoded the deepest two: 1000 = −8 (boundary) and 1001 = −7.
WHAT THE FORMULA SAYS FOR n = 4
Range of tap: −8 to 7. You didn't memorize it. You just proved it.
"8 combinations were negative" ↔ "−2^(n−1)". The register and the algebra are the same sentence in two alphabets.
S7 · EXAMPLE 4 — YOUR TURN: THE REAL byte · n = 8 · TYPE YOUR PREDICTION
CHALLENGEApply the formula to Java's real byte (n = 8) — type BOTH boundary values, then check against Byte.MIN_VALUE / Byte.MAX_VALUE.
min = max =
Work it in the notebook first: min = −2⁷, max = 2⁷ − 1.
MISCONCEPTION PATROL · THE THREE WRONG SENTENCES (T1)
"The left side of the range should also be 2^(n−1), without the minus… or 2^n − 1 like the max."
Only positive-only encodings top out at 2^n − 1. The signed negative side holds ONE extra value (−2^(n−1)) because the boundary pattern is its own two's complement — the single most confused point in this topic.
"1000 stayed the same after two's complement — I must have made a mistake."
Confirmed correct in Example 1: the boundary value of every signed range maps to itself. No mistake.
"I can read a pattern as plain binary even when MSB = 1."
That produces a wildly wrong LARGE POSITIVE value (1000 read raw = +8, truth = −8). Decoding is MANDATORY when MSB = 1 — and this exact trap returns in Topic 3's right-shift on negatives. Remember it.
PRACTICE · T1Notebook out — four questions, solutions locked below.
- A hypothetical type allocates 6 bits. State its range using the formula, then verify by writing the boundary bit patterns and decoding them manually.
- Decode the 8-bit pattern
11110110with the two's complement procedure (MSB check → subtract 1 → flip → convert → sign). - True/False with justification: "Every n-bit signed range contains exactly one more negative number than positive number (excluding zero)."
- A
shortis 16 bits. Without looking it up, derive its min/max from the formula and state both values.
Attempt all four on paper first — the derivation IS the learning.
- Q1n = 6 → min = −2⁵ = −32, max = 2⁵ − 1 = +31. Boundaries:
100000decodes to −32 (self-mapping boundary again!),011111= 16+8+4+2+1 = 31. ✓ - Q2
11110110: MSB = 1 → negative. Subtract 1 →11110101. Flip →00001010= 8 + 2 = 10. Attach sign → −10. - Q3True. Negatives: −1 … −2^(n−1) = 2^(n−1) values. Positives: 1 … 2^(n−1) − 1 = 2^(n−1) − 1 values. Exactly one more negative — the self-mapping boundary is the extra one.
- Q4n = 16 → min = −2¹⁵ = −32,768, max = 2¹⁵ − 1 = +32,767 — Java's documented
shortrange.
TOPIC 2 · PART 6 · BITWISE OR · AND · XOR
Three tiny tables run the whole show.
A bitwise operator takes two individual bits and produces ONE output bit by a fixed rule. Applied to full numbers, the rule runs independently at every bit position — rightmost with rightmost, next with next — with ZERO interaction between columns.
THE THREE TRUTH TABLES — PINNED FOR THE REST OF THE TOPIC
| A | B | A|B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
| A | B | A&B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
| A | B | A^B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Fastest recall hooks: OR — "any 1 wins" · AND — "any 0 kills" · XOR — "same → 0, different → 1".
| & are NOT || &&
Bitwise operators act on every bit position independently across the full binary representation. Logical operators (||, &&) act on a single true/false outcome. One character of difference, two completely different machines.
EXAMPLE 1 · THE BASELINE PAIR — a = 5, b = 9 · COLUMN BY COLUMN, PREDICT EACH BIT
| 2⁷ | 2⁶ | 2⁵ | 2⁴ | 2³ | 2² | 2¹ | 2⁰ | ||
| a = 5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | zero-padded to 8 bits — always |
| b = 9 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | |
| a | b | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | = 8+4+1 = 13 |
| a & b | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | = 1 — only 2⁰ had 1&1 |
| a ^ b | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | = 8+4 = 12 — 2⁰ was SAME so 0 |
In class: pick the output bit for each column BEFORE it lands. Green if you called it, red column if you didn't — then re-derive that one column from the table.
EXAMPLES 2 & 3 — SAME MACHINE, NEW NUMBERS
EXAMPLE 2 · a = 12, b = 10
EXAMPLE 3 · a = 7, b = 3
EXAMPLE 4 — a = 15, b = 6 · THE "ALL-ONES" PATTERN REVEALS ITSELF
| a = 15 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | all four low bits set |
| b = 6 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | |
| a | b | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | = 15 — every set bit survives |
| a & b | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | = 6 — b returned unchanged! |
| a ^ b | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | = 8+1 = 9 |
Pattern banner — not a coincidence: OR-ing with all 1s returns all 1s. AND-ing with all 1s returns the other operand unchanged. Within this 4-bit-low width, 15 = 1111 acts as the identity for AND — worth flagging, because interviews use it as a mask trick.
EXAMPLE 5 — a = 20, b = 5 · THE ANTI-GUESSING TRAP
| a = 20 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 00010100 |
| b = 5 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 00000101 |
| a | b | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | = 16+4+1 = 21 · NOT 25! |
| a & b | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | = 4 |
| a ^ b | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | = 16+1 = 17 |
20 + 5 = 25 sits close enough to 21 to hide a guesser. The result is NEVER computed from decimals — only bit by bit, then converted. That's why this pair is here.
EXAMPLE 6 — CODE VERIFICATION · PREDICT ALL THREE LINES BEFORE THE REVEAL
public class BitwiseCheck{ public static void main(String[] args) { byte a = 5, b = 9; System.out.println(a | b); // hand answer said 13 System.out.println(a & b); // hand answer said 1 System.out.println(a ^ b); // hand answer said 12 }}u1-addon folder — save → javac File.java (compile) → java File (run, no .java). No program today reads keyboard input, so no sample input is needed — outputs are fully determined by the code you type.MISCONCEPTION PATROL · T2
"| and & are just || and && written short."
Bitwise = per-bit across the whole representation. Logical = one true/false. Different machines.
"101 vs 1001 — I'll just line them up from the left."
Without zero-padding to EQUAL width, every column after the first is misaligned. Fixed-width padding first, always — the column table enforces it by design.
"20 | 5 should be around 25 — OR sort of adds them."
Example 5 exists to kill this: the answer is 21, not 25. No decimal arithmetic is ever involved — only bits.
PRACTICE · T2Four questions — full bit tables in the notebook.
- Compute
a | b,a & b,a ^ bfor a = 11, b = 6 — show the full bit table. - WITHOUT computing bit-by-bit: will
a & bbe smaller than, larger than, or equal to both a and b? Justify from the truth table, then verify with a = 25, b = 18. - True/False with justification: "
a ^ aalways equals 0, for any value of a." - A student claims
12 | 5 = 17because 12 + 5 = 17. Say exactly why the reasoning is wrong, then compute the correct answer.
Columns on paper first — the table is the technique.
- Q111 =
00001011, 6 =00000110→ OR =00001111= 15 · AND =00000010= 2 · XOR =00001101= 13. - Q2AND can only KEEP a 1 where BOTH have 1 — it can never create one. So
a & b≤ a and ≤ b, always. Check: 25 =00011001, 18 =00010010→ AND =00010000= 16 ≤ both. ✓ - Q3True. Every column compares a bit with ITSELF — always "same" — and XOR gives 0 for same bits. All columns 0 → result 0.
- Q4OR never adds — it works column-wise with no carries. 12 =
00001100, 5 =00000101→ OR =00001101= 13, not 17. (It LOOKS like addition here only because no columns overlap — 12 & 5 = 0. With overlap, e.g. Example 5, the illusion breaks.)
TOPIC 3 · PART 7 · SHIFT OPERATORS
Bits slide. The question is only: what fills the hole?
All three shift operators slide every bit sideways and discard what falls off the edge. The ENTIRE difference between them is the filler bit that enters the vacated end — and that one bit decides whether the sign survives.
| LEFT SHIFT << | RIGHT SHIFT >> | TRIPLE RIGHT SHIFT >>> | |
|---|---|---|---|
| Also known as | — | signed / arithmetic | unsigned / logical |
| Filler bit | always 0 | copy of the original MSB (sign-preserving) | always 0 — even for negatives |
| Meaning | × 2^k | floor-divide by 2^k, sign kept | sign DESTROYED on negatives → large positive |
| On positives | × 2^k | identical results | |
| On negatives | × 2^k | stays negative | becomes large positive |
| In Python | exists | exists | not supported (no fixed-width type) |
The "triple" refers to the three-character symbol >>>, not a shift count. a >>> 1 shifts exactly once. A naming trap, not a logic trap — but it costs marks every year.
LIVE REGISTERS · ONE PRESS, THREE MACHINES — WATCH THE FILLERS DIVERGE IN MOTION
ANIMATED · T3All three registers hold the SAME stored −5. Press SLIDE ONCE and watch every bit physically slide, the edge bit fall off, and each machine's own filler drop into the hole — blue 0, amber sign-copy, forced blue 0.
<< · LEFT SHIFT
filler enters on the RIGHT · always 0
Every slide doubles the value — sign and all.
>> · SIGNED RIGHT
filler = copy of the old MSB
The amber 1 keeps sliding in — the sign SURVIVES.
>>> · UNSIGNED RIGHT
filler forced to 0 — even here
One blue 0 in the sign seat — negative DESTROYED.
Same input, three destinies — press SLIDE ONCE to start the race.
EXAMPLES 1–3 · LEFT SHIFT — WATCH 5 DOUBLE, DOUBLE, DOUBLE
EXAMPLE 4 · RIGHT SHIFT ON A POSITIVE — 5 >> 1 AND 5 >> 2
TOPIC 3 · PART 8 · NEGATIVE OPERANDS — WHERE MARKS ARE WON
−5 >> 1 and −5 >>> 1 — same input, wildly different worlds.
These two walkthroughs carry the most misconception risk on the whole page. In class: predict the filler bit before every press. The full encode → shift → decode dance from Topic 1 comes back exactly as promised.
EXAMPLE 5 · −5 >> 1 — THE FULL THREE-STEP TRACE
EXAMPLE 6 · 5 >>> 1 — ON POSITIVES, >>> IS JUST >>
EXAMPLE 7 · −5 >>> 1 — THE OPERATOR'S DEFINING CASE · WATCH THE FILLERS DIVERGE
>> · FILLER = ORIGINAL MSB (1)
>>> · FILLER = ALWAYS 0
Same input: >> gave −3, >>> gave +125. One filler bit destroyed the sign. THAT is why >>> is called unsigned.
int width, -5 >>> 1 becomes a very large positive number close to 2³¹ (2147483645) — the mechanism is identical, only the width changes.EXAMPLE 8 · THE FLIP OPERATOR ~ (BITWISE COMPLEMENT) — ALL BITS SWAP 0↔1 AT ONCE, BOTH DIRECTIONS
~2 — POSITIVE GOES IN
~(−2) — NEGATIVE GOES IN
~ is NOT unary minus: ~2 = −3, but −2 = −2. The FLIP swaps EVERY bit, it never touches a "sign flag". The shortcut ~n = −(n+1) works in both directions — verify once by hand, then trust it.
FLIP = the ~ operator (bitwise complement): every bit swaps — all 0s become 1s and all 1s become 0s — and then you STOP. Result: ~n = −(n+1), so ~2 = −3.
NEGATION = unary minus -n (two's complement): FLIP every bit and then add 1. Result: -2 = −2’s stored pattern, i.e. -n = ~n + 1, so -2 = ~2 + 1 = −3 + 1 = −2.
One extra "+1" is the entire difference — flip stops after swapping the bits; negation swaps AND adds 1. In the bit machine below, the purple button does the FLIP only, and the red button performs the full NEGATE showing both steps separately. Drive both on the same number and watch them land one apart.
THE BIT MACHINE · DRIVE EVERY OPERATOR YOURSELF — PREDICT, PRESS, VERIFY
Load a number to begin. MSB seat is marked — when a 1 sits there, the decode dance from Topic 1 is mandatory.
⚠ WIDTH WARNING — this register is 8-bit for teaching clarity; a real Java int is 32-bit. For <<, >>, ~ and NEGATE the small-width answer matches Java exactly (on these small numbers). For >>> on a NEGATIVE it does NOT: the mechanism is identical but Java has 24 more bits, so e.g. -9 >>> 2 = 61 here but 1073741821 in real Java. The log line always shows BOTH — quote the 32-bit one in exams.
MISCONCEPTION PATROL · T3
"After shifting a negative, I'll just convert the bits to decimal."
The Topic-1 trap resurfaces exactly here, as promised. HARD RULE: if the post-shift MSB is 1, the two's-complement undo is NOT optional.
">> and >>> always give different answers."
They diverge ONLY on negatives. Examples 4 and 6 produced identical results on +5 — deliberately shown to stop the over-generalization.
"Triple right shift = shift three times."
Three CHARACTERS in the symbol, not three shifts. a >>> 1 shifts once.
"~ just flips the sign, like writing a minus."
~2 = −3 while −2 = −2 — different values, one keystroke apart. FLIP (~) swaps every bit and stops; NEGATION (-n) swaps every bit AND adds 1. -n = ~n + 1, always.
"Flip and negation are the same thing — both 'flip the bits'."
They differ by exactly +1. FLIP: ~5 = −6 (swap all bits, stop). NEGATION: -5 (swap all bits, then add 1). If your two answers are one apart, you used the wrong word.
PRACTICE · T3Five questions — Q5 is the checkpoint before Topic 4.
- Compute
9 << 2by hand, then verify it equals 9 × 4. - Compute
-9 >> 1with the FULL two's-complement decode shown at every step. - Compute
-9 >>> 1at 8-bit width, explain in ONE sentence why it differs from Q2's answer — then state what real 32-bit Java prints for-9 >>> 1. - Using ONLY the shortcut (no bit-flipping), compute
~7and~(-7)— then verify~7by flipping bits manually. - True/False with justification: "For any positive integer a,
a >> 1anda >>> 1always produce the same result."
Every step on paper — especially Q2's decode.
- Q19 =
00001001→ << 2 →00100100= 32 + 4 = 36 = 9 × 4 ✓ - Q2Encode −9: +9 =
00001001→ flip11110110→ +111110111. Shift (filler = 1):11111011. Decode: −1 →11111010→ flip00000101= 5 → −5. (Check: −9 ÷ 2 floors to −5 ✓) - Q3
11110111>>> 1 →01111011= 64+32+16+8+2+1 = 123 at 8-bit width. It differs because >>> forced a 0-filler, so the MSB became 0 and the sign was destroyed. Real 32-bit Java:-9 >>> 1= 2147483643 (and-9 >>> 2= 1073741821) — same mechanism, 32-bit width; THIS is the number an exam expects. - Q4~7 = −(7+1) = −8 · ~(−7) = −(−7+1) = 6. Manual check of ~7:
00000111→ flip11111000→ MSB 1 → −111110111→ flip00001000= 8 → −8 ✓ - Q5True. A positive number's MSB is 0, so >>'s "copy the MSB" filler IS 0 — identical to >>>'s forced 0. Checkpoint passed → Topic 4 unlocked.
TOPIC 4 · PART 9 · VALUE vs REFERENCE — THE VALUE HALF
What does = actually copy?
New cluster, fresh start — no bits required. One innocent symbol, two completely different behaviours: on a primitive, = copies the value; on an object variable, it copies the address. Getting this wrong is how programs silently corrupt each other's data.
Assigning a primitive is photocopying a page of notes. Once the copy is made, scribbling on the photocopy does NOTHING to the original — two physically separate pages that merely started out identical.
One person can carry many names — the birth-certificate name, a childhood nickname, what friends call them. If that person changes their hairstyle, EVERY name now refers to a person with the new hairstyle — because there was only ever ONE person.
EXAMPLE 1 · int a = 100 — WATCH THE VALUE GET "PRINTED", NOT LINKED
BOX a
BOX b
println(a) → 100 · println(b) → 2000. Two independent boxes. That's the entire value-type story.
EXAMPLES 2 & 3 · SAME RULE, NEW NUMBERS — AND NOT JUST FOR int
EXAMPLE 2 · int x, y
println(x) → 50 · println(y) → 999
EXAMPLE 3 · float p, q — the rule is NOT int-specific
println(p) → 3.5 · println(q) → 9.9 — every primitive behaves this way: int, float, char, boolean…
TOPIC 4 · PART 10 · THE REFERENCE HALF — ONE OBJECT, MANY NAMES
b was the only one we touched. a changed anyway.
Now the same = on a class type. A variable of a class type NEVER holds the object — it holds the object's address. Copy the variable, and you copy the address: two names, one object. Examples 4→8 build the full proof on one Car.
EXAMPLES 4 & 5 · CREATE THE CAR — DEFAULTS FIRST, VALUES SECOND
class Car{ String name; float mileage; int cost;}// ...inside main:Car a = new Car();System.out.println(a.name);System.out.println(a.mileage);System.out.println(a.cost);a.name = "BMW";a.mileage = 5.5f;a.cost = 75;System.out.println(a.name + " " + a.mileage + " " + a.cost);EXAMPLES 6 & 7 · THE SECOND NAME — AND THE PROOF
Car b; // reference ONLY — no new, NO object createdb = a; // copies the ADDRESS — b now names the SAME carSystem.out.println(b.name + " " + b.mileage + " " + b.cost);b.name = "Tata";b.mileage = 15.8f;b.cost = 13;System.out.println(b.name + " " + b.mileage + " " + b.cost);// the critical check — read through the FIRST name:System.out.println(a.name + " " + a.mileage + " " + a.cost);Car b; created NO object — no constructor ran, no memory for fields was allocated. Only new creates objects. A reference alone is an empty name tag.EXAMPLE 8 · A THIRD NAME — THE PATTERN GENERALIZES · WATCH THE PURPLE PULSE
REFERENCES LANE (names)
OBJECTS LANE (the one car)
println(a.cost) → 999 · println(b.cost) → 999 · println(c.cost) → 999. ANY number of references can share one object; a change through any one is visible through all.
Purple pulse = "same object, many references" — the purple returns in Topic 7 for the static zone, where sharing becomes official.
The literal 1000 is meaningless in isolation. Held by an int, it's a value. Held by a reference, it's an address pointing at an object. The meaning lives in the variable's TYPE, never in the number itself.
MISCONCEPTION PATROL · T4
"= always makes an independent copy — objects included."
For primitives yes; for objects, = copies the ADDRESS. That contrast is the entire topic.
"a and b are two separate objects that currently hold equal values."
Example 7 breaks this: a was never touched, yet a's output changed. There was only ever one object.
"Car b; creates a new Car."
No object exists without new — no constructor, no field memory. Just an empty name tag. (Topic 5 shows exactly where that tag lives.)
"1000 is obviously a value / obviously an address."
Depends ENTIRELY on the holding variable's kind — int-type or reference-type. Never on the number.
PRACTICE · T4Four questions — draw the boxes and arrows for each.
int p = 7; int q = p; q = 42;— final values of p and q? Explain in ONE sentence why.- Given
class Point { int x; int y; }andPoint m = new Point(); m.x = 5; m.y = 10; Point n = m; n.x = 99;— what ism.x? Explain with "one object, many references". - True/False with justification: "
Car b;by itself creates a new Car object in memory." - References p, q, r all point to the same
Employee. A change is made top.salary— what happens toq.salaryandr.salary? Justify via what = copies for reference types.
Boxes for values, arrows for references — always draw before answering.
- Q1p = 7, q = 42. q got an independent PHOTOCOPY of p's value; overwriting the copy never touches the original.
- Q2
m.x= 99.n = mcopied the address — m and n are two names for ONE Point, so the write through n is visible through m. - Q3False. Only
newtriggers object creation.Car b;makes an empty reference — no constructor call, no field memory. - Q4Both
q.salaryandr.salaryshow the new value instantly — = copied the same address into all three, so all three read the one shared object.
TOPIC 5 · PART 11 · THE JAVA MEMORY MODEL — WHERE EVERYTHING ACTUALLY LIVES
Your program can't run from the hard disk. Ever.
Topic 4 kept saying "the object lives at an address" — but WHERE? Time to name the rooms. A saved .java file on disk is just text at rest; the moment Java loads your program into RAM, it carves out one dedicated region — the Java Runtime Environment (JRE) — and splits it into four segments. Every variable you will ever write lives in exactly one of them.
FROM DISK TO RAM — THE LOADING STORY, PIECE BY PIECE
HARD DISK (storage — nothing runs here)
RAM (the only place code runs)
THE FOUR SEGMENTS — PINNED FOR THE REST OF THE CLASS (T5 → T6 → T7 ALL LIVE HERE)
CODE SEGMENT
The compiled program instructions themselves — your methods' bytecode sits here, read-only, while it runs.
STATIC SEGMENT
Static members live here — one shared copy per class. We flag it now and give it its own full topic (T7, the bonus).
HEAP SEGMENT
Objects — and EVERY instance variable belonging to them. When new runs, the memory comes from here.
STACK SEGMENT
Method calls in progress, local variables, and reference variables. Yes — references live HERE, not next to their object.
THE ONE RULE THAT CLASSIFIES EVERY VARIABLE — LOCATION, NOT NAME, NOT TYPE
INSTANCE VARIABLE
Declared directly inside a class, NOT inside any method. Allocated on the Heap. The JVM auto-fills it with a type default (null / 0 / 0.0 / false) the moment the object is created.
LOCAL VARIABLE
Declared directly inside a method. Allocated on the Stack. Gets NO default value — read it before assigning and the compiler refuses to build your program at all.
The test is purely visual/structural. A variable's name or data type NEVER decides its classification — only where the declaration physically sits. And since every Java line lives inside some class, "inside a method" always also means "inside a class" — the distinguishing question is specifically: is it inside a METHOD body or not?
EXAMPLE 1 · THE CLASSIFICATION PUZZLE — INSTANCE OR LOCAL?
int a; float b; char c; boolean d;EXAMPLE 2 · THE FULL DOG TRACE — CODE FIRST, THEN THE MEMORY MOVIE
class Dog{ String name; String breed; int cost;}class Demo{ public static void main(String[] args) { Dog d = new Dog(); System.out.println(d.name); System.out.println(d.breed); System.out.println(d.cost); d.name = "Scooby"; d.breed = "Pug"; d.cost = 10000; System.out.println(d.name + " " + d.breed + " " + d.cost); }}name, breed, cost sit directly inside class Dog, not inside any method → instance variables by the location rule.THE 7-STEP MEMORY MOVIE — EXACTLY WHAT new Dog() DID
main is always the FIRST method to execute in a Java program — it's already running on the stack.new Dog(). The new keyword activates the JVM's object-creation process.class Dog, finds its instance variables (name, breed, cost), and allocates memory for all three on the HEAP segment.name = null, breed = null, cost = 0. This filling step is called initialization.=. Reference d is created — references are NEVER on the heap or static segment; they always live on the STACK segment.d receives the heap address of the new object (conceptually, 1000) — d now refers to that object.d.name, d.breed, d.cost confirms the step-4 defaults: null null 0.HEAP · addr 1000
STACK · main's frame
d itself never moved, never changed, still holds 1000.TOPIC 5 · PART 12 · LOCAL VARIABLES — NO DEFAULTS, NO MERCY
The compiler would rather kill the build than guess a value.
Instance variables got free defaults. Local variables get nothing — and it's not a runtime surprise, it's a compile-time refusal. Same four variables as Example 1, now placed inside main:
EXAMPLE 3 · FOUR LOCALS, FOUR COMPILE ERRORS — ONE AT A TIME
public static void main(String[] args){ int a; float b; boolean c; double d; System.out.println(a); System.out.println(b); System.out.println(c); System.out.println(d);}THE STACK VIEW — RED HATCHING MEANS "NO VALUE EXISTS", NOT "VALUE IS ZERO"
STACK · main's frame · BEFORE THE FIX
The broken state: System.out.println(a); with a unassigned → variable a might not have been initialized. The read is the crime — declaring alone is legal.
The fix — assign before reading: a = 99; b = 99.99f; c = true; d = 100.99; — note the f suffix on 99.99f: a bare 99.99 is treated as a double, causing a type mismatch against a float variable.
THE FIXED FILE — SAME FOUR LOCALS, NOW PRINTABLE
a = 99; b = 99.99f; // the f suffix — bare 99.99 is a double c = true; d = 100.99; System.out.println(a); System.out.println(b); System.out.println(c); System.out.println(d);EXAMPLE 4 · THE PUZZLE RESOLVED — SAME FOUR LINES, OPPOSITE VERDICTS
DIRECTLY INSIDE A CLASS, NO METHOD
class Sample { int a; float b; char c; boolean d; }DIRECTLY INSIDE A METHOD (main)
public static void main(...) { int a; float b; char c; boolean d; }EXAMPLE 5 · THE RULE GENERALIZES — MEET Book
class Book { String title; String author; double price; }b1.title → null (String)b1.author → null (String)b1.price → 0.0 (double — new type, same numeric-default pattern as int's 0)EXAMPLE 6 · THE MIXED-SCOPE TRICK — SAME NAME, BOTH ROLES
class Counter{ int value; // instance — lives on the HEAP once an object exists void reset() { int value; // local — lives on the STACK, only during this call value = 0; // this local 'value' does NOT touch the instance variable of the same name }}value is judged purely by where it is declared: line 3 sits directly inside the class → instance, heap-allocated. Line 6 sits directly inside a method → local, stack-allocated, temporary — it vanishes when reset() returns. This example exists specifically to break anyone still classifying by NAME instead of by LOCATION.static — and re-runs this exact three-way classification game.MISCONCEPTION PATROL · THE FOUR WRONG SENTENCES (T5)
"I can tell it's an instance variable from its name / its type."
The classification rule is purely about declaration LOCATION. Examples 1, 4 and 6 exist to break this — Example 6 uses the very same name in both roles.
"Locals get defaults too — printing an unassigned int just gives 0."
Example 3 disproves this with an observable COMPILE error on all four variables — not a silent 0/false/null at runtime. The build simply fails.
"The reference is stored on the heap, next to its object."
References are ALWAYS created on the Stack, regardless of where the object lives — step 5 of the Dog trace, and the whole point of the cross-segment arrow.
"It's declared inside main's class, so it's an instance variable."
The test is about being inside a METHOD body or not — main is itself a method, so anything declared directly inside main is LOCAL, even though main sits inside a class.
PRACTICE · T5Five questions — draw the four segments before answering each.
- Classify each variable as instance or local, justifying with the location rule only:
class Wallet { double balance; void topUp() { double amount; amount = 50; } } - A learner writes
int score; System.out.println(score);inside a method and expects0to print. What actually happens, and why? - True/False with justification: "An object's instance variables are allocated at the same time as its reference variable, in the same memory segment."
- Given
class Player { String name; int lives; }andPlayer p = new Player();— state the exact default of each field immediately after this line, before any further code. - Explain in one or two sentences why main's local variables can share a name with a class's instance variables without any conflict.
Segment sketch first — code / static / heap / stack — then place every variable.
- Q1
balance— directly inside the class, not in a method → instance (heap).amount— directly insidetopUp()→ local (stack, no default, but it's assigned before any read, so legal). - Q2Compile error:
variable score might not have been initialized. Locals have no default-value concept — the compiler blocks the read; the program never runs at all. - Q3False twice over: instance variables go on the heap when
newruns (right side of =), the reference goes on the stack (left side) — different segments, and steps 3–4 happen before step 5 in the trace. - Q4
p.name= null (String),p.lives= 0 (int) — filled by the JVM's initialization step the moment the object is created. - Q5They live in different segments with different lifetimes: the instance copy sits inside the heap object, the local copy sits in the method's stack frame and dies when the method returns. Classification is by location, so identical names never merge or clash.
TOPIC 6 · PART 13 · RECURSION — THE STACK SEGMENT IN MOTION
A function that calls itself. A stack that grows until it can't.
Recursion is simply a function calling itself — not a special language feature, just an ordinary call where the callee happens to be the caller. But T5's Stack segment now goes DYNAMIC: every call — recursive or not — creates a dedicated stack frame (activation record) holding that call's local variables. Return → frame popped, control drops to the frame below. The stack grows and shrinks freely… but it cannot grow forever. Exceed its memory and you get a StackOverflowError.
EXAMPLE 1 · NO BASE CONDITION — WATCH IT DIE (NOT LEARNER-PACED, BY DESIGN)
static void fun(int n){ System.out.println(n); fun(n - 1); // calls itself — with NO condition to ever stop}// called as: fun(3);THE STACK ZONE DURING THE RUNAWAY — FRAMES PILE UNTIL THE ROOM BURSTS
EXAMPLE 2 · THE FIX — A BASE CONDITION, PLACED BEFORE THE CALL
static void fun(int n){ if (n < 1) { return; // base condition — stop HERE, do NOT recurse further } System.out.println(n); fun(n - 1);}// called as: fun(3);THE FULL 6-STEP FRAME TRACE — PREDICT AT EVERY FRAME: PRINT, OR POP?
fun(3) → frame created, n = 3. Base condition (3 < 1) false. Print 3. Call fun(2).fun(1).fun(0).return immediately — NO print, NO further call. Frame popped.EXAMPLE 3 · SAME MACHINE, NEW FUEL — fun(5)
TOPIC 6 · PART 14 · RETURN VS BREAK · RECURSION VS ITERATION
break doesn't even compile. And iteration is usually cheaper.
Two exam favourites left: the break trap (a compile-time error, not a style choice) and the honest efficiency comparison the interviewer always asks next.
EXAMPLE 4 · SWAP return FOR break — COMPILE AND SEE
The attempt: if (n < 1) { break; } inside fun → error: break outside switch or loop. break is ONLY valid inside a loop or a switch — a function body containing an if is neither. The build dies on the spot.
The only exit: return is the correct way to leave a function early. In a void function, a bare return; hands back only the flow of execution — no data, just control. Toggle the line back to return; and the compile succeeds, exactly as Example 2 showed.
EXAMPLE 5 · THE ITERATIVE EQUIVALENT — SAME OUTPUT, ONE FRAME
for (int i = 3; i >= 1; i--){ System.out.println(i);}EXAMPLE 6 · THE HONEST SCORECARD — RECURSIVE (EX 2) VS ITERATIVE (EX 5), SAME TASK
| RECURSIVE (Example 2) | ITERATIVE (Example 5) | |
|---|---|---|
| Stack frames created | 4 — one per call: fun(3), fun(2), fun(1), fun(0) | 1 — the containing method's own frame |
| Copies of the counting variable | 4 separate copies of n (one per frame, 4 bytes each = 16 bytes) | 1 copy of i (4 bytes), reused every iteration |
| Function calls made | 4 | 0 — just loop iterations within the same call |
Both time AND space favour iteration here. The general rule: for any problem solvable both ways, iteration is more efficient. Recursion is never the "more advanced, therefore better" option — it must earn its overhead (Example 8 shows where it does).
EXAMPLE 7 · A SECOND RECURSIVE FUNCTION — THE PATTERN, NOT THE EXAMPLE
static void sumDown(int n){ if (n < 1) { return; // base condition — CHECK FIRST, recurse second } System.out.println("Adding: " + n); sumDown(n - 1);}// called as: sumDown(4);EXAMPLE 8 · WHERE RECURSION EARNS ITS OVERHEAD — THE INTERVIEW LIST
SELF-SIMILAR STRUCTURES
Tree traversal — each subtree is itself a smaller tree. Binary search — each half-range is itself a smaller search. The problem's SHAPE is recursive, so the code that mirrors it is the natural fit.
DIVIDE & CONQUER + DP
Merge sort and quick sort — each half is itself a smaller sort. Dynamic programming (with memoization) — each subproblem is a smaller version of the original. Interview staples at Amazon, Google, Microsoft, Netflix — the classic Josephus problem being a famous recursion-based question.
Recursion is intentionally reached for on these — even knowing it costs more frames and call overhead — because the structure IS recursive. Interviewers test it precisely because it rewards understanding recursion deeply rather than avoiding it. 🎯
MISCONCEPTION PATROL · THE FOUR WRONG SENTENCES (T6)
"A recursive function must fully finish before anything prints."
Example 2's trace shows printing INTERLEAVED with the calls — each frame prints at the exact point where its println sits, not all at once at the end.
"I'll use break to exit the function."
Compile-time error — break is reserved for loops and switch. return is the ONLY valid early exit from a function (Example 4 exists purely to prove this).
"Recursion is the more advanced choice, so it's always better."
Example 6's scorecard says otherwise: iteration wins on memory AND calls whenever both are viable. Recursion's value comes from specific problem shapes (Example 8), not general superiority.
"The base condition can go after the recursive call — same thing."
Check it after and the recursive call has ALREADY happened — the check is defeated. Base condition at the very start, before any recursive call. No exceptions.
PRACTICE · T6Five questions — draw the frame tower for each trace.
- Trace
fun(4)from Example 2 step by step: list every stack frame created, what each one prints (if anything), and the final printed output. - A learner removes the
if (n < 1) return;line from Example 2 entirely. Describe exactly what will happen when this code runs. - Rewrite Example 7 (
sumDown) as an equivalent iterativeforloop, and state how many stack frames each version uses forsumDown(4). - True/False with justification: "For any problem that can be solved with a simple counting loop, recursion will use less memory than iteration."
- Name one category of problem (from Example 8) where recursion is generally preferred despite its overhead, and explain why in one sentence.
Frames on paper: grow up, pop down. Predict print-or-pop before each.
- Q1Frames: fun(4) prints 4 → fun(3) prints 3 → fun(2) prints 2 → fun(1) prints 1 → fun(0) hits base, prints NOTHING, pops. Then 1, 2, 3, 4 pop in reverse. Peak depth 5 frames. Output: 4 3 2 1.
- Q2Runaway recursion: prints 4 3 2 1 0 −1 −2 … with a NEW frame per call, until the stack segment runs out of space → the program terminates with a StackOverflowError (Example 1's exact failure mode).
- Q3
for (int i = 4; i >= 1; i--) { System.out.println("Adding: " + i); }— recursive version: 5 frames for sumDown(4) (n = 4,3,2,1,0); iterative version: 1 frame (the containing method's own). - Q4False. Recursion creates one frame per call, each with its own variable copy; iteration reuses one frame and one variable. For loop-solvable problems, iteration always uses less memory (Example 6: 16 bytes vs 4 bytes).
- Q5Any of: tree traversal / merge sort / quick sort / binary search / DP with memoization (or the Josephus problem) — because the problem's structure is itself self-similar, so the recursive code directly mirrors the shape of the problem.
TOPIC 7 · PART 15 · STATIC — COMPLETING THE VARIABLE PICTURE (BONUS)
One copy. Owned by the class. Shared by every object.
Quick recap from T5: instance = directly inside a class → heap; local = directly inside a method → stack. Today the third and final category: a static variable or method belongs to the class itself, not to any one object. Every object shares the exact same single copy — zero per-object duplication. Static members live in the Static segment — the fourth room we named in T5 but left empty until now.
EXAMPLE 1 · THREE VARIABLES, THREE CATEGORIES, ONE CLASS
class Demo{ int a; // instance — directly inside the class static int b; // static — inside the class, WITH the static keyword void method() { int c; // local — directly inside a method }}a → heap (per object) · b → static segment (ONE copy for the whole class) · c → stack (per method call). Distinguished purely by location plus, for static, the keyword — never by name or type.HEAP
STATIC
STACK
THE DECISION TEST — SHOULD THIS METHOD BE STATIC?
Tied to a specific object reference — the answer depends on WHICH object you ask. Lives with the heap data it reads.
Same answer no matter which object (or no object at all) — belongs to the class, called via the class name.
EXAMPLE 2 · MILEAGE VS HEART RATE — THE TEST, WORKED IN CODE
class Car{ double mileage; // instance — differs per car double getMileage() { return mileage; // depends on THIS car → instance method }}class Human{ static double averageHeartRate() { return 72.0; // same answer for everyone → static }}// ...inside main:Car myCar = new Car();myCar.mileage = 18.5;System.out.println(myCar.getMileage());System.out.println(Human.averageHeartRate());ClassName.member, no new, no reference. The exact opposite of T4's rule that instance members always need a specific object.EXAMPLE 3 · THE SHARED COUNTER — THREE OBJECTS, ONE PURPLE BUBBLE
class Counter{ static int totalObjects = 0; // ONE shared copy across every Counter Counter() { totalObjects++; // every new object bumps the SAME shared copy }}Counter c1 = new Counter();Counter c2 = new Counter();Counter c3 = new Counter();System.out.println(Counter.totalObjects);THE MEMORY PICTURE — THREE HEAP OBJECTS, ONE STATIC BUBBLE, THREE ARROWS IN
HEAP · one bubble PER object
STATIC · one bubble for the CLASS
TOPIC 7 · PART 16 · STYLE TRAPS, NAME RULES & THE FINAL CLASSIFICATION
It compiles. It's still wrong style. And the name trick has a limit.
Three last pieces: a call that compiles but lies, the real limit on same-name variables, and the cold classification drill that closes the whole variable picture.
EXAMPLE 4 · THE CALLING-CONVENTION TRAP — LEGAL ≠ GOOD
CORRECT STYLE — VIA THE CLASS NAME
Human.averageHeartRate()says what it is: class-ownedCOMPILES… BUT MISLEADING
Human h = new Human();
h.averageHeartRate()also compiles — Java allows itClassName.member, always.EXAMPLE 5 · ALL THREE CATEGORIES IN ONE CLASS — AND A REAL NAME CONSTRAINT
class Reading{ int value; // instance — per-object static int value2; // static — shared (deliberately a DIFFERENT name…) void update() { int value; // local — this call only value = 10; // touches neither the instance nor the static one — // each is classified purely by where it's declared }}EXAMPLE 6 · THE COLD CLASSIFICATION DRILL — ANSWER BEFORE REVEALING
name — classify it, out loud, before pressing.name → INSTANCE — inside the class, not inside a method, no static keyword. Heap, per object.branchCount — classify.branchCount → STATIC — inside the class AND marked static. Static segment, one shared copy.itemsAdded — classify.itemsAdded → LOCAL — inside a method. Stack, no default (but assigned before read, so legal).ONE MORE RULE WORTH KNOWING — STATIC CAN'T TOUCH INSTANCE
A static method cannot directly access an instance variable. A static method isn't tied to any particular object — and instance variables only exist once an object has been created. Which object's field would it even read? This bites the moment a class mixes both kinds of members (Examples 3 and 5) — and it's why main (a static method!) always had to create objects before touching their fields.
MISCONCEPTION PATROL · THE FOUR WRONG SENTENCES (T7)
"Each object gets its own copy of the static variable."
Example 3 disproves this by construction: three objects, ONE shared counter, printing 3 — never three separate 1s.
"It doesn't seem to need an object, so I'll make it static."
Vibes aren't the test. The real test: does the RESULT depend on object-specific data? Run the flowchart, not the feeling.
"A static method can freely read instance variables of its class."
It cannot — no specific object exists to draw instance data from. Instance variables only exist once an object has been created.
"The same-name trick from T5 works for static too."
A static and an instance variable in the same class CANNOT legally share an identical name — a real language constraint (Example 5's note), not a style preference.
PRACTICE · T7Four questions — flowchart first, then classify.
- Given
class Bank { double balance; static double interestRate; void deposit(double amt) { double newTotal; newTotal = balance + amt; } }— classify all three variables, justifying each with the location/keyword rule. - A learner writes a method
isPrime(int n)that uses only the parameternand no object data. Should it be static? Justify with the decision test. - True/False with justification: "If a class creates 100 objects, a static variable declared in that class exists in 100 separate copies, one per object."
- Explain, in one or two sentences, why a static method cannot directly reference an instance variable of its own class.
Location + keyword. Then the one-question flowchart. That's the whole toolkit.
- Q1
balance— inside the class, no static → instance (heap, per object).interestRate— inside the class, static keyword → static (static segment, one shared copy).newTotal— inside deposit() → local (stack, no default, assigned before read). - Q2Yes — static. The result depends only on the parameter n, never on any particular object's data — the decision test routes it straight to the static branch.
- Q3False. A static variable belongs to the CLASS — exactly one copy exists in the static segment no matter how many objects are created (Example 3: three objects, one counter, prints 3).
- Q4A static method isn't tied to any specific object, and instance variables only exist inside created objects — so there is no object to read the field from. (Which of the 100 objects' balance would it mean?)
ADD-ON · PART 17 · CLOSE
Seven ideas the exam hides. You now own all seven.
One line each — if any line feels foggy, its part number is right there. Re-run it before Unit 2.
- T1Two's complement (P3–5) — n bits hold −2ⁿ⁻¹ … 2ⁿ⁻¹−1; MSB=1 means DECODE (subtract 1, flip, convert, sign) — never read raw.
- T2OR · AND · XOR (P6) — three tiny truth tables, applied column by column with zero interaction between columns.
- T3Shifts & NOT (P7–8) —
<<doubles,>>halves keeping sign (amber filler),>>>forces blue 0s (positives agree, negatives diverge wildly);~x = −(x+1). - T4Value vs reference (P9–10) —
=photocopies a primitive's value but copies an object's ADDRESS: one object, many names, every name sees every change. - T5Memory model (P11–12) — four JRE rooms; instance → heap with free defaults, local → stack with NO defaults (compile error); classify by LOCATION, never name.
- T6Recursion (P13–14) — every call stacks a frame; base condition BEFORE the call or StackOverflowError;
returnexits,breakdoesn't compile; iteration is cheaper when both work. - T7Static (P15–16) — one class-owned copy in the static segment; the test is "does the answer depend on WHICH object?"; call via
ClassName.member; static can't touch instance.