☕ Language Core

Java Interview Master Notes

The 10 questions every interviewer asks about Java core — Google, Amazon, Flipkart. Understand these and you'll outclass 90% of candidates in the first 15 minutes.

10Concepts
3Difficulty Levels
~40mFull Read
100%Interview Ready
1
JDK, JRE, JVM — The Holy Trinity
Easy ~3 min
JVM runs bytecode. JRE = JVM + libraries to run Java apps. JDK = JRE + compiler + tools to build Java apps. It's nested: JVM ⊂ JRE ⊂ JDK.

Think of it like a restaurant. JVM is the chef — the actual engine that executes Java code. JRE is the full kitchen — chef plus all the utensils and ingredients (class libraries, runtime support) needed to actually serve food (run Java programs). JDK is the entire restaurant building — kitchen plus the architecture room where you design new recipes (javac compiler, debugger, javadoc, jar tool, etc.).

As a user who just wants to run a Java app? You install JRE. As a developer building Java apps? You install JDK. The JDK includes a JRE inside it, so you don't need both separately.

The JVM itself is a specification — not a single product. Oracle's HotSpot, OpenJ9, GraalVM are all JVM implementations. They all run the same bytecode (.class files) but differ in performance, GC strategies, startup time etc.

JDK javac · javadoc · jar · jdb · jshell · keytool JRE Java Class Libraries (java.lang, java.util, java.io…) JVM Class Loader Bytecode Verifier Interpreter / JIT GC + Runtime 👤 User needs JRE → 👨‍💻 Developer needs JDK →
JVM ⊂ JRE ⊂ JDK — every outer layer contains everything inside it
Key Points
  • JVM is platform-specific (different JVM for Windows/Linux/Mac) but bytecode is platform-neutral
  • JRE includes: JVM + Java SE API (rt.jar) + supporting files
  • JDK includes: JRE + javac (compiler) + javadoc + jar + jdb (debugger) + jshell (REPL)
  • From Java 9+, JRE as a standalone download was discontinued — JDK only
  • HotSpot JVM uses JIT (Just-In-Time) compilation to convert hot bytecode to native machine code

Gotchas

  • Saying "JVM is platform independent" is WRONG — JVM itself is platform-dependent. The bytecode it runs is platform independent.
  • Don't confuse JIT compiler (inside JVM, converts bytecode to native code at runtime) with javac (converts .java to .class bytecode at compile time)
2
How Java Achieves Platform Independence
Easy ~2 min
"Write Once, Run Anywhere" — Java compiles to bytecode (not machine code), and every platform has its own JVM that translates that bytecode to native instructions.

In C/C++, your code compiles directly to machine code — the binary is specific to Windows x86 or Linux ARM. You want to run on another OS? Recompile. Java took a different route.

When you write Java and compile with javac, you get .class files containing bytecode — an intermediate representation that's not native to any CPU. It's like a universal language that any JVM understands.

Each OS has its own JVM implementation that knows how to translate that bytecode to the native machine code of that specific platform. So the same HelloWorld.class runs on Windows, Linux, Mac — unchanged. The JVM absorbs the platform difference.

HelloWorld .java Source Code javac HelloWorld .class Bytecode JVM (Windows) JVM (Linux) JVM (macOS) x86 x64 ARM Same .class file runs on ALL platforms — JVM handles the difference
Same bytecode, different JVMs, same result — that's platform independence
Key Points
  • Bytecode is intermediate — not machine code, not source code
  • Each JVM is platform-dependent; bytecode is platform-independent
  • JIT compiler inside JVM converts hot bytecode to native code at runtime for performance
  • WORA: "Write Once, Run Anywhere" — Java's core marketing promise since 1995

Gotchas

  • "Java is 100% platform independent" — not entirely true. File paths, native libraries (JNI), GUI rendering can still be platform-specific.

Competitive Edge

GraalVM takes this further — it can AOT (Ahead-Of-Time) compile Java to native binary (no JVM needed at runtime), giving fast startup and lower memory. Used in production by Twitter/now X, Netflix. Mention this if asked about Java performance improvements.

3
Heap vs Stack Memory in JVM
Medium ~3 min
Stack stores method call frames and local primitives — one per thread, fast, auto-cleaned. Heap stores all objects — shared, GC-managed, slower.

