Why Java Dominates Indian Tech Interviews
Java is the most widely used programming language in Indian software companies — from enterprise IT (TCS, Infosys, Wipro, Cognizant) to product companies (Flipkart, Paytm, PhonePe, Zepto, Amazon India). Nearly 70% of backend roles in India require Java knowledge.
This guide covers the 80 most-asked Java interview questions organized by topic and difficulty, with answers that are concise enough to say in an interview but deep enough to show you actually understand the concept.
Fresher (0–1 yr)
Core Java, OOP basics, String, basic Collections
Mid-Level (2–4 yrs)
Collections internals, Multithreading, JVM, Java 8
Senior (5+ yrs)
Concurrency patterns, JVM tuning, Spring Boot, design patterns
Core Java (Q1–Q20)
== checks reference equality — whether both variables point to the same object in memory..equals() checks logical/value equality — whether the objects have the same content.Key example:
new String("hello") == new String("hello") is false (two different objects), but new String("hello").equals("hello") is true. Always use .equals() for String comparisons. For primitives, only == applies (no objects involved).
1. Security: Strings are used for class names, file paths, network connections — mutable strings would be a security risk.
2. Thread safety: Immutable objects are inherently thread-safe (no synchronization needed).
3. String pool optimization: The JVM can intern strings (reuse the same object for the same literal) because the value can never change.
When you "modify" a String, a new String object is created. Use
StringBuilder for mutable operations.
StringBuilder: Mutable, not thread-safe. Fastest for string manipulation in single-threaded code. Use in loops or heavy string building.
StringBuffer: Mutable, thread-safe (synchronized methods). Slower than StringBuilder due to synchronization overhead. Use only in multi-threaded string manipulation (rare in practice).
Interface: No constructors, no instance variables (only constants). Methods are public abstract by default (Java 8+ allows
default and static methods). Multiple implementation allowed.When to use: Use abstract class to share code among closely related classes. Use interface to define a contract that unrelated classes can implement (e.g.,
Comparable, Runnable).
Compile-time (method overloading): Same method name, different parameters — resolved at compile time.
Runtime (method overriding): A subclass overrides a parent method — the JVM decides at runtime which implementation to call based on the actual object type.
class Animal { void sound() { System.out.println("..."); } } class Dog extends Animal { void sound() { System.out.println("Woof"); } } Animal a = new Dog(); // Dog object, Animal reference a.sound(); // prints "Woof" — runtime polymorphism
Overriding: Subclass provides a specific implementation for a method already defined in the parent. Same signature (name + parameters). Resolved at runtime (dynamic polymorphism). The
@Override annotation is best practice to catch typos.
Exception (but not RuntimeException). The compiler forces you to handle them with try-catch or declare them with throws. Example: IOException, SQLException.Unchecked exceptions: Subclasses of
RuntimeException. Not enforced by the compiler. Example: NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException.Error: Separate from both — represents JVM-level failures like
OutOfMemoryError. Should not be caught.
finally: A block in exception handling. Code inside
finally always executes (whether exception occurred or not) — used for cleanup like closing connections.finalize(): A method of
Object class called by the GC before an object is collected. Deprecated in Java 9+, unreliable — don't use it.
StackOverflowError.Heap: Where objects are allocated (
new keyword). Shared across threads. Managed by the Garbage Collector. Divided into Young Generation (Eden + Survivor) and Old Generation (Tenured). OutOfMemoryError if full.
(default/package-private): Accessible within the same package (no keyword needed).
protected: Accessible within the same package AND subclasses (even in different packages).
public: Accessible from anywhere.
Collections Framework (Q21–Q35)
HashMap uses an array of buckets (Node[] table). When you call put(key, value):1. Calls
key.hashCode(), applies a hash function → determines bucket index.2. If the bucket is empty, a new
Node is inserted.3. If bucket has nodes (collision), Java 8+ uses a linked list up to 8 nodes, then converts to a Red-Black Tree (O(log n) lookup vs O(n) for linked list).
Default capacity: 16. Load factor: 0.75. When size exceeds capacity × load factor, HashMap resizes (doubles) and rehashes all entries — expensive O(n) operation.
a.equals(b) is true, then a.hashCode() == b.hashCode() must also be true.If you override only
equals(), two "equal" objects may land in different buckets (different hash codes) — get() will return null even though the key exists.If you override only
hashCode(), two objects in the same bucket may not be recognized as equal — duplicates can be stored.Java IDEs (IntelliJ, Eclipse) can generate both at once — always use that.
LinkedHashMap: Maintains insertion order (doubly-linked list). O(1) operations. Use when you need predictable iteration order.
TreeMap: Sorted by natural key order (or custom Comparator). O(log n) operations (Red-Black Tree). Use when you need sorted key iteration (e.g., range queries).
LinkedList: Doubly-linked list. O(n) random access (must traverse). O(1) insert/delete at head or tail (given a node reference). Best when you frequently add/remove from both ends. Also implements
Deque.In practice,
ArrayList is faster for most operations due to CPU cache locality.
LinkedHashSet: Backed by LinkedHashMap. Maintains insertion order. O(1) operations.
TreeSet: Backed by TreeMap. Sorted order (natural or Comparator). O(log n) operations. Useful for
floor(), ceiling(), headSet() range queries.All three prevent duplicates — they call
equals() and hashCode() (or compareTo() for TreeSet) to detect duplicates.
remove() during iteration (avoids ConcurrentModificationException).ListIterator: Only for
List implementations. Can traverse forward and backward. Supports add(), set(), and remove() during iteration. Has nextIndex() and previousIndex().
Multithreading & Concurrency (Q36–Q50)
Thread and overrides run(). Problem: Java doesn't support multiple inheritance, so your class can't extend anything else.Implementing Runnable: Your class implements
Runnable and defines run(). You pass it to a Thread constructor. Better: separates the task from the thread mechanism, allows the class to extend another class, and works well with ExecutorService.Best practice: Use
Callable<V> (returns a value) or Runnable with ExecutorService instead of creating raw threads.
volatile: Only provides visibility — tells the JVM to always read/write the variable from main memory, not thread-local cache. Does NOT provide atomicity. A volatile
counter++ is still not thread-safe (it's read-modify-write: 3 operations). Use volatile for a single flag variable (e.g., volatile boolean running = true).
Thread.State):1. NEW: Thread created but not started yet.
2. RUNNABLE: Thread is running or ready to run (waiting for CPU).
3. BLOCKED: Thread is waiting to acquire a monitor lock (e.g., another thread holds it).
4. WAITING: Thread called
wait(), join(), or LockSupport.park() — indefinitely waiting.5. TIMED_WAITING: Like WAITING but with a timeout —
sleep(n), wait(n), join(n).6. TERMINATED: Thread has finished execution.
Classic example: Thread A holds lock1, waits for lock2. Thread B holds lock2, waits for lock1. Both wait forever.
Prevention strategies:
1. Lock ordering: Always acquire locks in the same order across all threads.
2. tryLock() with timeout: Use
ReentrantLock.tryLock(timeout) — give up if lock can't be acquired.3. Avoid nested locks: If possible, never hold one lock while trying to acquire another.
4. Use higher-level concurrency:
ConcurrentHashMap, CopyOnWriteArrayList, java.util.concurrent classes.
ExecutorService (in java.util.concurrent) manages a thread pool — reusing threads instead of creating a new one for every task, which is expensive.Benefits: Thread pooling (avoids overhead of thread creation/destruction), task queuing, lifecycle management,
Future/Callable support for return values, scheduled execution.ExecutorService pool = Executors.newFixedThreadPool(4); Future<Integer> result = pool.submit(() -> compute()); pool.shutdown(); // orderly shutdown
newFixedThreadPool(n), newCachedThreadPool(), newSingleThreadExecutor(), newScheduledThreadPool(n).
JVM Internals (Q51–Q60)
JVM components:
1. Class Loader: Loads .class files (Bootstrap → Extension → Application class loaders).
2. Runtime Data Areas: Method Area (class metadata), Heap (objects), Stack (method frames, per-thread), PC Register, Native Method Stack.
3. Execution Engine: Interpreter (slow, line-by-line) + JIT Compiler (compiles hot paths to native code — makes Java fast) + Garbage Collector.
Heap structure: Young Generation (Eden + 2 Survivor spaces) → Old Generation (Tenured).
Minor GC: Cleans Young Gen (fast, frequent). Objects surviving multiple GCs are promoted to Old Gen.
Major/Full GC: Cleans Old Gen (slow, causes pauses). Goal: minimize "stop-the-world" pauses.
Common GC algorithms:
• Serial GC: Single-threaded. Good for small apps.
• G1 GC (default Java 9+): Region-based, concurrent, predictable pause times. Good for large heaps.
• ZGC / Shenandoah: Ultra-low latency (<1ms pauses). For latency-sensitive microservices.
Java 8+ Features (Q61–Q70)
// Before Java 8 Runnable r = new Runnable() { public void run() { System.out.println("hi"); } }; // With lambda Runnable r = () -> System.out.println("hi");
@FunctionalInterface). Examples: Runnable, Callable, Comparator, Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>.
Intermediate operations (return Stream, lazy):
filter(), map(), flatMap(), sorted(), distinct(), limit(), peek().Terminal operations (trigger execution, return result):
collect(), forEach(), count(), findFirst(), reduce(), anyMatch(), toList().
List<String> result = list.stream() .filter(s -> s.startsWith("A")) // intermediate .map(String::toUpperCase) // intermediate .collect(Collectors.toList()); // terminal
Optional<T> is a container that may or may not hold a non-null value. It makes nullability explicit in the API, reducing NullPointerException risks.
Optional<String> opt = Optional.ofNullable(getName()); opt.ifPresent(name -> System.out.println(name)); String result = opt.orElse("Unknown"); String result2 = opt.orElseThrow(() -> new NotFoundException());
findById()). Don't use Optional as a method parameter, in collections, or in entity fields.
default keyword. Introduced to enable backward-compatible API evolution — adding new methods to existing interfaces without breaking all implementing classes.
interface Vehicle { default void start() { System.out.println("Starting..."); } }
Spring Boot (Q71–Q80)
Dependency Injection (DI): The mechanism Spring uses to provide dependencies to objects — via constructor, setter, or field injection. Constructor injection is preferred (promotes immutability and testability).
@Service public class OrderService { private final PaymentService paymentService; // Constructor injection (preferred) public OrderService(PaymentService ps) { this.paymentService = ps; } }
@Component — they all register a Spring bean. The difference is semantic and enables additional functionality:@Component: Generic Spring bean — use when no other specialization fits.
@Service: Business logic layer. No extra behavior, but communicates intent.
@Repository: Data access layer. Automatically wraps persistence exceptions into Spring's
DataAccessException hierarchy.@Controller: Web MVC controller. Handles HTTP requests. Use
@RestController (= @Controller + @ResponseBody) for REST APIs.
@Transactional wraps a method in a database transaction. Spring uses AOP to proxy the bean — on method call, a transaction is begun; on normal return, it commits; on RuntimeException, it rolls back.Propagation: What happens when a transactional method calls another transactional method. Common values:
REQUIRED (default — join existing or create new), REQUIRES_NEW (always new transaction), NESTED.Isolation: Controls what a transaction can see from concurrent transactions. Levels:
READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE. Higher isolation = fewer anomalies but lower concurrency.
@SpringBootApplication is a meta-annotation combining:1.
@Configuration — marks it as a config class2.
@EnableAutoConfiguration — tells Spring Boot to guess and configure beans based on classpath dependencies3.
@ComponentScan — scans the package and sub-packages for Spring componentsAuto-configuration reads
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Java 2.7+) or spring.factories — lists of config classes. Each config class uses @ConditionalOnClass, @ConditionalOnMissingBean etc. to decide if it should apply.
prototype: A new instance created every time the bean is requested.
request: New instance per HTTP request (web context only).
session: New instance per HTTP session (web context only).
application: One instance per ServletContext lifecycle.
Injecting a prototype-scoped bean into a singleton requires special handling (using
ApplicationContext.getBean() or @Lookup) — otherwise the prototype bean gets created once (when the singleton is created) and reused, defeating its purpose.