Skip to content
2 min read

Java Basics — Part 1: Language Fundamentals

"Senior engineers who can't explain stack vs heap or why Integer == Integer fails at 128 have no business designing distributed systems." — Staff Engineer, Google


Real Incident: The Ariane 5 Explosion (1996)

A 64-bit floating point number was cast to a 16-bit signed integer. The value exceeded 32,767, causing an overflow exception that crashed the inertial guidance system. The $370 million rocket self-destructed 37 seconds after launch. Java's strict type system and runtime overflow behavior exist because of disasters like this. Understanding primitives, type promotion, and overflow isn't academic — it's engineering discipline.


1. Java Platform — JDK vs JRE vs JVM

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    JDK["JDK\n= JRE + Dev Tools"] --> JRE["JRE\n= JVM + Libraries"]
    JRE --> JVM["JVM\n= Runs Bytecode"]

    JDK -.-> T1["javac · jdb\njavadoc · jar"]
    JRE -.-> T2["java.lang · util\nio · net · sql"]
    JVM -.-> T3["ClassLoader\nJIT · GC"]

    style JDK fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style JRE fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
    style JVM fill:#FEF3C7,stroke:#FCD34D,color:#92400E
    style T1 fill:#EFF6FF,stroke:#93C5FD,color:#1E40AF
    style T2 fill:#ECFDF5,stroke:#6EE7B7,color:#065F46
    style T3 fill:#FFFBEB,stroke:#FCD34D,color:#92400E

Compilation Flow — Source to Execution

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    A[".java\n(source)"] ==> B[".class\n(bytecode)"]
    B ==> C["JVM loads\n& interprets"]
    C ==> D["JIT → native\n(hot methods)"]

    style A fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style B fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
    style C fill:#FEF3C7,stroke:#FCD34D,color:#92400E
    style D fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
Term What It Is Key Insight
JDK Development Kit = JRE + tools (javac, jdb, javadoc, jconsole) You need this to write Java
JRE Runtime = JVM + core libraries (java.lang, java.util, etc.) You need this to run Java
JVM Virtual Machine: ClassLoader + Execution Engine + GC Platform-dependent (one per OS)
JIT Just-In-Time compiler inside JVM Compiles hot bytecode to native — why Java gets faster over time

WORA Principle: Java is platform-independent (Write Once, Run Anywhere). The JVM is platform-dependent — each OS has its own implementation. Your .class files run unchanged on any JVM.

Interview Gold

"Java is compiled AND interpreted. javac compiles to bytecode (platform-independent). The JVM interprets bytecode, and the JIT compiler further compiles hot paths to native machine code for performance. This is why long-running Java apps outperform cold starts — the JIT optimizes based on runtime profiling (C1/C2 tiered compilation)."


2. Data Types — All 8 Primitives

Primitive Types (8 total — memorize the sizes)

Type Size Default Min Value Max Value Wrapper
byte 1 byte 0 -128 127 Byte
short 2 bytes 0 -32,768 32,767 Short
int 4 bytes 0 -2,147,483,648 2,147,483,647 Integer
long 8 bytes 0L -9.2 x 10^18 9.2 x 10^18 Long
float 4 bytes 0.0f ~1.4 x 10^-45 ~3.4 x 10^38 Float
double 8 bytes 0.0d ~4.9 x 10^-324 ~1.8 x 10^308 Double
char 2 bytes '' 0 (unsigned) 65,535 Character
boolean JVM-specific false Boolean

Reference Types

Everything that is not a primitive: String, arrays, objects, interfaces, enums, records.

Java
String name = "Vamsi";           // reference → String object in pool/heap
int[] numbers = {1, 2, 3};      // reference → array object on heap
Employee emp = new Employee();   // reference → Employee object on heap
List<String> list = null;        // reference → nothing (null)

Key difference: Primitives hold the value directly on the stack. References hold the memory address of an object on the heap.