Every thread in Java gets its own Stack. When a method is called, a new frame is pushed with local variables, return address, operand stack. Method returns? Frame popped. Clean, automatic, no GC needed.

The Heap is where every object you create with new lives. It's shared across all threads (which is why you need synchronization). The GC watches the heap and collects objects with no remaining references.

The JVM Heap is itself divided: Young Generation (new objects — Eden + Survivor S0/S1), Old Generation / Tenured (long-lived objects), and before Java 8, PermGen (class metadata — now replaced by Metaspace in the native memory).

Thread 1 Stack main() frame foo() frame bar() frame Thread 2 Stack run() frame HEAP (Shared) Young Generation Eden S0 S1 Old Generation (Tenured) Long-lived objects Metaspace Class meta (native mem) GC manages entire heap — all threads share it
Each thread has its own Stack. All threads share one Heap. Heap has Young + Old Generation + Metaspace.
Key Points
  • Stack: thread-local, LIFO, stores primitives + references + method frames. Cleared automatically on method return.
  • Heap: shared across threads, all objects go here, GC managed
  • Young Gen: new objects start here. Minor GC is frequent and fast.
  • Old Gen: survivors promoted here. Major/Full GC is rare and slow.
  • Metaspace (Java 8+): replaced PermGen. Stores class metadata in native memory — no fixed size, grows dynamically
  • StackOverflowError: stack too deep (deep recursion). OutOfMemoryError: heap exhausted.

Gotchas

  • "Objects always go on heap" — mostly true, but JVM escape analysis can stack-allocate objects that don't escape the method. JIT optimization.
  • PermGen was removed in Java 8, replaced by Metaspace. Saying PermGen in an interview without clarifying Java version is a yellow flag.
4
How Garbage Collection Works in Java
Medium ~4 min
GC automatically finds objects on the heap with no live references and frees their memory — using generational collection to be efficient about it.

Java's GC is based on one key insight: most objects die young. You create a String to format a log line, use it, and it's garbage. These short-lived objects are the vast majority. So GC focuses most effort on the Young Generation where new objects land.

How it works step by step: New objects go to Eden (Young Gen). When Eden fills up, a Minor GC runs — it marks all reachable objects starting from GC roots (stack variables, static fields, JNI refs). Live objects are copied to Survivor space S0. On next Minor GC, S0 survivors go to S1, S1 goes to S0 (they alternate). After surviving a certain number of GC cycles (default 15, the "tenuring threshold"), objects are promoted to Old Gen.

When Old Gen fills up, a Major GC (or Full GC) runs — more expensive, longer pause. The goal of modern GC tuning is to keep Full GCs rare.

GC algorithms in Java: Serial GC (single-threaded, small apps), Parallel GC (multi-threaded, throughput focus — Java 8 default), G1GC (Garbage First — splits heap into regions, targets low pause time — Java 9+ default), ZGC (sub-millisecond pauses, Java 15+), Shenandoah (low pause, concurrent).

Key Points
  • GC Roots: thread stacks, static variables, JNI references — starting points for reachability
  • Minor GC: young gen only, fast (milliseconds). Major/Full GC: entire heap, slow (can be seconds)
  • Stop-The-World (STW) pause: all application threads paused while GC runs (in some phases)
  • G1GC: divides heap into equal-size regions, collects garbage-first (highest garbage regions), predictable pause times. Default since Java 9.
  • ZGC/Shenandoah: concurrent GC — almost all work done while app runs. Sub-millisecond pauses.
  • You can request GC with System.gc() but JVM may ignore it — it's just a hint
Common Follow-ups
Can objects be garbage collected while they're still referenced?
No. GC only collects unreachable objects. But WeakReference and SoftReference allow GC to collect objects even with references — used for caches.
What causes a memory leak in Java if GC is automatic?
Holding references to objects you no longer need. Classic examples: static collections that grow forever, unclosed streams, event listeners never removed, ThreadLocal not cleaned up.

Gotchas

  • "GC prevents all memory leaks" — FALSE. If you hold references, GC won't collect. Logical memory leaks happen all the time in Java.
  • finalize() is deprecated in Java 9 and removed later. Don't mention it as a GC hook — interviewers know it's unreliable.

Competitive Edge

The "generational hypothesis" is the theoretical foundation of all modern GCs — objects either die very quickly (most) or live very long (few). By segregating young and old objects and collecting them independently, GC avoids scanning the entire heap every time. This is why minor GC is 100x faster than full GC.

