Java Basic Interview Questions-Reference Answers
Core Java Concepts
What’s the difference between ==
and .equals()
in Java?
Reference Answer:
==
compares references (memory addresses) for objects and values for primitives.equals()
compares the actual content/state of objects- By default,
.equals()
uses==
(reference comparison) unless overridden - When overriding
.equals()
, you must also override.hashCode()
to maintain the contract: if two objects are equal according to.equals()
, they must have the same hash code - String pool example:
"hello" == "hello"
is true due to string interning, butnew String("hello") == new String("hello")
is false
1 | String s1 = "hello"; |
Explain the Java memory model and garbage collection.
Reference Answer:
Memory Areas:
- Heap: Object storage, divided into Young Generation (Eden, S0, S1) and Old Generation
- Stack: Method call frames, local variables, partial results
- Method Area/Metaspace: Class metadata, constant pool
- PC Register: Current executing instruction
- Native Method Stack: Native method calls
Garbage Collection Process:
- Objects created in Eden space
- When Eden fills, minor GC moves surviving objects to Survivor space
- After several GC cycles, long-lived objects promoted to Old Generation
- Major GC cleans Old Generation (more expensive)
Common GC Algorithms:
- Serial GC: Single-threaded, suitable for small applications
- Parallel GC: Multi-threaded, good for throughput
- G1GC: Low-latency, good for large heaps
- ZGC/Shenandoah: Ultra-low latency collectors
What are the differences between abstract classes and interfaces?
Reference Answer:
Aspect | Abstract Class | Interface |
---|---|---|
Inheritance | Single inheritance | Multiple inheritance |
Methods | Can have concrete methods | All methods abstract (before Java 8) |
Variables | Can have instance variables | Only public static final variables |
Constructor | Can have constructors | Cannot have constructors |
Access Modifiers | Any access modifier | Public by default |
Modern Java (8+) additions:
- Interfaces can have default and static methods
- Private methods in interfaces (Java 9+)
When to use:
- Abstract Class: When you have common code to share and “is-a” relationship
- Interface: When you want to define a contract and “can-do” relationship
Concurrency and Threading
How does the volatile
keyword work?
Reference Answer:
Purpose: Ensures visibility of variable changes across threads and prevents instruction reordering.
Memory Effects:
- Reads/writes to volatile variables are directly from/to main memory
- Creates a happens-before relationship
- Prevents compiler optimizations that cache variable values
When to use:
- Simple flags or state variables
- Single writer, multiple readers scenarios
- Not sufficient for compound operations (like increment)
1 | public class VolatileExample { |
Limitations: Doesn’t provide atomicity for compound operations. Use AtomicBoolean
, AtomicInteger
, etc., for atomic operations.
Explain different ways to create threads and their trade-offs.
Reference Answer:
1. Extending Thread class:
1 | class MyThread extends Thread { |
- Pros: Simple, direct control
- Cons: Single inheritance limitation, tight coupling
2. Implementing Runnable:
1 | class MyTask implements Runnable { |
- Pros: Better design, can extend other classes
- Cons: Still creates OS threads
3. ExecutorService:
1 | ExecutorService executor = Executors.newFixedThreadPool(10); |
- Pros: Thread pooling, resource management
- Cons: More complex, need proper shutdown
4. CompletableFuture:
1 | CompletableFuture.supplyAsync(() -> { /* computation */ }) |
- Pros: Asynchronous composition, functional style
- Cons: Learning curve, can be overkill for simple tasks
5. Virtual Threads (Java 19+):
1 | Thread.startVirtualThread(() -> { /* task */ }); |
- Pros: Lightweight, millions of threads possible
- Cons: New feature, limited adoption
What’s the difference between synchronized
and ReentrantLock
?
Reference Answer:
Feature | synchronized | ReentrantLock |
---|---|---|
Type | Intrinsic/implicit lock | Explicit lock |
Acquisition | Automatic | Manual (lock/unlock) |
Fairness | No fairness guarantee | Optional fairness |
Interruptibility | Not interruptible | Interruptible |
Try Lock | Not available | Available |
Condition Variables | wait/notify | Multiple Condition objects |
Performance | JVM optimized | Slightly more overhead |
ReentrantLock Example:
1 | private final ReentrantLock lock = new ReentrantLock(true); // fair lock |
Collections and Data Structures
How does HashMap work internally?
Reference Answer:
Internal Structure:
- Array of buckets (Node<K,V>[] table)
- Each bucket can contain a linked list or red-black tree
- Default initial capacity: 16, load factor: 0.75
Hash Process:
- Calculate hash code of key using
hashCode()
- Apply hash function:
hash(key) = key.hashCode() ^ (key.hashCode() >>> 16)
- Find bucket:
index = hash & (capacity - 1)
Collision Resolution:
- Chaining: Multiple entries in same bucket form linked list
- Treeification (Java 8+): When bucket size ≥ 8, convert to red-black tree
- Untreeification: When bucket size ≤ 6, convert back to linked list
Resizing:
- When size > capacity × load factor, capacity doubles
- All entries rehashed to new positions
- Expensive operation, can cause performance issues
Poor hashCode() Impact:
If hashCode()
always returns same value, all entries go to one bucket, degrading performance to O(n) for operations.
1 | // Simplified internal structure |
When would you use ConcurrentHashMap vs Collections.synchronizedMap()?
Reference Answer:
Collections.synchronizedMap():
- Wraps existing map with synchronized methods
- Synchronization: Entire map locked for each operation
- Performance: Poor in multi-threaded scenarios
- Iteration: Requires external synchronization
- Fail-fast: Iterators can throw ConcurrentModificationException
ConcurrentHashMap:
- Synchronization: Segment-based locking (Java 7) or CAS operations (Java 8+)
- Performance: Excellent concurrent read performance
- Iteration: Weakly consistent iterators, no external sync needed
- Fail-safe: Iterators reflect state at creation time
- Atomic operations: putIfAbsent(), replace(), computeIfAbsent()
1 | // ConcurrentHashMap example |
Use ConcurrentHashMap when:
- High concurrent access
- More reads than writes
- Need atomic operations
- Want better performance
Design Patterns and Architecture
Implement the Singleton pattern and discuss its problems.
Reference Answer:
1. Eager Initialization:
1 | public class EagerSingleton { |
- Pros: Thread-safe, simple
- Cons: Creates instance even if never used
2. Lazy Initialization (Thread-unsafe):
1 | public class LazySingleton { |
3. Thread-safe Lazy (Double-checked locking):
1 | public class ThreadSafeSingleton { |
4. Enum Singleton (Recommended):
1 | public enum EnumSingleton { |
Problems with Singleton:
- Testing: Difficult to mock, global state
- Coupling: Tight coupling throughout application
- Scalability: Global bottleneck
- Serialization: Need special handling
- Reflection: Can break private constructor
- Classloader: Multiple instances with different classloaders
Explain dependency injection and inversion of control.
Reference Answer:
Inversion of Control (IoC):
Principle where control of object creation and lifecycle is transferred from the application code to an external framework.
Dependency Injection (DI):
A technique to implement IoC where dependencies are provided to an object rather than the object creating them.
Types of DI:
1. Constructor Injection:
1 | public class UserService { |
2. Setter Injection:
1 | public class UserService { |
3. Field Injection:
1 | public class UserService { |
Benefits:
- Testability: Easy to inject mock dependencies
- Flexibility: Change implementations without code changes
- Decoupling: Reduces tight coupling between classes
- Configuration: Centralized dependency configuration
Without DI:
1 | public class UserService { |
With DI:
1 | public class UserService { |
Performance and Optimization
How would you identify and resolve a memory leak in a Java application?
Reference Answer:
Identification Tools:
- JVisualVM: Visual profiler, heap dumps
- JProfiler: Commercial profiler
- Eclipse MAT: Memory Analyzer Tool
- JConsole: Built-in monitoring
- Application metrics: OutOfMemoryError frequency
Detection Signs:
- Gradual memory increase over time
- OutOfMemoryError exceptions
- Increasing GC frequency/duration
- Application slowdown
Analysis Process:
1. Heap Dump Analysis:
1 | jcmd <pid> GC.run_finalization |
2. Common Leak Scenarios:
Static Collections:
1 | public class LeakyClass { |
Listener Registration:
1 | public class EventPublisher { |
ThreadLocal Variables:
1 | public class ThreadLocalLeak { |
Resolution Strategies:
- Use weak references where appropriate
- Implement proper cleanup in finally blocks
- Clear collections when no longer needed
- Remove listeners in lifecycle methods
- Use try-with-resources for automatic cleanup
- Monitor object creation patterns
What are some JVM tuning parameters you’ve used?
Reference Answer:
Heap Memory:
1 | -Xms2g # Initial heap size |
Garbage Collection:
1 | # G1GC (recommended for large heaps) |
GC Logging:
1 | -Xlog:gc*:gc.log:time,tags |
Performance Monitoring:
1 | -XX:+PrintGCDetails |
JIT Compilation:
1 | -XX:+TieredCompilation |
Common Tuning Scenarios:
- High throughput: Parallel GC, larger heap
- Low latency: G1GC or ZGC, smaller pause times
- Memory constrained: Smaller heap, compressed OOPs
- CPU intensive: More GC threads, tiered compilation
Modern Java Features
Explain streams and when you’d use them vs traditional loops.
Reference Answer:
Stream Characteristics:
- Functional: Declarative programming style
- Lazy: Operations executed only when terminal operation called
- Immutable: Original collection unchanged
- Chainable: Fluent API for operation composition
Stream Example:
1 | List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); |
When to use Streams:
- Data transformation pipelines
- Complex filtering/mapping operations
- Parallel processing (
.parallelStream()
) - Functional programming style preferred
- Readability over performance for complex operations
When to use Traditional Loops:
- Simple iterations without transformations
- Performance critical tight loops
- Early termination needed
- State modification during iteration
- Index-based operations
Performance Considerations:
1 | // Stream overhead for simple operations |
What are records in Java 14+ and when would you use them?
Reference Answer:
Records Definition:
Records are immutable data carriers that automatically generate boilerplate code.
Basic Record:
1 | public record Person(String name, int age, String email) {} |
Custom Methods:
1 | public record Point(double x, double y) { |
When to Use Records:
- Data Transfer Objects (DTOs)
- Configuration objects
- API response/request models
- Value objects in domain modeling
- Tuple-like data structures
- Database result mapping
Example Use Cases:
API Response:
1 | public record UserResponse(Long id, String username, String email, LocalDateTime createdAt) {} |
Configuration:
1 | public record DatabaseConfig(String url, String username, String password, |
Limitations:
- Cannot extend other classes (can implement interfaces)
- All fields are implicitly final
- Cannot declare instance fields beyond record components
- Less flexibility than regular classes
Records vs Classes:
- Use Records: Immutable data, minimal behavior
- Use Classes: Mutable state, complex behavior, inheritance needed
System Design Integration
How would you design a thread-safe cache with TTL (time-to-live)?
Reference Answer:
Design Requirements:
- Thread-safe concurrent access
- Automatic expiration based on TTL
- Efficient cleanup of expired entries
- Good performance for reads and writes
Implementation:
1 | public class TTLCache<K, V> { |
Usage Example:
1 | // Create cache with 5-minute default TTL, cleanup every minute |
Alternative Approaches:
- Caffeine Cache: Production-ready with advanced features
- Guava Cache: Google’s caching library
- Redis: External cache for distributed systems
- Chronicle Map: Off-heap storage for large datasets
Explain how you’d handle database connections in a high-traffic application.
Reference Answer:
Connection Pooling Strategy:
1. HikariCP Configuration (Recommended):
1 |
|
2. Connection Pool Sizing:
1 | connections = ((core_count * 2) + effective_spindle_count) |
3. Transaction Management:
1 |
|
4. Read/Write Splitting:
1 |
|
5. Monitoring and Health Checks:
1 |
|
6. Best Practices for High Traffic:
Connection Management:
- Always use connection pooling
- Set appropriate timeouts
- Monitor pool metrics
- Use read replicas for read-heavy workloads
Query Optimization:
- Use prepared statements
- Implement proper indexing
- Cache frequently accessed data
- Use batch operations for bulk updates
Resilience Patterns:
- Circuit breaker for database failures
- Retry logic with exponential backoff
- Graceful degradation when database unavailable
- Database failover strategies
Performance Monitoring:
1 |
|
This comprehensive approach ensures database connections are efficiently managed in high-traffic scenarios while maintaining performance and reliability.