Type Promotion Rules (Widening)

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    BYTE["byte"] ==> SHORT["short"]
    SHORT ==> INT["int"]
    CHAR["char"] ==> INT
    INT ==> LONG["long"]
    LONG ==> FLOAT["float"]
    FLOAT ==> DOUBLE["double"]

    style BYTE fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style SHORT fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style INT fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
    style CHAR fill:#FEF3C7,stroke:#FCD34D,color:#92400E
    style LONG fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
    style FLOAT fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
    style DOUBLE fill:#FEE2E2,stroke:#FCA5A5,color:#991B1B
Java
byte b = 10;
int i = b;          // OK: widening (automatic)
byte x = i;         // COMPILE ERROR: narrowing requires cast
byte y = (byte) i;  // OK: explicit cast (may lose data)

// TRAP: byte + byte = int (promotion to int for arithmetic)
byte a = 10, c = 20;
byte result = a + c;        // COMPILE ERROR! a + c is int
byte result = (byte)(a + c); // OK

The float Precision Trap

long to float is widening but loses precision! A long has 64-bit integer precision; a float has only ~7 decimal digits. long l = 123456789L; float f = l; — value becomes 1.23456792E8 (rounded).


3. Variables — Local, Instance, Static

Lifecycle

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    S["Static\n(class load → shutdown)"] ~~~ I["Instance\n(new → GC'd)"] ~~~ L["Local\n(method call → return)"]

    style S fill:#DBEAFE,stroke:#93C5FD,color:#1E40AF
    style I fill:#D1FAE5,stroke:#6EE7B7,color:#065F46
    style L fill:#FEF3C7,stroke:#FCD34D,color:#92400E
Type Declared Where Stored Where Default Must Initialize?
Local Inside method/block Stack None YES (compile error otherwise)
Instance Class body (no static) Heap (inside object) Type default (0, null, false) No
Static Class body (with static) Metaspace (method area) Type default No
Java
public class VariableDemo {
    static int classCount = 0;       // static: one per class, lives in Metaspace
    private String name;             // instance: one per object, lives on heap
    private final int id;            // instance + final: must be set in constructor

    public void process() {
        int temp = 42;               // local: lives on stack frame
        // int x; System.out.println(x); // COMPILE ERROR: local not initialized
    }
}

Variable Shadowing

Java
public class Shadow {
    int x = 10;                      // instance variable

    public void method(int x) {     // parameter shadows instance variable
        System.out.println(x);       // prints parameter value
        System.out.println(this.x);  // prints instance variable (10)

        int x = 5; // COMPILE ERROR: can't shadow local with another local
    }
}

Interview Gold

"Local variables are NOT thread-safe by coincidence — they live on the thread's own stack frame. Instance/static variables live on the heap and are shared, so they need synchronization for thread safety."


4. Operators

Arithmetic, Relational, and Logical

Category Operators Notes
Arithmetic + - * / % / on integers truncates: 7/2 = 3
Relational == != < > <= >= == on objects compares references
Logical && || ! Short-circuit: right side may not execute
Bitwise & | ^ ~ Operate on individual bits
Shift << >> >>> >>> is unsigned (fills with 0)
Assignment = += -= *= /= %= <<= >>= Compound operators include implicit cast
Unary ++ -- + - ~ ! Pre vs post increment matters
Ternary ? : result = condition ? val1 : val2;
instanceof instanceof Pattern matching in Java 16+: if (obj instanceof String s)

Short-Circuit Evaluation (Critical for Interviews)

Java
// && : if left is false, right is NEVER evaluated
String s = null;
if (s != null && s.length() > 0) { } // safe: s.length() never called

// || : if left is true, right is NEVER evaluated
if (list.isEmpty() || list.get(0).isValid()) { } // safe if empty

// & and | : ALWAYS evaluate BOTH sides (no short-circuit)
if (s != null & s.length() > 0) { } // NullPointerException!

Bitwise and Shift Operators (FAANG Favorite)

Java
// XOR swap (no temp variable)
int a = 5, b = 3;
a = a ^ b;  // a=6
b = a ^ b;  // b=5
a = a ^ b;  // a=3

// Check if power of 2
boolean isPow2 = (n > 0) && ((n & (n - 1)) == 0);