5
Difference between == and equals()
Easy ~2 min
== checks if two references point to the exact same object in memory. equals() checks if two objects are logically equal (same content) — and you can override it.

Think of == like asking "are these two people the same physical person?" — comparing ID cards (memory addresses). equals() is asking "do these two people have the same name?" — comparing content.

For primitives (int, char, boolean), == compares values directly (no objects involved). For objects, == compares references — same object? For Strings, == is almost always wrong because two different String objects with the same content are NOT the same reference.

The String Pool is the classic gotcha. String literals (not new String()) are interned — stored in the pool. Two literals with same content share the same reference, so == accidentally works. But new String("hello") == new String("hello") is false — two objects on heap.

Java
// == vs equals() — the classic interview demo
String a = "hello";           // goes to String Pool
String b = "hello";           // same pool reference
String c = new String("hello"); // new heap object

System.out.println(a == b);         // true  (same pool ref)
System.out.println(a == c);         // false (different objects)
System.out.println(a.equals(c));   // true  (same content)

// For your own classes — override equals() AND hashCode()
class Person {
    String name;
    public boolean equals(Object o) {
        if(!(o instanceof Person)) return false;
        return this.name.equals(((Person) o).name);
    }
}
Key Points
  • == on objects: reference equality (same memory address)
  • equals() on objects: logical equality (defined by the class) — overridable
  • String Pool: literals are interned, new String() bypasses the pool
  • Always override hashCode() when you override equals() — contract: equal objects must have equal hash codes
  • Integer cache: Java caches Integer values -128 to 127. Integer.valueOf(100) == Integer.valueOf(100) is true. Above 127? false.

Gotchas

  • The Integer cache trap: Integer a = 127; Integer b = 127; a == b → true. Integer a = 128; Integer b = 128; a == b → false. Interviewers love this.
  • If you override equals() but not hashCode(), your objects will break in HashMaps and HashSets — equal objects may end up in different buckets.

Competitive Edge

The equals-hashCode contract is the most important Java contract. If a.equals(b) is true, then a.hashCode() == b.hashCode() MUST be true. The reverse doesn't need to hold (hash collision is okay). Violating this silently breaks HashMap, HashSet, and any hash-based collection.

6
HashMap vs ConcurrentHashMap
Hard ~5 min
HashMap is fast but not thread-safe — concurrent access corrupts it. ConcurrentHashMap is thread-safe with segment-level (Java 7) or node-level (Java 8+) locking for high concurrency.

HashMap uses an array of buckets. Each bucket is a linked list (or tree for 8+ entries). put(key, value): compute hash → find bucket → add node. get(key): compute hash → find bucket → traverse list. All fast — O(1) average.

But two threads calling put() simultaneously can corrupt the structure. In Java 7, resizing could cause infinite loops in multi-threaded use (now fixed but still unsafe). Never use HashMap in multi-threaded code without external synchronization.

Hashtable was the old solution — synchronized every method. Safe, but terrible performance — one lock for everything, one thread at a time. Essentially serialized access.

ConcurrentHashMap (Java 7): splits the map into 16 segments, each with its own lock. 16 threads can write simultaneously (to different segments). Much better than Hashtable.

ConcurrentHashMap (Java 8+): Even better — no segments anymore. Uses CAS (Compare-And-Swap) operations and synchronized only on individual bucket nodes. Lock granularity is now per-bucket. In practice, reads are lock-free (volatile reads). Writes lock only the specific bucket being modified.

Feature HashMap Hashtable ConcurrentHashMap
Thread-safe? ❌ No ✅ Yes (method-level) ✅ Yes (fine-grained)
Null keys/values 1 null key, null values ❌ No nulls ❌ No nulls
Locking None Entire map Per bucket (Java 8+)
Performance Fastest Slowest High-concurrency best
Iteration Fail-fast (ConcurrentModificationException) Enumerator Weakly consistent (no exception)
Key Points
  • HashMap: O(1) average get/put, O(n) worst case. Java 8+ uses TreeNode (red-black tree) when bucket size > 8, making worst case O(log n)
  • HashMap load factor default 0.75 — resizes when 75% full (doubles capacity)
  • ConcurrentHashMap allows concurrent reads without locking (reads are weakly consistent)
  • ConcurrentHashMap's iterator is weakly consistent — doesn't throw ConcurrentModificationException, but may or may not reflect concurrent modifications
  • Use Collections.synchronizedMap(HashMap) for a quick thread-safe wrapper — but it's coarse-grained like Hashtable
