diff --git a/README.md b/README.md
index 6e4956b..cf7bf34 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ One module per article. This repository currently holds:
| [`binary-tree-traversals`](binary-tree-traversals/) | Binary Tree Traversals in Java: Inorder, Preorder, Postorder and Level Order (Recursive and Iterative) | this commit |
| [`sliding-window`](sliding-window/) | Sliding Window and Two Pointers in Java: 12 Interview Problems Solved | this commit |
| [`dynamic-programming`](dynamic-programming/) | Dynamic Programming in Java for Beginners: Memoization vs Tabulation (10 Classic Problems) | this commit |
+| [`fresher-qa`](fresher-qa/) | Top 50 Java Interview Questions for Freshers (2026) with Simple Answers | this commit |
## `collections` module
diff --git a/fresher-qa/README.md b/fresher-qa/README.md
new file mode 100644
index 0000000..157fb67
--- /dev/null
+++ b/fresher-qa/README.md
@@ -0,0 +1,32 @@
+# fresher-qa
+
+Runnable companion for the ankurm.com article
+[Top 50 Java Interview Questions for Freshers (2026) with Simple Answers](https://ankurm.com/top-50-java-interview-questions-for-freshers/).
+
+One small demo per question, grouped in six classes. Tests assert what each demo prints and write the
+transcripts the article quotes. There is no `docs/` folder on purpose: the explanations live in the article.
+
+| Component | Version | Verified against |
+|---|---|---|
+| JDK | 25.0.5+8 (Temurin, LTS) | `java -version` |
+| JUnit | 6.1.3 | `maven-metadata.xml` on Maven Central |
+| Maven | 3.9.11 | `mvn -version` |
+
+```bash
+export JAVA_HOME=/path/to/jdk-25 && export PATH="$JAVA_HOME/bin:$PATH"
+cd fresher-qa
+mvn test # 6 tests; rewrites output/01-06
+./scripts/run-all.sh # also regenerates output/07 (javac's own error messages for src/broken/)
+```
+
+| Class | Questions | Transcript |
+|---|---|---|
+| `BasicsQa` | 1-9 | `output/01-basics.txt` |
+| `OopQa` | 10-20 | `output/02-oop.txt` |
+| `StringsQa` | 21-27 | `output/03-strings.txt` |
+| `CollectionsQa` | 28-38 | `output/04-collections.txt` |
+| `ExceptionsQa` | 39-43 | `output/05-exceptions.txt` |
+| `ModernJavaQa` | 44-50 | `output/06-modern-java.txt` |
+
+`src/broken/` holds five files that must NOT compile (one of them only fails below Java 25); `scripts/compile-errors.sh` runs `javac` on each and
+commits the real message to `output/07-compile-errors.txt`. They are excluded from the Maven build.
diff --git a/fresher-qa/output/01-basics.txt b/fresher-qa/output/01-basics.txt
new file mode 100644
index 0000000..00632fe
--- /dev/null
+++ b/fresher-qa/output/01-basics.txt
@@ -0,0 +1,43 @@
+=== Q01 Why is Java platform independent? ===
+magic = CAFEBABE
+class file major version = 69
+=== Q02 JDK vs JRE vs JVM ===
+running on Java 25
+JVM: OpenJDK 64-Bit Server VM
+compiler available: true
+=== Q03 Primitive types and their sizes ===
+byte 1 byte, -128 to 127
+short 2 bytes, -32768 to 32767
+int 4 bytes, -2147483648 to 2147483647
+long 8 bytes, -9223372036854775808 to 9223372036854775807
+float 4 bytes, double 8 bytes, char 2 bytes
+field defaults: 0, false, null
+=== Q04 Type casting and overflow ===
+7 / 2 = 3, (double) 7 / 2 = 3.5
+(byte) 130 = -126
+Integer.MAX_VALUE + 1 = -2147483648
+c + 1 = 66, (char) (c + 1) = B
+0.1 + 0.2 = 0.30000000000000004
+Math.addExact: integer overflow
+=== Q05 Autoboxing and the Integer cache ===
+127 == 127 -> true
+128 == 128 -> false
+128 equals 128 -> true
+Long 127 equals Integer 127 -> false
+=== Q06 Is Java pass by value or pass by reference? ===
+after reassign: original
+after mutate: original + mutated
+=== Q07 static variables, static blocks and initialisation order ===
+static block runs once, when the class loads
+instance block runs before each constructor body
+constructor #1
+instance block runs before each constructor body
+constructor #2
+=== Q08 final, finally and finalize ===
+final array contents changed: 99
+in try
+finally always runs
+Object.finalize forRemoval = true, since 9
+=== Q09 Stack vs heap ===
+heap array length = 1000000
+StackOverflowError after many frames: true
diff --git a/fresher-qa/output/02-oop.txt b/fresher-qa/output/02-oop.txt
new file mode 100644
index 0000000..466a009
--- /dev/null
+++ b/fresher-qa/output/02-oop.txt
@@ -0,0 +1,36 @@
+=== Q10 The four pillars of OOP ===
+Circle area = 3.14
+Rectangle area = 6.00
+rejected: amount must be positive, balance still 500.0
+=== Q11 Inheritance and constructor chaining ===
+Animal constructor
+Dog constructor
+Animal constructor
+Puppy constructor for Bruno
+=== Q12 Method overloading vs overriding ===
+FancyPrinter.show(Object)
+show(String)
+FancyPrinter.show(Object)
+=== Q13 Abstract class vs interface ===
+Scooter has 2 wheels, charging, plug Type 2
+=== Q14 Polymorphism: what is and is not dynamic ===
+Child.who() | Parent.field | Parent.static | Child.static
+=== Q15 Can you override static, private or final methods? ===
+Base.secret | Base.locked
+=== Q16 How do you create an immutable class? ===
+members: [Asha, Ravi]
+add blocked: UnsupportedOperationException
+=== Q17 Composition vs inheritance (is-a vs has-a) ===
+stack = [99, 1, 2], pop gives 2
+=== Q18 Access modifiers ===
+a -> public
+b -> protected
+c -> (package-private)
+d -> private
+=== Q19 Object class methods: toString, equals, hashCode ===
+default toString looks like ClassName@hex: true
+p1.equals(p2) = true
+set contains an equal point: false
+=== Q20 Constructors: default, chained and private ===
+default config: dev:8080
+same Registry instance: true
diff --git a/fresher-qa/output/03-strings.txt b/fresher-qa/output/03-strings.txt
new file mode 100644
index 0000000..b82726b
--- /dev/null
+++ b/fresher-qa/output/03-strings.txt
@@ -0,0 +1,31 @@
+=== Q21 == vs equals() on Strings ===
+a == b -> true
+a == c -> false
+a.equals(c) -> true
+=== Q22 Why is String immutable? ===
+s = java
+upper = JAVA
+same object returned when nothing changes: true
+=== Q23 String vs StringBuilder vs StringBuffer ===
+builder result: cba-
+append is synchronized: StringBuffer=true, StringBuilder=false
+=== Q24 The string pool, compile-time constants and intern() ===
+"hel" + "lo" == "hello" -> true
+"hel" + lo == "hello" -> false
+"hel" + finalLo == "hello" -> true
+("hel" + lo).intern() == "hello" -> true
+=== Q25 Reverse a string and check for a palindrome ===
+weivretni
+true
+false
+=== Q26 Useful String methods and the split() surprises ===
+trim().length() = 6
+strip().length() = 2
+" ".isBlank() = true, "ab".repeat(3) = ababab
+"a,b,,".split(",").length = 2
+"a,b,,".split(",", -1).length = 4
+String.join: 2026-11-11
+=== Q27 Count characters, find the first non-repeating one, check anagrams ===
+frequency: {s=3, w=1, i=1}
+first non-repeating: w
+listen/silent anagrams: true
diff --git a/fresher-qa/output/04-collections.txt b/fresher-qa/output/04-collections.txt
new file mode 100644
index 0000000..2e2ad15
--- /dev/null
+++ b/fresher-qa/output/04-collections.txt
@@ -0,0 +1,49 @@
+=== Q28 The Collections Framework hierarchy ===
+ArrayList extends AbstractList implements [List, RandomAccess, Cloneable, Serializable]
+LinkedList extends AbstractSequentialList implements [List, Deque, Cloneable, Serializable]
+HashSet extends AbstractSet implements [Set, Cloneable, Serializable]
+TreeSet extends AbstractSet implements [NavigableSet, Cloneable, Serializable]
+ArrayDeque extends AbstractCollection implements [Deque, Cloneable, Serializable]
+HashMap extends AbstractMap implements [Map, Cloneable, Serializable]
+TreeMap extends AbstractMap implements [NavigableMap, Cloneable, Serializable]
+=== Q29 ArrayList vs LinkedList ===
+ArrayList is RandomAccess: true
+LinkedList is RandomAccess: false
+LinkedList is also a Deque: true
+after add(0, 5): [5, 10, 20, 30], get(2) = 20
+=== Q30 How HashMap works internally ===
+Aa: hashCode=2112, bucket (16 slots)=0
+BB: hashCode=2112, bucket (16 slots)=0
+Ankur: hashCode=63412443, bucket (16 slots)=12
+both survive a collision: first, second, size 2
+=== Q31 HashMap vs Hashtable vs ConcurrentHashMap vs TreeMap ===
+HashMap: null key accepted
+Hashtable: null key throws NullPointerException
+ConcurrentHashMap: null key throws NullPointerException
+TreeMap: null key throws NullPointerException
+=== Q32 HashSet vs LinkedHashSet vs TreeSet ===
+HashSet [banana, apple, cherry, pear, mango]
+LinkedHashSet [pear, apple, mango, banana, cherry]
+TreeSet [apple, banana, cherry, mango, pear]
+=== Q33 Comparable vs Comparator ===
+natural (Comparable): [Student[name=Asha, marks=88], Student[name=Meena, marks=88], Student[name=Ravi, marks=90]]
+marks desc, then name: [Student[name=Ravi, marks=90], Student[name=Asha, marks=88], Student[name=Meena, marks=88]]
+=== Q34 Fail-fast iterators and ConcurrentModificationException ===
+removing 2 inside for-each -> ConcurrentModificationException
+removing the second-to-last element threw nothing, list = [1, 2, 4]
+removeIf even -> [1, 3, 5]
+=== Q35 Arrays.asList() and List.of() pitfalls ===
+write-through to the array: changed
+Arrays.asList().add -> UnsupportedOperationException
+Arrays.asList(int[]) has 1 element
+List.of(.., null) -> NullPointerException
+=== Q36 Queue, Deque and PriorityQueue ===
+PriorityQueue polls: 1 2 3 4 5
+ArrayDeque as a stack pops: cba
+=== Q37 Mutable keys and the equals()/hashCode() contract in a HashMap ===
+get with the mutated key: null
+get with a fresh "A1" key: null
+size is still 1
+=== Q38 unmodifiableList() view vs List.copyOf() snapshot ===
+view sees the change: [a, b, c]
+copy does not: [a, b]
diff --git a/fresher-qa/output/05-exceptions.txt b/fresher-qa/output/05-exceptions.txt
new file mode 100644
index 0000000..874a9cd
--- /dev/null
+++ b/fresher-qa/output/05-exceptions.txt
@@ -0,0 +1,21 @@
+=== Q39 Checked vs unchecked exceptions ===
+checked: NoSuchFileException (is an IOException: true)
+unchecked: ArithmeticException: / by zero
+NoSuchFileException is checked: true
+=== Q40 try, catch, finally and return ===
+finally runs before the method actually returns
+returned 1
+primitiveSnapshot = 10
+finallyOverrides = 7
+=== Q41 throw vs throws, and custom exceptions ===
+withdrawal refused <- cause: balance 100 < 500
+=== Q42 try-with-resources and suppressed exceptions ===
+open first
+open second
+close second
+close first
+caught: body failed
+suppressed: close failed: second
+=== Q43 Error vs Exception, and multi-catch ===
+Error, not Exception: Requested array size exceeds VM limit
+multi-catch caught ClassCastException
diff --git a/fresher-qa/output/06-modern-java.txt b/fresher-qa/output/06-modern-java.txt
new file mode 100644
index 0000000..02e378a
--- /dev/null
+++ b/fresher-qa/output/06-modern-java.txt
@@ -0,0 +1,27 @@
+=== Q44 Lambdas and functional interfaces ===
+180.0 true 6 hello 5
+=== Q45 Streams: map, filter, collect, laziness ===
+[ASHA, ARJUN]
+{4=2, 5=2}
+nothing visited yet: no terminal operation
+visited asha
+first = asha
+reuse -> stream has already been operated upon or closed
+=== Q46 Optional ===
+expensive() called
+orElse : value
+orElseGet : value
+nothing here
+=== Q47 Records ===
+Point[x=1, y=2] equals Point[x=1, y=2]: true, x() = 1
+rejected: negative coordinate
+=== Q48 var, switch expressions and text blocks ===
+{"name": "asha",
+ "grade": "A"}
+=== Q49 Method references and default methods ===
+abc
+walk and swim
+=== Q50 Threads: start() vs run(), and virtual threads ===
+running on: main
+running on: worker-1
+virtual: true
diff --git a/fresher-qa/output/07-compile-errors.txt b/fresher-qa/output/07-compile-errors.txt
new file mode 100644
index 0000000..c42e3bd
--- /dev/null
+++ b/fresher-qa/output/07-compile-errors.txt
@@ -0,0 +1,38 @@
+=== javac FinalOverride.java ===
+FinalOverride.java:6: error: locked() in Derived cannot override locked() in Base
+ String locked() { return "Derived.locked"; }
+ ^
+ overridden method is final
+1 error
+
+=== javac CheckedNotHandled.java ===
+CheckedNotHandled.java:6: error: unreported exception IOException; must be caught or declared to be thrown
+ String s = Files.readString(Path.of("missing.txt"));
+ ^
+1 error
+
+=== javac LossyCast.java ===
+LossyCast.java:3: error: incompatible types: possible lossy conversion from double to int
+ int x = 3.5;
+ ^
+LossyCast.java:4: error: incompatible types: possible lossy conversion from int to byte
+ byte b = 200;
+ ^
+2 errors
+
+=== javac MissingReturnAndUnreachable.java ===
+MissingReturnAndUnreachable.java:4: error: missing return statement
+ }
+ ^
+1 error
+
+=== javac --release 21 PreSuper.java ===
+PreSuper.java:8: error: flexible constructors is not supported in -source 21
+ super();
+ ^
+ (use -source 25 or higher to enable flexible constructors)
+1 error
+
+=== javac --release 25 PreSuper.java ===
+compiled without errors
+
diff --git a/fresher-qa/pom.xml b/fresher-qa/pom.xml
new file mode 100644
index 0000000..35832e8
--- /dev/null
+++ b/fresher-qa/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+ com.ankurm.interviewlab
+ fresher-qa
+ 1.0.0
+ jar
+
+ java-interview-lab :: fresher-qa
+
+ Runnable companion code for ankurm.com's "Top 50 Java Interview Questions for Freshers" article:
+ one small demo per question, with tests that assert what each demo prints and write the transcripts the article quotes.
+
+
+
+ 25
+ UTF-8
+ 6.1.3
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junit.version}
+ pom
+ import
+
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.14.0
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.6.0
+
+ -Xmx512m -Xss1m -Duser.timezone=UTC -Dfile.encoding=UTF-8
+
+
+
+
+
diff --git a/fresher-qa/scripts/compile-errors.sh b/fresher-qa/scripts/compile-errors.sh
new file mode 100755
index 0000000..399b5f2
--- /dev/null
+++ b/fresher-qa/scripts/compile-errors.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# Compiles each file in src/broken/ (they are NOT part of the Maven build) and captures javac's own message.
+set -uo pipefail
+cd "$(dirname "$0")/../src/broken"
+out=../../output/07-compile-errors.txt
+: > "$out"
+for f in FinalOverride CheckedNotHandled LossyCast MissingReturnAndUnreachable; do
+ echo "=== javac $f.java ===" >> "$out"
+ javac --release 25 -d /tmp/fresher-broken "$f.java" 2>&1 | grep -v '^Picked up JAVA_TOOL_OPTIONS' >> "$out"
+ echo >> "$out"
+done
+# The same file under both language levels: an error before Java 25, fine from Java 25 (JEP 513).
+for rel in 21 25; do
+ echo "=== javac --release $rel PreSuper.java ===" >> "$out"
+ if javac --release "$rel" -d /tmp/fresher-broken "PreSuper.java" 2>&1 | grep -v '^Picked up JAVA_TOOL_OPTIONS' >> "$out"; [ "${PIPESTATUS[0]}" -eq 0 ]; then
+ echo "compiled without errors" >> "$out"
+ fi
+ echo >> "$out"
+done
+cat "$out"
diff --git a/fresher-qa/scripts/run-all.sh b/fresher-qa/scripts/run-all.sh
new file mode 100755
index 0000000..293ebfb
--- /dev/null
+++ b/fresher-qa/scripts/run-all.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# Regenerates every file in output/. Needs JDK 25 and Maven (first Maven run online).
+# 01-06 are written by JUnit tests that assert the lines they write; 07 is javac's own output for src/broken/.
+set -euo pipefail
+cd "$(dirname "$0")/.."
+mvn -B -q -T1 test
+./scripts/compile-errors.sh > /dev/null
+echo "done:"; ls -1 output
diff --git a/fresher-qa/src/broken/CheckedNotHandled.java b/fresher-qa/src/broken/CheckedNotHandled.java
new file mode 100644
index 0000000..76a424c
--- /dev/null
+++ b/fresher-qa/src/broken/CheckedNotHandled.java
@@ -0,0 +1,8 @@
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+class CheckedNotHandled {
+ public static void main(String[] args) {
+ String s = Files.readString(Path.of("missing.txt"));
+ }
+}
diff --git a/fresher-qa/src/broken/FinalOverride.java b/fresher-qa/src/broken/FinalOverride.java
new file mode 100644
index 0000000..84ad0d9
--- /dev/null
+++ b/fresher-qa/src/broken/FinalOverride.java
@@ -0,0 +1,7 @@
+class Base {
+ final String locked() { return "Base.locked"; }
+}
+
+class Derived extends Base {
+ String locked() { return "Derived.locked"; }
+}
diff --git a/fresher-qa/src/broken/LossyCast.java b/fresher-qa/src/broken/LossyCast.java
new file mode 100644
index 0000000..637f73d
--- /dev/null
+++ b/fresher-qa/src/broken/LossyCast.java
@@ -0,0 +1,6 @@
+class LossyCast {
+ public static void main(String[] args) {
+ int x = 3.5;
+ byte b = 200;
+ }
+}
diff --git a/fresher-qa/src/broken/MissingReturnAndUnreachable.java b/fresher-qa/src/broken/MissingReturnAndUnreachable.java
new file mode 100644
index 0000000..d614280
--- /dev/null
+++ b/fresher-qa/src/broken/MissingReturnAndUnreachable.java
@@ -0,0 +1,5 @@
+class MissingReturnAndUnreachable {
+ static int sign(int n) {
+ if (n > 0) return 1;
+ }
+}
diff --git a/fresher-qa/src/broken/PreSuper.java b/fresher-qa/src/broken/PreSuper.java
new file mode 100644
index 0000000..42dfaac
--- /dev/null
+++ b/fresher-qa/src/broken/PreSuper.java
@@ -0,0 +1,10 @@
+class Animal {
+ Animal() { }
+}
+
+class Puppy extends Animal {
+ Puppy(String name) {
+ if (name.isBlank()) throw new IllegalArgumentException("name required");
+ super();
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/BasicsQa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/BasicsQa.java
new file mode 100644
index 0000000..9437d7d
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/BasicsQa.java
@@ -0,0 +1,167 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Questions 1-9: how Java runs, primitives, casting, boxing, pass-by-value, static, final, stack and heap. */
+public final class BasicsQa {
+ private BasicsQa() {}
+
+ public static void main(String[] args) throws Exception {
+ run();
+ }
+
+ public static void run() throws Exception {
+ q01(); q02(); q03(); q04(); q05(); q06(); q07(); q08(); q09();
+ }
+
+ static void q01() throws IOException {
+ Qa.header(1, "Why is Java platform independent?");
+ // >>> q01
+ try (InputStream in = BasicsQa.class.getResourceAsStream("BasicsQa.class");
+ DataInputStream data = new DataInputStream(in)) {
+ int magic = data.readInt(); // first four bytes of every .class file
+ data.readUnsignedShort(); // minor version
+ int major = data.readUnsignedShort(); // class-file format version
+ System.out.println("magic = " + Integer.toHexString(magic).toUpperCase());
+ System.out.println("class file major version = " + major);
+ }
+ // <<< q01
+ }
+
+ static void q02() {
+ Qa.header(2, "JDK vs JRE vs JVM");
+ // >>> q02
+ System.out.println("running on Java " + Runtime.version().feature());
+ System.out.println("JVM: " + System.getProperty("java.vm.name"));
+ System.out.println("compiler available: " + (javax.tools.ToolProvider.getSystemJavaCompiler() != null));
+ // <<< q02
+ }
+
+ static int defaultInt;
+ static boolean defaultBoolean;
+ static String defaultString;
+
+ static void q03() {
+ Qa.header(3, "Primitive types and their sizes");
+ // >>> q03
+ System.out.println("byte " + Byte.BYTES + " byte, " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE);
+ System.out.println("short " + Short.BYTES + " bytes, " + Short.MIN_VALUE + " to " + Short.MAX_VALUE);
+ System.out.println("int " + Integer.BYTES + " bytes, " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);
+ System.out.println("long " + Long.BYTES + " bytes, " + Long.MIN_VALUE + " to " + Long.MAX_VALUE);
+ System.out.println("float " + Float.BYTES + " bytes, double " + Double.BYTES + " bytes, char " + Character.BYTES + " bytes");
+ System.out.println("field defaults: " + defaultInt + ", " + defaultBoolean + ", " + defaultString);
+ // <<< q03
+ }
+
+ static void q04() {
+ Qa.header(4, "Type casting and overflow");
+ // >>> q04
+ System.out.println("7 / 2 = " + (7 / 2) + ", (double) 7 / 2 = " + ((double) 7 / 2));
+ System.out.println("(byte) 130 = " + (byte) 130);
+ System.out.println("Integer.MAX_VALUE + 1 = " + (Integer.MAX_VALUE + 1));
+ char c = 'A';
+ System.out.println("c + 1 = " + (c + 1) + ", (char) (c + 1) = " + (char) (c + 1));
+ System.out.println("0.1 + 0.2 = " + (0.1 + 0.2));
+ try {
+ Math.addExact(Integer.MAX_VALUE, 1);
+ } catch (ArithmeticException e) {
+ System.out.println("Math.addExact: " + e.getMessage());
+ }
+ // <<< q04
+ }
+
+ static void q05() {
+ Qa.header(5, "Autoboxing and the Integer cache");
+ // >>> q05
+ Integer a = 127, b = 127;
+ Integer c = 128, d = 128;
+ System.out.println("127 == 127 -> " + (a == b));
+ System.out.println("128 == 128 -> " + (c == d));
+ System.out.println("128 equals 128 -> " + c.equals(d));
+ Long boxed = 127L;
+ System.out.println("Long 127 equals Integer 127 -> " + boxed.equals(127));
+ // <<< q05
+ }
+
+ // >>> q06
+ static void reassign(StringBuilder sb) {
+ sb = new StringBuilder("new object"); // changes only the local copy of the reference
+ }
+
+ static void mutate(StringBuilder sb) {
+ sb.append(" + mutated"); // follows the reference to the same object
+ }
+ // <<< q06
+
+ static void q06() {
+ Qa.header(6, "Is Java pass by value or pass by reference?");
+ // >>> q06b
+ StringBuilder sb = new StringBuilder("original");
+ reassign(sb);
+ System.out.println("after reassign: " + sb);
+ mutate(sb);
+ System.out.println("after mutate: " + sb);
+ // <<< q06b
+ }
+
+ // >>> q07
+ static class Order {
+ static int created;
+ static { System.out.println("static block runs once, when the class loads"); }
+ { System.out.println("instance block runs before each constructor body"); }
+
+ Order() {
+ created++;
+ System.out.println("constructor #" + created);
+ }
+ }
+ // <<< q07
+
+ static void q07() {
+ Qa.header(7, "static variables, static blocks and initialisation order");
+ // >>> q07b
+ new Order();
+ new Order();
+ // <<< q07b
+ }
+
+ static void q08() throws Exception {
+ Qa.header(8, "final, finally and finalize");
+ // >>> q08
+ final int[] numbers = {1, 2, 3};
+ numbers[0] = 99; // legal: final fixes the reference, not the contents
+ System.out.println("final array contents changed: " + numbers[0]);
+ try {
+ System.out.println("in try");
+ } finally {
+ System.out.println("finally always runs");
+ }
+ Deprecated d = Object.class.getDeclaredMethod("finalize").getAnnotation(Deprecated.class);
+ System.out.println("Object.finalize forRemoval = " + d.forRemoval() + ", since " + d.since());
+ // <<< q08
+ }
+
+ // >>> q09
+ static int depth;
+
+ static void recurseForever() {
+ depth++;
+ recurseForever();
+ }
+ // <<< q09
+
+ static void q09() {
+ Qa.header(9, "Stack vs heap");
+ // >>> q09b
+ int[] onHeap = new int[1_000_000]; // the array lives on the heap; only the reference sits on the stack
+ System.out.println("heap array length = " + onHeap.length);
+ try {
+ recurseForever();
+ } catch (StackOverflowError e) {
+ System.out.println("StackOverflowError after many frames: " + (depth > 1000));
+ }
+ // <<< q09b
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/CollectionsQa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/CollectionsQa.java
new file mode 100644
index 0000000..3b8699b
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/CollectionsQa.java
@@ -0,0 +1,213 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Queue;
+import java.util.RandomAccess;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** Questions 28-38: the framework, ArrayList vs LinkedList, HashMap internals, sets, sorting, iterators, pitfalls. */
+public final class CollectionsQa {
+ private CollectionsQa() {}
+
+ public static void main(String[] args) {
+ run();
+ }
+
+ public static void run() {
+ q28(); q29(); q30(); q31(); q32(); q33(); q34(); q35(); q36(); q37(); q38();
+ }
+
+ static void q28() {
+ Qa.header(28, "The Collections Framework hierarchy");
+ // >>> q28
+ for (Class> c : List.of(ArrayList.class, LinkedList.class, HashSet.class, TreeSet.class, ArrayDeque.class, HashMap.class, TreeMap.class)) {
+ String parents = Arrays.stream(c.getInterfaces()).map(Class::getSimpleName).toList().toString();
+ System.out.println(c.getSimpleName() + " extends " + c.getSuperclass().getSimpleName() + " implements " + parents);
+ }
+ // <<< q28
+ }
+
+ static void q29() {
+ Qa.header(29, "ArrayList vs LinkedList");
+ // >>> q29
+ System.out.println("ArrayList is RandomAccess: " + (new ArrayList<>() instanceof RandomAccess));
+ System.out.println("LinkedList is RandomAccess: " + (new LinkedList<>() instanceof RandomAccess));
+ System.out.println("LinkedList is also a Deque: " + (new LinkedList<>() instanceof Deque));
+ List list = new ArrayList<>(List.of(10, 20, 30));
+ list.add(0, 5); // ArrayList shifts every later element one slot right
+ System.out.println("after add(0, 5): " + list + ", get(2) = " + list.get(2));
+ // <<< q29
+ }
+
+ static void q30() {
+ Qa.header(30, "How HashMap works internally");
+ // >>> q30
+ // Same formula as java.util.HashMap.hash() followed by the (table.length - 1) mask
+ for (String key : new String[] {"Aa", "BB", "Ankur"}) {
+ int h = key.hashCode();
+ int spread = h ^ (h >>> 16);
+ System.out.println(key + ": hashCode=" + h + ", bucket (16 slots)=" + (spread & 15));
+ }
+ Map map = new HashMap<>();
+ map.put("Aa", "first");
+ map.put("BB", "second"); // same hashCode, so same bucket, but not equals()
+ System.out.println("both survive a collision: " + map.get("Aa") + ", " + map.get("BB") + ", size " + map.size());
+ // <<< q30
+ }
+
+ static void q31() {
+ Qa.header(31, "HashMap vs Hashtable vs ConcurrentHashMap vs TreeMap");
+ // >>> q31
+ Map> maps = new LinkedHashMap<>();
+ maps.put("HashMap", new HashMap<>());
+ maps.put("Hashtable", new Hashtable<>());
+ maps.put("ConcurrentHashMap", new ConcurrentHashMap<>());
+ maps.put("TreeMap", new TreeMap<>());
+ for (var e : maps.entrySet()) {
+ try {
+ e.getValue().put(null, 1);
+ System.out.println(e.getKey() + ": null key accepted");
+ } catch (NullPointerException ex) {
+ System.out.println(e.getKey() + ": null key throws NullPointerException");
+ }
+ }
+ // <<< q31
+ }
+
+ static void q32() {
+ Qa.header(32, "HashSet vs LinkedHashSet vs TreeSet");
+ // >>> q32
+ List fruit = List.of("pear", "apple", "mango", "banana", "cherry");
+ System.out.println("HashSet " + new HashSet<>(fruit));
+ System.out.println("LinkedHashSet " + new LinkedHashSet<>(fruit));
+ System.out.println("TreeSet " + new TreeSet<>(fruit));
+ // <<< q32
+ }
+
+ // >>> q33
+ record Student(String name, int marks) implements Comparable {
+ @Override
+ public int compareTo(Student other) { // the "natural" order: by name
+ return name.compareTo(other.name);
+ }
+ }
+ // <<< q33
+
+ static void q33() {
+ Qa.header(33, "Comparable vs Comparator");
+ // >>> q33b
+ List students = new ArrayList<>(List.of(new Student("Ravi", 90), new Student("Asha", 88), new Student("Meena", 88)));
+ Collections.sort(students);
+ System.out.println("natural (Comparable): " + students);
+ students.sort(Comparator.comparingInt(Student::marks).reversed().thenComparing(Student::name));
+ System.out.println("marks desc, then name: " + students);
+ // <<< q33b
+ }
+
+ static void q34() {
+ Qa.header(34, "Fail-fast iterators and ConcurrentModificationException");
+ // >>> q34
+ List list = new ArrayList<>(List.of(1, 2, 3, 4, 5));
+ try {
+ for (Integer n : list) {
+ if (n == 2) list.remove(n); // structural change while the for-each iterator is live
+ }
+ } catch (java.util.ConcurrentModificationException e) {
+ System.out.println("removing 2 inside for-each -> ConcurrentModificationException");
+ }
+ List second = new ArrayList<>(List.of(1, 2, 3, 4));
+ for (Integer n : second) {
+ if (n == 3) second.remove(n); // removing the second-to-last element: no exception, loop just stops
+ }
+ System.out.println("removing the second-to-last element threw nothing, list = " + second);
+ list.removeIf(n -> n % 2 == 0); // the safe way
+ System.out.println("removeIf even -> " + list);
+ // <<< q34
+ }
+
+ static void q35() {
+ Qa.header(35, "Arrays.asList() and List.of() pitfalls");
+ // >>> q35
+ String[] array = {"x", "y"};
+ List view = Arrays.asList(array);
+ view.set(0, "changed");
+ System.out.println("write-through to the array: " + array[0]);
+ try {
+ view.add("z");
+ } catch (UnsupportedOperationException e) {
+ System.out.println("Arrays.asList().add -> UnsupportedOperationException");
+ }
+ System.out.println("Arrays.asList(int[]) has " + Arrays.asList(new int[] {1, 2, 3}).size() + " element");
+ try {
+ List.of("a", null);
+ } catch (NullPointerException e) {
+ System.out.println("List.of(.., null) -> NullPointerException");
+ }
+ // <<< q35
+ }
+
+ static void q36() {
+ Qa.header(36, "Queue, Deque and PriorityQueue");
+ // >>> q36
+ Queue pq = new PriorityQueue<>(List.of(5, 1, 4, 2, 3));
+ StringBuilder order = new StringBuilder();
+ while (!pq.isEmpty()) order.append(pq.poll()).append(' ');
+ System.out.println("PriorityQueue polls: " + order.toString().trim());
+ Deque stack = new ArrayDeque<>();
+ stack.push("a"); stack.push("b"); stack.push("c");
+ System.out.println("ArrayDeque as a stack pops: " + stack.pop() + stack.pop() + stack.pop());
+ // <<< q36
+ }
+
+ // >>> q37
+ static class Key {
+ String id;
+ Key(String id) { this.id = id; }
+ @Override public boolean equals(Object o) { return o instanceof Key k && k.id.equals(id); }
+ @Override public int hashCode() { return id.hashCode(); }
+ }
+ // <<< q37
+
+ static void q37() {
+ Qa.header(37, "Mutable keys and the equals()/hashCode() contract in a HashMap");
+ // >>> q37b
+ Map map = new HashMap<>();
+ Key k = new Key("A1");
+ map.put(k, "value");
+ k.id = "B2"; // the hashCode changes after the entry was filed
+ System.out.println("get with the mutated key: " + map.get(k));
+ System.out.println("get with a fresh \"A1\" key: " + map.get(new Key("A1")));
+ System.out.println("size is still " + map.size());
+ // <<< q37b
+ }
+
+ static void q38() {
+ Qa.header(38, "unmodifiableList() view vs List.copyOf() snapshot");
+ // >>> q38
+ List original = new ArrayList<>(List.of("a", "b"));
+ List view = Collections.unmodifiableList(original);
+ List copy = List.copyOf(original);
+ original.add("c");
+ System.out.println("view sees the change: " + view);
+ System.out.println("copy does not: " + copy);
+ // <<< q38
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/ExceptionsQa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/ExceptionsQa.java
new file mode 100644
index 0000000..ecb4bcd
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/ExceptionsQa.java
@@ -0,0 +1,149 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+
+/** Questions 39-43: checked vs unchecked, finally and return, custom exceptions, try-with-resources, Error vs Exception. */
+public final class ExceptionsQa {
+ private ExceptionsQa() {}
+
+ public static void main(String[] args) {
+ run();
+ }
+
+ public static void run() {
+ q39(); q40(); q41(); q42(); q43();
+ }
+
+ static void q39() {
+ Qa.header(39, "Checked vs unchecked exceptions");
+ // >>> q39
+ try {
+ Files.readString(Path.of("missing.txt")); // checked: the compiler forces you to deal with IOException
+ } catch (IOException e) {
+ System.out.println("checked: " + e.getClass().getSimpleName() + " (is an IOException: " + (e instanceof IOException) + ")");
+ }
+ try {
+ int zero = 0;
+ System.out.println(10 / zero); // unchecked: no try/catch required
+ } catch (ArithmeticException e) {
+ System.out.println("unchecked: ArithmeticException: " + e.getMessage());
+ }
+ System.out.println("NoSuchFileException is checked: " + !RuntimeException.class.isAssignableFrom(NoSuchFileException.class));
+ // <<< q39
+ }
+
+ // >>> q40
+ @SuppressWarnings("finally")
+ static int tryOrFinally() {
+ try {
+ return 1;
+ } finally {
+ System.out.println("finally runs before the method actually returns");
+ }
+ }
+
+ static int primitiveSnapshot() {
+ int x = 10;
+ try {
+ return x; // value 10 is copied aside here
+ } finally {
+ x = 99; // too late to change what is returned
+ }
+ }
+
+ @SuppressWarnings("finally")
+ static int finallyOverrides() {
+ try {
+ throw new IllegalStateException("lost");
+ } finally {
+ return 7; // swallows the exception: never do this
+ }
+ }
+ // <<< q40
+
+ static void q40() {
+ Qa.header(40, "try, catch, finally and return");
+ // >>> q40b
+ System.out.println("returned " + tryOrFinally());
+ System.out.println("primitiveSnapshot = " + primitiveSnapshot());
+ System.out.println("finallyOverrides = " + finallyOverrides());
+ // <<< q40b
+ }
+
+ // >>> q41
+ static class InsufficientFundsException extends Exception {
+ InsufficientFundsException(String message, Throwable cause) { super(message, cause); }
+ }
+
+ static void withdraw(int balance, int amount) throws InsufficientFundsException {
+ try {
+ if (amount > balance) throw new IllegalArgumentException("balance " + balance + " < " + amount);
+ } catch (IllegalArgumentException e) {
+ throw new InsufficientFundsException("withdrawal refused", e); // wrap, keep the cause
+ }
+ }
+ // <<< q41
+
+ static void q41() {
+ Qa.header(41, "throw vs throws, and custom exceptions");
+ // >>> q41b
+ try {
+ withdraw(100, 500);
+ } catch (InsufficientFundsException e) {
+ System.out.println(e.getMessage() + " <- cause: " + e.getCause().getMessage());
+ }
+ // <<< q41b
+ }
+
+ // >>> q42
+ static class Res implements AutoCloseable {
+ private final String name;
+ private final boolean failOnClose;
+
+ Res(String name, boolean failOnClose) {
+ this.name = name;
+ this.failOnClose = failOnClose;
+ System.out.println("open " + name);
+ }
+
+ @Override
+ public void close() {
+ System.out.println("close " + name);
+ if (failOnClose) throw new IllegalStateException("close failed: " + name);
+ }
+ }
+ // <<< q42
+
+ static void q42() {
+ Qa.header(42, "try-with-resources and suppressed exceptions");
+ // >>> q42b
+ try (Res first = new Res("first", false); Res second = new Res("second", true)) {
+ throw new RuntimeException("body failed");
+ } catch (RuntimeException e) {
+ System.out.println("caught: " + e.getMessage());
+ System.out.println("suppressed: " + e.getSuppressed()[0].getMessage());
+ }
+ // <<< q42b
+ }
+
+ static void q43() {
+ Qa.header(43, "Error vs Exception, and multi-catch");
+ // >>> q43
+ try {
+ long[] tooBig = new long[Integer.MAX_VALUE];
+ System.out.println(tooBig.length);
+ } catch (OutOfMemoryError e) {
+ System.out.println("Error, not Exception: " + e.getMessage());
+ }
+ try {
+ Object o = "text";
+ Integer n = (Integer) o;
+ } catch (ClassCastException | NullPointerException e) { // multi-catch: one handler, several types
+ System.out.println("multi-catch caught " + e.getClass().getSimpleName());
+ }
+ // <<< q43
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/ModernJavaQa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/ModernJavaQa.java
new file mode 100644
index 0000000..e3d8735
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/ModernJavaQa.java
@@ -0,0 +1,151 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/** Questions 44-50: lambdas, streams, Optional, records, var/switch/text blocks, method references, threads. */
+public final class ModernJavaQa {
+ private ModernJavaQa() {}
+
+ public static void main(String[] args) throws Exception {
+ run();
+ }
+
+ public static void run() throws Exception {
+ q44(); q45(); q46(); q47(); q48(); q49(); q50();
+ }
+
+ // >>> q44
+ @FunctionalInterface
+ interface Discount {
+ double apply(double price);
+ }
+ // <<< q44
+
+ static void q44() {
+ Qa.header(44, "Lambdas and functional interfaces");
+ // >>> q44b
+ Discount tenOff = price -> price * 0.9;
+ Predicate isEmpty = String::isEmpty;
+ Function length = String::length;
+ Supplier greeting = () -> "hello";
+ BiFunction add = (x, y) -> x + y;
+ System.out.println(tenOff.apply(200) + " " + isEmpty.test("") + " " + length.apply("lambda")
+ + " " + greeting.get() + " " + add.apply(2, 3));
+ // <<< q44b
+ }
+
+ static void q45() {
+ Qa.header(45, "Streams: map, filter, collect, laziness");
+ // >>> q45
+ List names = List.of("asha", "ravi", "meena", "arjun");
+ System.out.println(names.stream().filter(n -> n.startsWith("a")).map(String::toUpperCase).collect(Collectors.toList()));
+ System.out.println(names.stream().collect(Collectors.groupingBy(String::length, Collectors.counting())));
+ Stream lazy = names.stream().peek(n -> System.out.println("visited " + n));
+ System.out.println("nothing visited yet: no terminal operation");
+ System.out.println("first = " + lazy.findFirst().get()); // stops after the first element
+ Stream used = names.stream();
+ used.count();
+ try {
+ used.count();
+ } catch (IllegalStateException e) {
+ System.out.println("reuse -> " + e.getMessage());
+ }
+ // <<< q45
+ }
+
+ static String expensive() {
+ System.out.println("expensive() called");
+ return "default";
+ }
+
+ static void q46() {
+ Qa.header(46, "Optional");
+ // >>> q46
+ Optional present = Optional.of("value");
+ System.out.println("orElse : " + present.orElse(expensive())); // argument is evaluated even though unused
+ System.out.println("orElseGet : " + present.orElseGet(() -> expensive())); // lambda is never run
+ Optional empty = Optional.empty();
+ System.out.println(empty.map(String::toUpperCase).orElse("nothing here"));
+ // <<< q46
+ }
+
+ // >>> q47
+ record Point(int x, int y) {
+ Point { // compact constructor: validation
+ if (x < 0 || y < 0) throw new IllegalArgumentException("negative coordinate");
+ }
+ }
+ // <<< q47
+
+ static void q47() {
+ Qa.header(47, "Records");
+ // >>> q47b
+ Point p = new Point(1, 2);
+ System.out.println(p + " equals " + new Point(1, 2) + ": " + p.equals(new Point(1, 2)) + ", x() = " + p.x());
+ try {
+ new Point(-1, 0);
+ } catch (IllegalArgumentException e) {
+ System.out.println("rejected: " + e.getMessage());
+ }
+ // <<< q47b
+ }
+
+ static void q48() {
+ Qa.header(48, "var, switch expressions and text blocks");
+ // >>> q48
+ var scores = Map.of("asha", 91);
+ int marks = scores.get("asha");
+ String grade = switch (marks / 10) {
+ case 10, 9 -> "A";
+ case 8 -> "B";
+ default -> "C";
+ };
+ String json = """
+ {"name": "asha",
+ "grade": "%s"}
+ """.formatted(grade);
+ System.out.print(json);
+ // <<< q48
+ }
+
+ // >>> q49
+ interface Walker { default String move() { return "walk"; } }
+ interface Swimmer { default String move() { return "swim"; } }
+
+ static class Duck implements Walker, Swimmer {
+ @Override public String move() { return Walker.super.move() + " and " + Swimmer.super.move(); }
+ }
+ // <<< q49
+
+ static void q49() {
+ Qa.header(49, "Method references and default methods");
+ // >>> q49b
+ List items = List.of("b", "a", "c");
+ items.stream().sorted(String::compareTo).forEach(System.out::print); // static-style and bound references
+ System.out.println();
+ System.out.println(new Duck().move());
+ // <<< q49b
+ }
+
+ static void q50() throws Exception {
+ Qa.header(50, "Threads: start() vs run(), and virtual threads");
+ // >>> q50
+ Runnable task = () -> System.out.println("running on: " + Thread.currentThread().getName());
+ Thread t = new Thread(task, "worker-1");
+ t.run(); // plain method call, still on main
+ t.start(); // new thread
+ t.join();
+ Thread v = Thread.ofVirtual().name("virtual-1").start(() ->
+ System.out.println("virtual: " + Thread.currentThread().isVirtual()));
+ v.join();
+ // <<< q50
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/OopQa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/OopQa.java
new file mode 100644
index 0000000..b3a52a5
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/OopQa.java
@@ -0,0 +1,309 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+/** Questions 10-20: the four pillars, constructors, overloading vs overriding, abstract vs interface, immutability. */
+public final class OopQa {
+ private OopQa() {}
+
+ public static void main(String[] args) {
+ run();
+ }
+
+ public static void run() {
+ q10(); q11(); q12(); q13(); q14(); q15(); q16(); q17(); q18(); q19(); q20();
+ }
+
+ // ---------------------------------------------------------------- Q10
+ // >>> q10
+ interface Shape { // abstraction: callers only know "area()"
+ double area();
+ }
+
+ record Circle(double radius) implements Shape {
+ public double area() { return Math.PI * radius * radius; }
+ }
+
+ record Rectangle(double w, double h) implements Shape {
+ public double area() { return w * h; }
+ }
+
+ static class Account {
+ private double balance; // encapsulation: no outside code can touch this directly
+
+ void deposit(double amount) {
+ if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
+ balance += amount;
+ }
+
+ double balance() { return balance; }
+ }
+ // <<< q10
+
+ static void q10() {
+ Qa.header(10, "The four pillars of OOP");
+ // >>> q10b
+ List shapes = List.of(new Circle(1), new Rectangle(2, 3));
+ for (Shape s : shapes) { // polymorphism: one call, different behaviour
+ System.out.printf("%s area = %.2f%n", s.getClass().getSimpleName(), s.area());
+ }
+ Account account = new Account();
+ account.deposit(500);
+ try {
+ account.deposit(-1);
+ } catch (IllegalArgumentException e) {
+ System.out.println("rejected: " + e.getMessage() + ", balance still " + account.balance());
+ }
+ // <<< q10b
+ }
+
+ // ---------------------------------------------------------------- Q11
+ // >>> q11
+ static class Animal {
+ Animal() { System.out.println("Animal constructor"); }
+ }
+
+ static class Dog extends Animal {
+ Dog() {
+ super(); // implicit if you leave it out
+ System.out.println("Dog constructor");
+ }
+ }
+
+ static class Puppy extends Animal {
+ Puppy(String name) {
+ if (name.isBlank()) throw new IllegalArgumentException("name required"); // before super(): legal since Java 25
+ super();
+ System.out.println("Puppy constructor for " + name);
+ }
+ }
+ // <<< q11
+
+ static void q11() {
+ Qa.header(11, "Inheritance and constructor chaining");
+ // >>> q11b
+ new Dog();
+ new Puppy("Bruno");
+ // <<< q11b
+ }
+
+ // ---------------------------------------------------------------- Q12
+ // >>> q12
+ static class Printer {
+ String show(Object o) { return "show(Object)"; }
+ String show(String s) { return "show(String)"; }
+ }
+
+ static class FancyPrinter extends Printer {
+ @Override
+ String show(Object o) { return "FancyPrinter.show(Object)"; }
+ }
+ // <<< q12
+
+ static void q12() {
+ Qa.header(12, "Method overloading vs overriding");
+ // >>> q12b
+ Printer p = new FancyPrinter();
+ Object text = "hello";
+ System.out.println(p.show(text)); // compile time picks show(Object) from the declared type; run time picks FancyPrinter's override
+ System.out.println(p.show("hello")); // compile time picks show(String); FancyPrinter never overrode it
+ System.out.println(p.show((Object) 42)); // same as the first line: overload by declared type, body by real class
+ // <<< q12b
+ }
+
+ // ---------------------------------------------------------------- Q13
+ // >>> q13
+ abstract static class Vehicle { // abstract class: state + constructor + partial code
+ protected final String name;
+ Vehicle(String name) { this.name = name; }
+ abstract int wheels();
+ String describe() { return name + " has " + wheels() + " wheels"; }
+ }
+
+ interface Electric { // interface: a capability, many allowed
+ default String charge() { return "charging"; }
+ static String standard() { return "Type 2"; }
+ }
+
+ static class Scooter extends Vehicle implements Electric {
+ Scooter() { super("Scooter"); }
+ int wheels() { return 2; }
+ }
+ // <<< q13
+
+ static void q13() {
+ Qa.header(13, "Abstract class vs interface");
+ // >>> q13b
+ Scooter s = new Scooter();
+ System.out.println(s.describe() + ", " + s.charge() + ", plug " + Electric.standard());
+ // <<< q13b
+ }
+
+ // ---------------------------------------------------------------- Q14
+ // >>> q14
+ static class Parent {
+ String name = "Parent.field";
+ static String kind() { return "Parent.static"; }
+ String who() { return "Parent.who()"; }
+ }
+
+ static class Child extends Parent {
+ String name = "Child.field"; // hides, does not override
+ static String kind() { return "Child.static"; } // hides, does not override
+ @Override String who() { return "Child.who()"; }
+ }
+ // <<< q14
+
+ static void q14() {
+ Qa.header(14, "Polymorphism: what is and is not dynamic");
+ // >>> q14b
+ Parent p = new Child();
+ System.out.println(p.who() + " | " + p.name + " | " + Parent.kind() + " | " + Child.kind());
+ // <<< q14b
+ }
+
+ // ---------------------------------------------------------------- Q15
+ // >>> q15
+ static class Base {
+ static String hello() { return "Base.hello"; }
+ private String secret() { return "Base.secret"; }
+ final String locked() { return "Base.locked"; }
+ String callSecret() { return secret(); }
+ }
+
+ static class Derived extends Base {
+ static String hello() { return "Derived.hello"; } // legal: hides
+ private String secret() { return "Derived.secret"; } // legal: a new, unrelated method
+ // final String locked() { ... } // compile error: cannot override final
+ }
+ // <<< q15
+
+ static void q15() {
+ Qa.header(15, "Can you override static, private or final methods?");
+ // >>> q15b
+ Base b = new Derived();
+ System.out.println(b.callSecret() + " | " + b.locked());
+ // <<< q15b
+ }
+
+ // ---------------------------------------------------------------- Q16
+ // >>> q16
+ static final class Team { // final class: nobody can subclass and add mutators
+ private final String name;
+ private final List members;
+
+ Team(String name, List members) {
+ this.name = name;
+ this.members = List.copyOf(members); // defensive copy in
+ }
+
+ String name() { return name; }
+ List members() { return members; } // already unmodifiable, safe to return
+ }
+ // <<< q16
+
+ static void q16() {
+ Qa.header(16, "How do you create an immutable class?");
+ // >>> q16b
+ List input = new ArrayList<>(List.of("Asha", "Ravi"));
+ Team team = new Team("Core", input);
+ input.add("Intruder"); // does not leak into the Team
+ System.out.println("members: " + team.members());
+ try {
+ team.members().add("Hacker");
+ } catch (UnsupportedOperationException e) {
+ System.out.println("add blocked: UnsupportedOperationException");
+ }
+ // <<< q16b
+ }
+
+ // ---------------------------------------------------------------- Q17
+ static void q17() {
+ Qa.header(17, "Composition vs inheritance (is-a vs has-a)");
+ // >>> q17
+ Stack stack = new Stack<>(); // java.util.Stack EXTENDS Vector, so it inherits all of Vector
+ stack.push(1);
+ stack.push(2);
+ stack.add(0, 99); // legal, and it breaks "last in, first out"
+ System.out.println("stack = " + stack + ", pop gives " + stack.pop());
+ // <<< q17
+ }
+
+ // ---------------------------------------------------------------- Q18
+ // >>> q18
+ static class Modifiers {
+ public int a;
+ protected int b;
+ int c; // package-private: no keyword at all
+ private int d;
+ }
+ // <<< q18
+
+ static void q18() {
+ Qa.header(18, "Access modifiers");
+ // >>> q18b
+ for (Field f : Modifiers.class.getDeclaredFields()) {
+ String m = Modifier.toString(f.getModifiers());
+ System.out.println(f.getName() + " -> " + (m.isEmpty() ? "(package-private)" : m));
+ }
+ // <<< q18b
+ }
+
+ // ---------------------------------------------------------------- Q19
+ // >>> q19
+ static class Point {
+ final int x, y;
+ Point(int x, int y) { this.x = x; this.y = y; }
+ @Override public boolean equals(Object o) {
+ return o instanceof Point p && p.x == x && p.y == y;
+ }
+ // hashCode() deliberately NOT overridden
+ }
+ // <<< q19
+
+ static void q19() {
+ Qa.header(19, "Object class methods: toString, equals, hashCode");
+ // >>> q19b
+ Object plain = new Object();
+ System.out.println("default toString looks like ClassName@hex: " + plain.toString().matches("java\\.lang\\.Object@[0-9a-f]+"));
+ Point p1 = new Point(1, 2), p2 = new Point(1, 2);
+ System.out.println("p1.equals(p2) = " + p1.equals(p2));
+ Set set = new HashSet<>(List.of(p1));
+ System.out.println("set contains an equal point: " + set.contains(p2));
+ // <<< q19b
+ }
+
+ // ---------------------------------------------------------------- Q20
+ // >>> q20
+ static class Config {
+ final String env;
+ final int port;
+
+ Config() { this("dev", 8080); } // this(...) chains to another constructor
+ Config(String env, int port) { this.env = env; this.port = port; }
+ // The moment you write ANY constructor, the free no-argument one disappears.
+ // So Config() above had to be written by hand.
+ }
+
+ static class Registry {
+ private static final Registry INSTANCE = new Registry();
+ private Registry() {} // private constructor: nobody else can call new
+ static Registry get() { return INSTANCE; }
+ }
+ // <<< q20
+
+ static void q20() {
+ Qa.header(20, "Constructors: default, chained and private");
+ // >>> q20b
+ Config c = new Config();
+ System.out.println("default config: " + c.env + ":" + c.port);
+ System.out.println("same Registry instance: " + (Registry.get() == Registry.get()));
+ // <<< q20b
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/Qa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/Qa.java
new file mode 100644
index 0000000..dc7299d
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/Qa.java
@@ -0,0 +1,14 @@
+package com.ankurm.interviewlab.fresherqa;
+
+/** Tiny helper shared by the six question classes: prints the section header the transcripts use. */
+final class Qa {
+ private Qa() {}
+
+ static void header(int n, String title) {
+ System.out.println("=== Q" + (n < 10 ? "0" : "") + n + " " + title + " ===");
+ }
+
+ static void blank() {
+ System.out.println();
+ }
+}
diff --git a/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/StringsQa.java b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/StringsQa.java
new file mode 100644
index 0000000..ef0c843
--- /dev/null
+++ b/fresher-qa/src/main/java/com/ankurm/interviewlab/fresherqa/StringsQa.java
@@ -0,0 +1,127 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** Questions 21-27: equality and the string pool, immutability, StringBuilder, intern, reverse, methods, frequency. */
+public final class StringsQa {
+ private StringsQa() {}
+
+ public static void main(String[] args) throws Exception {
+ run();
+ }
+
+ public static void run() throws Exception {
+ q21(); q22(); q23(); q24(); q25(); q26(); q27();
+ }
+
+ static void q21() {
+ Qa.header(21, "== vs equals() on Strings");
+ // >>> q21
+ String a = "java";
+ String b = "java";
+ String c = new String("java");
+ System.out.println("a == b -> " + (a == b)); // two literals share one pooled object
+ System.out.println("a == c -> " + (a == c)); // new always builds a separate object
+ System.out.println("a.equals(c) -> " + a.equals(c)); // equals compares the characters
+ // <<< q21
+ }
+
+ static void q22() {
+ Qa.header(22, "Why is String immutable?");
+ // >>> q22
+ String s = "java";
+ String upper = s.toUpperCase();
+ System.out.println("s = " + s);
+ System.out.println("upper = " + upper);
+ System.out.println("same object returned when nothing changes: " + (s.toLowerCase() == s));
+ // <<< q22
+ }
+
+ static void q23() throws Exception {
+ Qa.header(23, "String vs StringBuilder vs StringBuffer");
+ // >>> q23
+ StringBuilder sb = new StringBuilder("a");
+ sb.append("b").append("c").insert(0, "-").reverse();
+ System.out.println("builder result: " + sb);
+ boolean buf = Modifier.isSynchronized(StringBuffer.class.getMethod("append", String.class).getModifiers());
+ boolean bld = Modifier.isSynchronized(StringBuilder.class.getMethod("append", String.class).getModifiers());
+ System.out.println("append is synchronized: StringBuffer=" + buf + ", StringBuilder=" + bld);
+ // <<< q23
+ }
+
+ static void q24() {
+ Qa.header(24, "The string pool, compile-time constants and intern()");
+ // >>> q24
+ String lo = "lo";
+ final String finalLo = "lo";
+ System.out.println("\"hel\" + \"lo\" == \"hello\" -> " + ("hel" + "lo" == "hello"));
+ System.out.println("\"hel\" + lo == \"hello\" -> " + ("hel" + lo == "hello"));
+ System.out.println("\"hel\" + finalLo == \"hello\" -> " + ("hel" + finalLo == "hello"));
+ System.out.println("(\"hel\" + lo).intern() == \"hello\" -> " + (("hel" + lo).intern() == "hello"));
+ // <<< q24
+ }
+
+ // >>> q25
+ static String reverse(String s) {
+ return new StringBuilder(s).reverse().toString();
+ }
+
+ static boolean isPalindrome(String s) {
+ String cleaned = s.toLowerCase().replaceAll("[^a-z0-9]", "");
+ return cleaned.equals(reverse(cleaned));
+ }
+ // <<< q25
+
+ static void q25() {
+ Qa.header(25, "Reverse a string and check for a palindrome");
+ // >>> q25b
+ System.out.println(reverse("interview"));
+ System.out.println(isPalindrome("A man, a plan, a canal: Panama"));
+ System.out.println(isPalindrome("hello"));
+ // <<< q25b
+ }
+
+ static void q26() {
+ Qa.header(26, "Useful String methods and the split() surprises");
+ // >>> q26
+ String spaced = "\u2003 hi \u2003"; // an em space on each side
+ System.out.println("trim().length() = " + spaced.trim().length());
+ System.out.println("strip().length() = " + spaced.strip().length());
+ System.out.println("\" \".isBlank() = " + " ".isBlank() + ", \"ab\".repeat(3) = " + "ab".repeat(3));
+ System.out.println("\"a,b,,\".split(\",\").length = " + "a,b,,".split(",").length);
+ System.out.println("\"a,b,,\".split(\",\", -1).length = " + "a,b,,".split(",", -1).length);
+ System.out.println("String.join: " + String.join("-", "2026", "11", "11"));
+ // <<< q26
+ }
+
+ // >>> q27
+ static Map frequency(String s) {
+ Map counts = new LinkedHashMap<>(); // keeps first-seen order
+ for (char ch : s.toCharArray()) {
+ counts.merge(ch, 1, Integer::sum);
+ }
+ return counts;
+ }
+
+ static boolean areAnagrams(String x, String y) {
+ char[] a = x.toCharArray(), b = y.toCharArray();
+ Arrays.sort(a);
+ Arrays.sort(b);
+ return Arrays.equals(a, b);
+ }
+ // <<< q27
+
+ static void q27() {
+ Qa.header(27, "Count characters, find the first non-repeating one, check anagrams");
+ // >>> q27b
+ Map counts = frequency("swiss");
+ System.out.println("frequency: " + counts);
+ char first = counts.entrySet().stream().filter(e -> e.getValue() == 1).findFirst().get().getKey();
+ System.out.println("first non-repeating: " + first);
+ System.out.println("listen/silent anagrams: " + areAnagrams("listen", "silent"));
+ // <<< q27b
+ }
+}
diff --git a/fresher-qa/src/test/java/com/ankurm/interviewlab/fresherqa/TranscriptsTest.java b/fresher-qa/src/test/java/com/ankurm/interviewlab/fresherqa/TranscriptsTest.java
new file mode 100644
index 0000000..7445a6c
--- /dev/null
+++ b/fresher-qa/src/test/java/com/ankurm/interviewlab/fresherqa/TranscriptsTest.java
@@ -0,0 +1,94 @@
+package com.ankurm.interviewlab.fresherqa;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.Callable;
+import org.junit.jupiter.api.Test;
+
+/** Runs each question class, asserts the claims the article makes, and writes output/NN-*.txt. */
+class TranscriptsTest {
+
+ private static String capture(String file, Callable body) throws Exception {
+ PrintStream original = System.out;
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(buffer, true, StandardCharsets.UTF_8));
+ try {
+ body.call();
+ } finally {
+ System.setOut(original);
+ }
+ String text = buffer.toString(StandardCharsets.UTF_8);
+ Files.createDirectories(Path.of("output"));
+ Files.writeString(Path.of("output", file), text);
+ return text;
+ }
+
+ private static void has(String text, String... expected) {
+ for (String e : expected) assertTrue(text.contains(e), "missing: " + e + "\n" + text);
+ }
+
+ @Test
+ void basics() throws Exception {
+ String t = capture("01-basics.txt", () -> { BasicsQa.run(); return null; });
+ has(t, "magic = CAFEBABE", "class file major version = 69", "running on Java 25",
+ "(byte) 130 = -126", "Integer.MAX_VALUE + 1 = -2147483648", "0.1 + 0.2 = 0.30000000000000004",
+ "127 == 127 -> true", "128 == 128 -> false", "Long 127 equals Integer 127 -> false",
+ "after reassign: original", "after mutate: original + mutated",
+ "Object.finalize forRemoval = true", "StackOverflowError after many frames: true");
+ assertTrue(t.indexOf("static block") < t.indexOf("instance block"));
+ assertTrue(t.indexOf("instance block") < t.indexOf("constructor #1"));
+ }
+
+ @Test
+ void oop() throws Exception {
+ String t = capture("02-oop.txt", () -> { OopQa.run(); return null; });
+ has(t, "rejected: amount must be positive, balance still 500.0",
+ "Animal constructor\nDog constructor", "Puppy constructor for Bruno",
+ "FancyPrinter.show(Object)\nshow(String)\nFancyPrinter.show(Object)",
+ "Child.who() | Parent.field | Parent.static | Child.static",
+ "Base.secret | Base.locked", "members: [Asha, Ravi]", "add blocked: UnsupportedOperationException",
+ "stack = [99, 1, 2], pop gives 2", "p1.equals(p2) = true", "set contains an equal point: false",
+ "same Registry instance: true", "d -> private", "c -> (package-private)");
+ }
+
+ @Test
+ void strings() throws Exception {
+ String t = capture("03-strings.txt", () -> { StringsQa.run(); return null; });
+ has(t, "a == b -> true", "a == c -> false", "a.equals(c) -> true",
+ "append is synchronized: StringBuffer=true, StringBuilder=false",
+ "trim().length() = 6", "strip().length() = 2",
+ "\"a,b,,\".split(\",\").length = 2", "\"a,b,,\".split(\",\", -1).length = 4",
+ "first non-repeating: w", "listen/silent anagrams: true");
+ }
+
+ @Test
+ void collections() throws Exception {
+ String t = capture("04-collections.txt", () -> { CollectionsQa.run(); return null; });
+ has(t, "Aa: hashCode=2112", "BB: hashCode=2112", "both survive a collision: first, second, size 2",
+ "HashMap: null key accepted", "Hashtable: null key throws NullPointerException",
+ "TreeMap: null key throws NullPointerException",
+ "LinkedHashSet [pear, apple, mango, banana, cherry]", "TreeSet [apple, banana, cherry, mango, pear]",
+ "removing 2 inside for-each -> ConcurrentModificationException",
+ "get with the mutated key: null", "get with a fresh \"A1\" key: null", "size is still 1",
+ "view sees the change: [a, b, c]", "copy does not: [a, b]");
+ }
+
+ @Test
+ void exceptions() throws Exception {
+ String t = capture("05-exceptions.txt", () -> { ExceptionsQa.run(); return null; });
+ has(t, "primitiveSnapshot = 10", "finallyOverrides = 7", "withdrawal refused <- cause: balance 100 < 500",
+ "close second\nclose first", "suppressed: close failed: second");
+ }
+
+ @Test
+ void modern() throws Exception {
+ String t = capture("06-modern-java.txt", () -> { ModernJavaQa.run(); return null; });
+ has(t, "180.0 true 6 hello 5", "[ASHA, ARJUN]", "reuse -> stream has already been operated upon or closed",
+ "running on: main", "running on: worker-1", "virtual: true", "walk and swim");
+ }
+}