// Multiply/divide by 2 (faster than * or /)
int doubled = n << 1;   // n * 2
int halved  = n >> 1;   // n / 2

// Unsigned right shift
int x = -1;             // 11111111 11111111 11111111 11111111
int y = x >>> 1;        // 01111111 11111111 11111111 11111111 = 2147483647
int z = x >> 1;         // 11111111 11111111 11111111 11111111 = -1 (sign extended)

Compound Assignment Implicit Cast

Java
byte b = 10;
b = b + 1;    // COMPILE ERROR: b + 1 is int
b += 1;       // OK! compound operators include implicit cast to (byte)
b++;          // OK! same implicit cast

5. Control Flow

if/else and switch

Java
// Traditional switch (statement, falls through)
switch (day) {
    case MONDAY:
    case FRIDAY:
        System.out.println("Working hard");
        break;  // forget this = fall-through bug
    default:
        System.out.println("Other");
}

// Java 14+ switch EXPRESSION (no fall-through, returns value)
String result = switch (day) {
    case MONDAY, FRIDAY -> "Working hard";
    case SATURDAY, SUNDAY -> "Weekend!";
    default -> {
        String computed = "Day " + day.ordinal();
        yield computed;  // yield for multi-line blocks
    }
};

Interview Gold

Java 14+ switch expressions are exhaustive — the compiler enforces that all possible values are handled (either with cases or a default). This eliminates an entire class of bugs.

Loops — for, while, do-while

Java
// Enhanced for-each (Java 5+)
for (String item : list) { }  // can't modify list during iteration

// Labeled break/continue (useful for nested loops)
outer:
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] == target) {
            System.out.println("Found at [" + i + "][" + j + "]");
            break outer;  // exits BOTH loops
        }
    }
}

// do-while: always executes AT LEAST once
do {
    response = callService();
} while (response.isRetryable());

Loop Interview Traps

Java
// Infinite loop: int overflow wraps around
for (int i = 0; i >= 0; i++) { }  // runs 2.1 billion times then i becomes negative

// ConcurrentModificationException
for (String s : list) {
    if (s.equals("remove")) list.remove(s);  // THROWS
}
// Fix: use Iterator.remove() or list.removeIf(s -> s.equals("remove"))

6. Arrays

Declaration and Initialization

Java
// Declaration
int[] numbers;           // preferred style
int numbers2[];          // C-style (legal but discouraged)

// Static initialization
int[] primes = {2, 3, 5, 7, 11};

// Dynamic initialization
int[] arr = new int[10]; // all zeros
String[] names = new String[5]; // all null

// Multi-dimensional
int[][] matrix = new int[3][4];    // 3 rows, 4 columns
int[][] jagged = new int[3][];     // rows of different lengths
jagged[0] = new int[2];
jagged[1] = new int[5];

Arrays Utility Class

Java
import java.util.Arrays;

Arrays.sort(arr);                          // O(n log n) — DualPivotQuicksort
Arrays.binarySearch(sortedArr, key);       // O(log n) — array MUST be sorted
Arrays.fill(arr, 0);                       // fill all elements
Arrays.copyOf(arr, newLength);             // resize/copy
Arrays.equals(arr1, arr2);                 // element-by-element comparison
Arrays.deepEquals(matrix1, matrix2);       // for multi-dimensional
Arrays.stream(arr).sum();                  // stream operations

Array vs ArrayList

Feature Array ArrayList
Size Fixed at creation Dynamic (grows 1.5x)
Primitives Supports directly Only objects (autoboxing)
Performance Slightly faster (no boxing) Slight overhead
Type safety Covariant (unsafe) Generic (safe at compile time)
API .length field .size() method, rich API
Memory Compact Object overhead per element
Multi-dimension Native support List of Lists
Java
// Array covariance trap (compiles but fails at runtime)
Object[] arr = new String[3];
arr[0] = 42;  // Compiles! Throws ArrayStoreException at runtime

// ArrayList generics catch this at compile time
List<String> list = new ArrayList<>();
// list.add(42); // COMPILE ERROR — type safety