Common Follow-ups
Why is HashMap fail-fast and ConcurrentHashMap fail-safe?
HashMap's iterator tracks a modCount. If the map changes during iteration, counts don't match → ConcurrentModificationException. ConcurrentHashMap's iterator snapshots the state or works on weakly consistent view — never throws, but may miss new entries.
What changed between Java 7 and Java 8 in HashMap internals?
Java 8 introduced treeification — buckets with 8+ nodes convert from linked list to red-black tree, reducing worst-case from O(n) to O(log n). Also improves hash computation to better distribute keys.

Gotchas

  • HashMap allows ONE null key and multiple null values. ConcurrentHashMap allows NEITHER — calling put(null, ...) throws NullPointerException. Hashtable also rejects nulls.
  • ConcurrentHashMap's size() is only approximate in Java 8 — it sums segment counts which may not be perfectly synchronized. Use mappingCount() for large maps.

Competitive Edge

In Java 8, ConcurrentHashMap added computeIfAbsent(), merge(), and forEach() — atomic compound operations. Instead of check-then-put (race condition), use map.computeIfAbsent(key, k -> new ArrayList()).add(value) — atomically. This is the correct way to implement concurrent multi-maps.

7
Process vs Thread in Java
Medium ~3 min
In Java, a Process is a running JVM instance with its own heap. Threads live inside the JVM, share heap memory, and are created via Thread class or Runnable/Callable interface.

When you run a Java program, you start a JVM process. That process has one thread at the start — the main thread. From there you spawn more threads using new Thread(runnable).start() or ExecutorService.

Java threads are kernel-level threads (1:1 with OS threads, via JNI) since the early days. This means true parallelism on multi-core CPUs. Java 19+ introduced Virtual Threads (Project Loom) — lightweight threads managed by the JVM, not the OS. You can have millions of virtual threads (vs thousands of OS threads). Virtual threads are the answer to "how do you handle 1 million concurrent connections in Java?"

Two ways to create threads: extend Thread class (bad — you waste your one inheritance slot), implement Runnable (better), or implement Callable (returns a value + can throw checked exceptions). Always prefer ExecutorService over raw threads in production.

Java
// 3 ways to create threads — know all three

// 1. Extend Thread (avoid — wastes inheritance)
class MyThread extends Thread {
    public void run() { System.out.println("Thread"); }
}

// 2. Implement Runnable (preferred for simple tasks)
Runnable r = () -> System.out.println("Runnable");
new Thread(r).start();

// 3. Callable — returns value, can throw checked exception
Callable<Integer> c = () -> 42;
ExecutorService ex = Executors.newFixedThreadPool(4);
Future<Integer> f = ex.submit(c);
System.out.println(f.get()); // blocks until done

// Java 19+: Virtual Threads — lightweight, millions possible
Thread.ofVirtual().start(() -> System.out.println("Virtual!"));
Key Points
  • Thread states: NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED
  • start() creates a new thread. run() just executes on current thread — common beginner mistake.
  • ExecutorService (thread pool) is the production way — avoids thread creation overhead
  • Daemon threads: background threads that JVM kills when all non-daemon threads finish (e.g., GC thread)
  • Virtual threads (Java 21 LTS): platform threads are expensive OS threads. Virtual threads are JVM-managed, cheap. Ideal for I/O-bound workloads.

Gotchas

  • Calling run() directly instead of start() — this runs the code on the current thread, no new thread created. Very common mistake.
  • Thread.sleep() doesn't release any locks it holds. Object.wait() DOES release the lock. Crucial difference for synchronization.
8
Synchronization and Deadlock in Java
Hard ~4 min
Synchronization prevents race conditions by ensuring only one thread accesses a critical section at a time. Deadlock is when two threads each hold a lock the other needs — eternal standoff.

Every Java object has an intrinsic lock (monitor). The synchronized keyword grabs that lock. Only one thread can hold a given object's lock at a time — others block.

