Java & Programming Fundamentals
"The alphabet before the poetry — master these cold, they appear everywhere."
Java is the dominant language for FAANG backend systems and the de-facto language of coding interviews. Before you can write a single algorithm, you need to know how the computer stores data (types), how it makes decisions (control flow), and how it organizes code (methods and OOP). Every topic in this course — arrays, trees, graphs, dynamic programming — is built on top of these fundamentals. Skipping this is like trying to write poetry without knowing the alphabet.
📖 Before We Start — The Big Picture
Think of a computer program as a recipe. Variables are your ingredients, data types are the containers (you can't store a liquid in a bag or a powder in a bottle), control flow is the list of steps, and methods are reusable sub-recipes. The computer follows these instructions one at a time, storing and reading values from memory as it goes.
At the hardware level, everything is just 0s and 1s in memory. Java's
type system gives those raw bits meaning — a
32-bit pattern means a very different thing when
interpreted as an int vs a float. This
lecture teaches you to pick the right container for the right data,
every time.
🧱 Data Types
1.1 — Primitive Types (8 total)
Stored directly on the stack. Fixed size, no null, ultra-fast access. These are the atoms of Java.
| Type | Bits | Range | Default | DSA Usage |
|---|---|---|---|---|
byte |
8 | −128 → 127 | 0 | raw byte buffers, rarely in DSA |
short |
16 | −32,768 → 32,767 | 0 | rarely used |
int ⭐
|
32 | −2.1B → 2.1B | 0 | default integer — use this always |
long ⭐
|
64 | ±9.2 × 10¹⁸ | 0L | large products, timestamps, overflow-prone problems |
float |
32 | ~6-7 sig. digits | 0.0f | avoid — precision issues |
double ⭐
|
64 | ~15-16 sig. digits | 0.0 | default decimal |
char ⭐
|
16 | 0 → 65,535 (Unicode) | \u0000 | frequency arrays, char arithmetic |
boolean |
1 bit | true / false | false | flags, visited arrays, conditions |
• Default to
int for all integers.• Switch to
long when result can exceed ~2.1 billion
(e.g. n × m with n,m ≈ 10⁵).•
char - 'a' gives you an index 0–25 — this is how you
build O(1) frequency arrays for lowercase letters.•
double for geometry, probabilities, averages.
1.2 — Reference (Non-Primitive) Types
Variables hold a memory address (reference) pointing to an object on the heap. Can be null. Includes all classes, arrays, interfaces.
// Primitive — value lives IN the variable (stack) int x = 42; // x IS 42 // Reference — variable holds ADDRESS pointing to heap String s = "hello"; // s holds address of "hello" object on heap int[] arr = {1,2,3}; // arr holds address of the array on heap // Consequence: == checks identity (same address), not equality! String a = new String("hi"); String b = new String("hi"); System.out.println(a == b); // false (different addresses!) System.out.println(a.equals(b)); // true (same content) ← USE THIS
1.3 — Type Casting (Interview Gotchas)
// Widening (implicit, safe) — smaller → larger int i = 100; long l = i; // auto-widens: no data loss double d = i; // auto-widens to 100.0 // Narrowing (explicit, lossy) — larger → smaller double pi = 3.99; int truncated = (int) pi; // → 3 (TRUNCATES, does NOT round!) // ⚠️ GOTCHA 1: integer division truncates double wrong = 5 / 2; // → 2.0 (int division first, THEN widened) double correct = 5.0 / 2; // → 2.5 (double division) double correct2 = (double) 5 / 2; // → 2.5 (cast first) // ⚠️ GOTCHA 2: int overflow — use long! int n = 100000; int overflow = n * n; // → negative! (wraps around 2.1B limit) long safe = (long) n * n; // → 10000000000L ✓ // ⭐ char arithmetic — essential for DSA int idx = 'e' - 'a'; // → 4 (freq array index) char c = (char)('a' + 3); // → 'd' boolean isLower = (ch >= 'a' && ch <= 'z'); // char range check char upper = (char)(ch - 32); // lowercase → uppercase
Java caches
Integer values from −128 to 127 (the
Integer cache). Outside this range each auto-boxing creates
a new heap object.Integer a = 127, b = 127; a == b →
true (same cached object)Integer c = 128, d = 128; c == d →
false (two different heap objects!)Rule: ALWAYS use
.equals() to compare
wrapper types — never ==.Additional trap: Unboxing a
null
Integer to int silently throws
NullPointerException!
🔁 Control Flow
2.1 — All Loop Types
•
for: When you know the exact iteration count
upfront.•
while: When iteration count depends on a condition
(unknown upfront). Classic for binary search, two-pointer, digit
extraction.•
do-while: When the body must execute at least once.
Classic for menu loops, digit extraction where n=0 is possible.•
for-each: Cleanest for read-only traversal of
collections/arrays when you don't need the index.
// ── FOR loop — use when count is known ─────────────────────── for (int i = 0; i < n; i++) // forward: i goes 0..n-1 for (int i = n-1; i >= 0; i--) // reverse: suffix processing for (int i = 0; i < n; i += 2) // step 2: even indices for (int i = 0; i < n-1; i++) // n-1 pairs: adj comparison // ── WHILE loop — use when count is unknown ─────────────────── while (lo <= hi) { ... } // binary search template while (n > 0) { digit = n%10; n/=10;} // digit extraction while (n > 1) { n /= 2; count++; } // count divisions → O(log n) while (fast != null && fast.next != null) // linked list traversal // ── DO-WHILE — body runs at least once ─────────────────────── do { digit = n % 10; reversed = reversed * 10 + digit; n /= 10; } while (n != 0); // handles n=0 correctly // ── FOR-EACH — read-only traversal ─────────────────────────── for (int x : arr) sum += x; // array for (String s : list) process(s); // collection for (Map.Entry<K,V> e : map.entrySet()) // map iteration // ── NESTED LOOPS — know the complexity! ────────────────────── for (int i = 0; i < n; i++) // ┐ O(n²) — all pairs for (int j = i+1; j < n; j++) {} // ┘ j starts at i+1: n(n-1)/2 pairs // ── BREAK / CONTINUE ───────────────────────────────────────── break; // exits the INNERMOST loop only continue; // skips to NEXT iteration of innermost loop // To break an outer loop: use labeled break outer: for (...) { for (...) { if (found) break outer; } // exits outer loop! }
2.2 — Conditionals & Short-Circuit Evaluation
// Standard if-else if (x > 0) { ... } else if (x == 0) { ... } else { ... } // Ternary — for simple single-value selection int max = (a > b) ? a : b; int abs = (x < 0) ? -x : x; // ternary absolute value // Switch (Java 14+ — enhanced switch expression) int days = switch (month) { case 1, 3, 5, 7, 8, 10, 12 -> 31; case 4, 6, 9, 11 -> 30; default -> 28; }; // ⭐ Short-circuit evaluation — CRITICAL for null safety! // && stops at first FALSE condition if (node != null && node.val == target) { ... } // if node==null, right side NEVER evaluates → no NullPointerException // || stops at first TRUE condition if (arr == null || arr.length == 0) return; // if arr==null, second check NEVER runs → safe! // ⚠️ Integer.MAX_VALUE and MIN_VALUE — essential constants int max = Integer.MAX_VALUE; // 2^31 - 1 = 2,147,483,647 int min = Integer.MIN_VALUE; // -2^31 = -2,147,483,648 int res = Math.max(a, b); // Math utility methods int abs = Math.abs(x); double sq = Math.sqrt(n);
🔧 Methods & Arrays
3.1 — Method Anatomy
// access static return name parameters public static int binarySearch(int[] arr, int target) { // body return -1; // must return int (matches return type) } // void methods — no return value (but can still return early!) public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } // Method overloading — same name, different params int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } // different return type too // Varargs — variable number of args (used in DSA for flexibility) int sum(int... nums) { int total = 0; for (int n : nums) total += n; return total; } // call: sum(1), sum(1,2), sum(1,2,3,4,5) — all valid
3.2 — Pass-by-Value: The Most Misunderstood Thing in Java
The confusion: for objects and arrays, the VALUE being passed is the reference (address).
A copy of the address is passed → mutations through that address persist. But reassigning the reference inside the method doesn't affect the caller.
// PRIMITIVE: copy of value — original unchanged void tryChange(int x) { x = 99; } int a = 5; tryChange(a); System.out.println(a); // still 5 — x was a COPY // ARRAY: copy of reference — mutations visible to caller! void mutate(int[] arr) { arr[0] = 99; } int[] arr = {1,2,3}; mutate(arr); System.out.println(arr[0]); // 99 — mutation persisted! // REFERENCE REASSIGNMENT: doesn't affect caller void reassign(int[] arr) { arr = new int[]{9,9,9}; } reassign(arr); System.out.println(arr[0]); // still 99 — local arr was a copy of address // ⭐ Consequence: swap(int a, int b) NEVER works // swap(int[] arr, int i, int j) DOES work (mutations on shared array)
3.3 — Arrays: Your #1 DSA Tool
// ── Declaration & Initialization ───────────────────────────── int[] a = new int[5]; // [0,0,0,0,0] — auto-initialized to 0 boolean[] vis = new boolean[n]; // [false,false,...] auto init int[] b = {3, 1, 4, 1, 5}; // literal init int[][] mat = new int[3][4]; // 3 rows, 4 cols — all 0 int[][] grid = {{1,2},{3,4}}; // 2D literal // ── Access ──────────────────────────────────────────────────── a.length // FIELD (no parens!) — O(1) mat.length // number of rows mat[0].length // number of cols in row 0 // ── java.util.Arrays methods ────────────────────────────────── Arrays.sort(a); // O(n log n) — dual-pivot quicksort Arrays.sort(a, 0, 5); // sort subarray [0,5) Arrays.fill(a, -1); // fill all with -1 O(n) Arrays.fill(a, 2, 5, 0); // fill range [2,5) with 0 Arrays.copyOf(a, a.length); // full copy — O(n) Arrays.copyOfRange(a, 1, 4); // subarray copy — indices [1,4) Arrays.equals(a, b); // element-wise comparison Arrays.binarySearch(a, target); // O(log n) — array must be sorted! Arrays.toString(a); // "[1, 2, 3]" for printing // ── Custom sort with Comparator ─────────────────────────────── Integer[] arr = {3,1,4,1,5}; Arrays.sort(arr, (a, b) -> b - a); // descending — note: needs Integer[], not int[] // Sort by absolute value: Arrays.sort(arr, (a, b) -> Math.abs(a) - Math.abs(b));
📦 OOP Fundamentals
4.1 — The Four Pillars (with DSA relevance)
| Pillar | What it means | DSA example |
|---|---|---|
| Encapsulation | Bundle data + methods; hide internals | LinkedList class with private head, public add/remove |
| Abstraction | Expose what, hide how | PriorityQueue — you call offer/poll, not heapifyUp |
| Inheritance | Subclass extends superclass, reuses code | TreeNode extends Node; DFS/BFS methods inherited |
| Polymorphism | Same method, different behaviour | Comparator — sort by different keys without changing sort logic |
// The class you'll write/use constantly in tree problems class TreeNode { int val; TreeNode left, right; TreeNode(int val) { this.val = val; } TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } } // ListNode — for linked list problems class ListNode { int val; ListNode next; ListNode(int val) { this.val = val; } }
4.2 — Interfaces, Comparators & Generics
// Custom sorting — appears in interval, scheduling, and K-th problems // Sort intervals by start time Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // Sort strings by length Arrays.sort(words, (a, b) -> a.length() - b.length()); // Max-heap (reverse natural order) — used constantly! PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a,b) -> b-a); // Min-heap by second element of pair PriorityQueue<int[]> pq = new PriorityQueue<>((a,b) -> a[1] - b[1]); // Generics — type-safe collections List<Integer> list = new ArrayList<>(); Map<String, List<Integer>> graph = new HashMap<>(); Set<Integer> visited = new HashSet<>();
4.3 — static & final Keywords
// static — belongs to the CLASS, shared across all instances class Counter { static int count = 0; // shared across ALL instances int id; // unique per instance Counter() { id = ++count; } } // static methods can't access instance fields (no 'this') // Why main() is static: JVM calls it without creating an object // final — prevents reassignment / inheritance / override final int MAX = 100; // constant; convention: ALL_CAPS final String s = "hi"; s = "bye"; // ✗ COMPILE ERROR // DSA constants you'll use constantly: static final int INF = Integer.MAX_VALUE; // Dijkstra, DP static final int MOD = 1_000_000_007; // modular arithmetic
4.4 — The equals() + hashCode() Contract
a.equals(b) is
true, then
a.hashCode() == b.hashCode() MUST also be
true.Breaking this contract causes silent bugs in
HashMap,
HashSet, and every hash-based structure.When using a custom object as a
HashMap key, you MUST
override both methods together.
class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point p = (Point) o; return x == p.x && y == p.y; } @Override public int hashCode() { return 31 * x + y; // must be consistent with equals() } } // Without overriding: map.get(new Point(1,2)) → null even after put! Map<Point, String> map = new HashMap<>(); map.put(new Point(1,2), "origin"); map.get(new Point(1,2)); // → "origin" ✓ (with both overrides)
⏱ Big-O Complexity Analysis
Big-O is an upper bound on the growth rate of an algorithm's resource usage (time or space) as input size n → ∞. It describes how much slower your algorithm gets when you double the input — not the exact running time in milliseconds.
5.1 — The 4 Simplification Rules
| # | Rule | Example | Simplifies to |
|---|---|---|---|
| 1 | Drop constants | O(3n + 500) | O(n) |
| 2 | Drop non-dominant terms | O(n² + n + log n) | O(n²) |
| 3 | Different inputs → different variables | Two arrays of size a and b processed sequentially | O(a + b), NOT O(n) |
| 4 | Nested loops on same input → multiply | Outer O(n), inner O(n) | O(n²) |
5.2 — Complexity Recognition by Code Pattern
// ── O(1) — fixed number of operations ──────────────────────── arr[i]; // array access map.get(key); // hashmap lookup pq.peek(); // heap peek // ── O(log n) — input HALVED each step ──────────────────────── while (n > 1) { n /= 2; } // log₂n iterations // Binary search, heap operations, balanced BST ops // ── O(√n) — loop to square root ────────────────────────────── for (int i = 2; i*i <= n; i++) // prime check — √n iterations // ── O(n) — single pass ─────────────────────────────────────── for (int i = 0; i < n; i++) {} // n iterations // Two-pointer, sliding window (both pointers move ≤ n times total) // ── O(n log n) — sort, or divide + linear merge ────────────── Arrays.sort(arr); // O(n log n) // Merge sort: log n levels × O(n) merge = O(n log n) // ── O(n²) — nested loops over same input ───────────────────── for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) // always O(n²) even if j=i+1 // ── O(2ⁿ) — two recursive calls or all subsets ─────────────── // fib(n-1) + fib(n-2), or all 2ⁿ subsets of an array // ── O(n!) — all permutations ───────────────────────────────── // Generating all arrangements of n elements
| Big-O | n=10 | n=100 | n=1,000 | n=10,000 | Verdict |
|---|---|---|---|---|---|
| O(1) | 1 | 1 | 1 | 1 | 🟢 Perfect |
| O(log n) | 3 | 7 | 10 | 13 | 🟢 Excellent |
| O(√n) | 3 | 10 | 32 | 100 | 🟢 Good |
| O(n) | 10 | 100 | 1,000 | 10,000 | 🟢 Good |
| O(n log n) | 33 | 664 | 10,000 | 133,000 | 🟡 Acceptable |
| O(n²) | 100 | 10,000 | 1M | 100M (slow) | 🔴 Avoid for n>10⁴ |
| O(2ⁿ) | 1,024 | 10³⁰ 💀 | — | — | 🔴 Only for n≤20 |
| O(n!) | 3.6M | 💀 | — | — | 🔴 Only for n≤10 |
5.2.5 — Input Constraints → Expected Complexity
| Input Size (n) | Max Complexity | Typical Algorithms |
|---|---|---|
| n ≤ 10 | O(n!) or O(nⁿ) | Permutations, brute-force |
| n ≤ 20 | O(2ⁿ) | Backtracking, bitmask DP |
| n ≤ 500 | O(n³) | 3D DP, Floyd-Warshall |
| n ≤ 5,000 | O(n²) | DP tables, O(n²) sorts |
| n ≤ 10⁵ | O(n log n) | Merge sort, heap, segment tree |
| n ≤ 10⁶ | O(n) | Linear scan, two pointers, hashing |
| n ≤ 10⁸ | O(n) tight constant | Optimised linear, bitwise ops |
| n ≤ 10¹⁸ | O(log n) | Binary search, fast exponentiation |
5.3 — Space Complexity
Then ask: "Can I reduce space to O(1) using in-place manipulation?"
Two-pointers, in-place reversal, and bit manipulation are classic O(1) space techniques.
// O(1) space — only fixed number of extra variables int sum = 0; for (int x : arr) sum += x; // one variable, regardless of n // O(n) space — proportional to input int[] copy = Arrays.copyOf(arr, n); // n-size extra array Map<?,?> map = new HashMap<>(); // up to n entries // O(n) space — RECURSION! Each call frame is on the stack int factorial(int n) { if(n==0) return 1; return n * factorial(n-1); // depth n → O(n) stack space } // O(log n) space — binary search (recursive) depth = log n // O(n) space — merge sort call stack depth = log n, but merge arrays = O(n) // Auxiliary space = extra space beyond input storage // Total space = input + auxiliary
🗂 Java Collections Cheat Sheet
Memorize this table. Every data structure lecture will use these.
| Collection | Key Operations & Time | When to use | Important gotchas |
|---|---|---|---|
| int[] / array | get/set: O(1), length: O(1) | Fixed-size, primitives, frequency arrays | Fixed size, no built-in methods |
| ArrayList<T> | get: O(1), add: O(1)*, remove(i): O(n) | Dynamic array, ordered, indexed access | *amortized; remove by index is O(n) |
| LinkedList<T> | add/remove ends: O(1), get(i): O(n) | Deque operations, frequent inserts at ends | Not cache-friendly; prefer ArrayDeque for stack/queue |
| HashMap<K,V> | get/put/contains: O(1) avg, O(n) worst | Frequency count, complement lookup, memoization | Unordered; worst case O(n) if hash collisions |
| LinkedHashMap<K,V> | Same as HashMap | LRU Cache (preserves insertion order) | Slightly slower than HashMap |
| TreeMap<K,V> | get/put/floor/ceil: O(log n) | Need sorted keys, floor/ceil queries | Keys must be Comparable; Red-Black tree internally |
| HashSet<T> | add/contains/remove: O(1) avg | De-dup, O(1) lookup, visited set | No ordering; no duplicates |
| TreeSet<T> | add/contains/floor/ceil: O(log n) | Sorted set, range queries | Keys must be Comparable |
| PriorityQueue<T> | offer/poll: O(log n), peek: O(1) | Top-K, Dijkstra, median stream | Min-heap by default; use (a,b)->b-a for max-heap |
| ArrayDeque<T> | push/pop/peek (both ends): O(1) | Stack, queue, sliding window (deque) | Faster than Stack class; preferred in all DSA |
| Stack<T> | push/pop/peek: O(1) | Legacy; use ArrayDeque instead | Synchronized (slow); avoid in competitive coding |
tm.floorKey(x) — largest key ≤ x |
tm.ceilingKey(x) — smallest key ≥ xtm.lowerKey(x) — largest key < x |
tm.higherKey(x) — smallest key > xtm.firstKey() / tm.lastKey() — min / max
keytm.headMap(x) — submap with all keys < x
| tm.tailMap(x) — all keys ≥ xUse for: Calendar events, sliding window problems, frequency-range queries.
// ── ArrayList ───────────────────────────────────────────────── List<Integer> list = new ArrayList<>(); list.add(5); // append list.add(0, 5); // insert at index 0 — O(n)! list.get(i); // O(1) list.remove(i); // by index — O(n); shifts everything list.remove(Integer.valueOf(5)); // by value — O(n) list.size(); list.isEmpty(); list.contains(x); Collections.sort(list); // O(n log n) Collections.reverse(list); // ── HashMap ─────────────────────────────────────────────────── Map<String,Integer> map = new HashMap<>(); map.put("a", 1); map.get("a"); // returns null if absent map.getOrDefault("a", 0); // ← use this to avoid null checks! map.containsKey("a"); map.containsValue(1); map.put("a", map.getOrDefault("a",0)+1); // frequency count pattern map.putIfAbsent("a", new ArrayList<>()); // graph adjacency list for (var e : map.entrySet()) // iterate all entries System.out.println(e.getKey() + " → " + e.getValue()); // ── PriorityQueue ───────────────────────────────────────────── PriorityQueue<Integer> minPQ = new PriorityQueue<>(); // min-heap PriorityQueue<Integer> maxPQ = new PriorityQueue<>(Collections.reverseOrder()); minPQ.offer(5); // add minPQ.peek(); // view min without removing — O(1) minPQ.poll(); // remove and return min — O(log n) // ── ArrayDeque as Stack ────────────────────────────────────── Deque<Integer> stack = new ArrayDeque<>(); stack.push(5); // push to front stack.pop(); // remove from front stack.peek(); // view front without removing // ── ArrayDeque as Queue ────────────────────────────────────── Deque<Integer> queue = new ArrayDeque<>(); queue.offer(5); // enqueue at back queue.poll(); // dequeue from front queue.peek(); // view front
🔤 String Essentials for DSA
Java strings are immutable — every modification creates a new string. This is the #1 interview trap for beginners.
String s = ""; for(int i=0; i<n; i++) s += "a";Each
+= creates a new string and copies everything. Use
StringBuilder instead.StringBuilder sb = new StringBuilder(); for(...) sb.append("a");
return sb.toString();
String Operations You Must Know
| Operation | Code | Time | Notes |
|---|---|---|---|
| Length | s.length() |
O(1) | Note: () for String, not .length |
| Get char | s.charAt(i) |
O(1) | Returns char, not String |
| Substring | s.substring(i, j) |
O(j-i) | Creates new string [i, j) |
| Compare | s.equals(t) |
O(n) | NEVER use == for strings! |
| To char array | s.toCharArray() |
O(n) | Convert to mutable array |
| Split | s.split(" ") |
O(n) | Returns String[] |
| Trim | s.trim() |
O(n) | Remove leading/trailing spaces |
| Contains | s.contains("ab") |
O(n×m) | Naive search |
| Index of | s.indexOf("ab") |
O(n×m) | Returns -1 if not found |
| To lowercase | s.toLowerCase() |
O(n) | Creates new string |
StringBuilder sb = new StringBuilder();sb.append("hello"); — O(1) amortizedsb.insert(0, "x"); — O(n) — shifts everythingsb.reverse(); — O(n)sb.toString(); — O(n) — get final StringRule: If you're building a string character by character, ALWAYS use StringBuilder.
Common String Patterns in Interviews
- Two Pointers: Palindrome check, reverse string → LC 125
- Frequency Count: Anagram check, permutation in string → LC 242
- Sliding Window: Longest substring without repeats → LC 3
- HashMap: Group anagrams → LC 49
- Stack: Valid parentheses → LC 20
💪 In-Lecture Practice Problems
These are solved during the lecture. Think first, attempt on paper, then check solution.
📝 Assignment
Complete the assignment before moving to Lecture 2. It includes 8 Easy, 7 Medium, 5 Hard problems, 4 complexity exercises, and 7 bonus problems — all with LeetCode links.
📄 Open Assignment →
✅ Lecture Completion Checklist
Check each item before advancing to Lecture 2.
5/2 == 2 in Java and how to fix it
Don't just write code; command memory. Understanding the JVM Stack and Heap is the difference between a senior engineer and a hobbyist. Master this foundation before we move to OOP and complex data structures.