You can synchronize a method (lock = the object) or a block (lock = any specified object). Synchronized methods are simpler; synchronized blocks are more flexible and allow finer granularity (lock only the specific object you're protecting, not the whole this).

Deadlock example in Java: Thread 1 locks Object A then tries to lock Object B. Thread 2 locks Object B then tries to lock Object A. Both block forever. Standard fix: always acquire locks in the same fixed order.

Beyond synchronized, Java's java.util.concurrent.locks package offers ReentrantLock — same mutual exclusion but with tryLock() (avoid deadlock by not waiting forever), lockInterruptibly() (respond to interrupts), and separate read/write locks (ReentrantReadWriteLock).

Java
// synchronized method — lock is 'this'
class Counter {
    private int count = 0;
    public synchronized void increment() { count++; }
    public synchronized int getCount() { return count; }
}

// ReentrantLock — more control, avoid deadlock with tryLock
ReentrantLock lock = new ReentrantLock();
try {
    if (lock.tryLock(1, TimeUnit.SECONDS)) {
        // got the lock, do critical work
        lock.unlock();
    }
} catch (InterruptedException e) { /* handle */ }

// volatile — visibility guarantee, NOT atomicity
volatile boolean running = true; // other threads see this change immediately
Key Points
  • synchronized guarantees mutual exclusion + memory visibility (happens-before relationship)
  • volatile: ensures all threads see the latest value of a variable — no caching in CPU registers. Does NOT make compound ops atomic.
  • atomic package: AtomicInteger, AtomicLong — lock-free thread-safe operations using CAS
  • ReentrantLock: reentrant (same thread can lock multiple times), interruptible, timed, with fairness option
  • Deadlock prevention in Java: lock ordering, tryLock() with timeout, avoid nested locks
  • wait()/notify()/notifyAll() must be called inside synchronized block — on the same lock object

Gotchas

  • volatile doesn't make count++ atomic. It's 3 ops: read, increment, write. Use AtomicInteger instead.
  • synchronized on a static method uses the Class object as lock. On an instance method, it's the instance. Different locks — they don't block each other.
  • Calling wait() without checking condition in a loop leads to spurious wakeup bugs. Always: while(!condition) wait();

Competitive Edge

Java's synchronized uses biased locking, thin locks, and inflated monitors internally — it starts cheap and escalates. In Java 15+, biased locking was disabled as it slows modern workloads with high contention. This is why moving to ReentrantLock or concurrent collections is generally better for high-throughput systems.

9
What Happens Internally When a Java Program Runs
Medium ~4 min
Source → javac compiles to bytecode → JVM loads classes → bytecode verifier checks → interpreter/JIT executes → GC manages memory — all in a pipeline you should be able to describe step by step.

This is the question that separates people who just use Java from people who understand Java. Let's walk through it precisely.

Step 1 — Compilation: javac HelloWorld.java → produces HelloWorld.class with bytecode. Bytecode is not native code. It's compact, platform-neutral instructions for the JVM.

Step 2 — Class Loading: When JVM starts, the Class Loader subsystem kicks in. Three built-in loaders: Bootstrap ClassLoader (loads core Java classes from rt.jar — java.lang, java.util), Extension ClassLoader (loads from ext directories), Application ClassLoader (loads your .class files and classpath). Delegation model: child always asks parent first before loading itself.

Step 3 — Bytecode Verification: The Bytecode Verifier checks that the .class file is valid, hasn't been tampered with, and doesn't violate access rules. This is Java's security gate — ensures bytecode won't crash the JVM.

Step 4 — Execution: The Execution Engine runs the bytecode. Initially via Interpreter — reads and executes one bytecode instruction at a time (slow). The JVM tracks which methods are called frequently (hot methods). Those get compiled by the JIT (Just-In-Time) Compiler to native machine code — stored in code cache — and subsequent calls run at native speed. This is why Java warms up over time and gets faster.

Step 5 — Memory Management: GC runs in the background, cleaning the heap. Main thread starts with main(). Program exits when all non-daemon threads finish.

.java Source javac .class Bytecode load ClassLoader + Verifier exec Interpreter + JIT Compiler native Native Machine Code GC runs alongside execution, managing heap memory continuously
Java's complete execution pipeline — from your .java file to native machine code
Key Points
  • ClassLoader delegation: Application → Extension → Bootstrap (parent-first)
  • Bytecode Verifier: security check before execution — ensures type safety, no illegal stack ops
  • Interpreter runs code immediately (no warm-up). JIT kicks in for hot methods after profiling.
  • JIT compiles to native code, stored in Code Cache — default 240MB, tunable with -XX:ReservedCodeCacheSize
  • JVM startup has 3 phases: class loading, JIT compilation, steady-state execution

Gotchas

  • JIT doesn't compile everything — only hot spots (called often). Cold code runs interpreted forever.
  • Class loading is lazy by default — a class is loaded only when first referenced, not at JVM startup.

Competitive Edge

HotSpot JIT has two compilers: C1 (fast compilation, basic optimization — for methods called thousands of times) and C2 (slow compilation, aggressive optimization — for methods called millions of times, the "hot" hot methods). Java 10+ uses Graal as an optional JIT (replacing C2) — written in Java itself, proving that Java can compile Java.

10
String vs StringBuilder vs StringBuffer
Medium ~3 min
String is immutable — every modification creates a new object. StringBuilder is mutable and fast (not thread-safe). StringBuffer is mutable and thread-safe (but slower due to synchronization).

String in Java is immutable. When you do str = str + "world", Java doesn't modify the existing String. It creates a NEW String object and points str to it. The old object becomes garbage. This is why concatenating Strings in a loop with + is O(n²) — you're creating n objects and copying more each time.

Immutability makes Strings safe to share across threads (no sync needed), safe to use as HashMap keys (hash never changes), and allows String pooling (interning). These are features, not accidents.

StringBuilder is a mutable buffer. append() adds to the internal char array without creating new objects. Use it for building strings in loops or when concatenating many pieces. Not thread-safe.

StringBuffer is exactly like StringBuilder but every method is synchronized. Thread-safe, but the synchronization overhead makes it slower. In the real world, you rarely need StringBuffer — if you're building a string in a multi-threaded context, you usually build it locally (StringBuilder) and then share the immutable String result.

Feature String StringBuilder StringBuffer
Mutable? ❌ Immutable ✅ Mutable ✅ Mutable
Thread-safe? ✅ Yes (immutable) ❌ No ✅ Yes (synchronized)
Performance Slow for concat loops ⚡ Fastest Slower (sync overhead)
String Pool ✅ Literals interned N/A N/A
When to use Fixed strings, keys Build strings in loops Multi-threaded concat
Java
// BAD — O(n²), creates n new String objects in loop
String result = "";
for(int i = 0; i < 1000; i++) result += i; // terrible!

// GOOD — O(n), single object, internal array grows as needed
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 1000; i++) sb.append(i);
String result = sb.toString(); // convert once at the end

// Fun fact: javac optimizes simple + concatenations to StringBuilder
// BUT not in loops — hence you still need to do it manually
String s = "Hello" + " World"; // javac makes this StringBuilder internally
Key Points
  • String immutability enables: thread safety, String pooling, safe HashMap keys, caching hashCode
  • String stores char[] internally (Java 8) or byte[] with encoding flag (Java 9+ — Compact Strings)
  • StringBuilder initial capacity: 16 chars. Doubles when full. Amortized O(1) append.
  • Java compiler optimizes "a" + "b" + "c" to use StringBuilder. But NOT in loops.
  • Java 11+: String.repeat(), isBlank(), strip() added. Java 15+: Text Blocks """..."""

Gotchas

  • String comparison with == in switch statements is fine in Java 7+ (compiler uses hashCode + equals internally). But in your own code, always use equals() for String comparison.
  • String.intern() forces a string into the pool — useful for memory optimization when you have millions of repeated strings, but be careful — the pool lives in heap (Java 7+) and is GC'd, but intern() abuse can slow things down.

Competitive Edge

Java 9 introduced Compact Strings — if a String contains only Latin-1 characters (most strings do), it's stored as byte[] instead of char[]. This halves memory for typical strings. Java 11 added String.repeat() and isBlank(). Java 13+ added Text Blocks for multiline strings. Knowing these recent additions signals you keep up with the language evolution.

☕ You now know Java Core better than 90% of candidates. Go get it.

Ready to test Java Core?

Review JVM, OOP, collections, and concurrency through targeted questions.

Start practice
Home 01. DBMS 02. OOPs 03. Computer Networks 04. Operating System 05. C++ Core 05. Java Core 05. Python Core 06. Puzzles 07. Linux 08. Git 09. SQL 10. Projects 11. System Design 12. DSA Concepts