Java Core Interview Questions¶
Fundamentals: OOP concepts, memory management, concurrency basics, language features, and best practices.
1. Why is String immutable in Java?¶
View Answer
Direct Answer
String objects in Java are immutable (cannot be changed after creation). Once a String is created, any modification creates a new String object.
Why (Reasoning & Benefits)
- Thread Safety: Immutability eliminates synchronization needs. Multiple threads can safely share the same String.
- Cacheability: Strings can be pooled (String pool in heap). Same string literals reuse the same object.
- HashMap Keys: Strings are hashCode-safe. If mutable, the hash would change, breaking HashMap lookups.
- Security: Safe for sensitive data like file paths, database URLs, passwords. Cannot be altered mid-use.
- Performance: String interning optimizes memory usage.
Best Practice
Use StringBuilder (not String) for string mutations in loops to avoid creating intermediate objects and garbage.
2. What's the difference between == and .equals()?¶
View Answer
Quick Comparison
| Operator | Checks | Use Case |
|---|---|---|
| == | Identity (object reference) | Comparing null, singletons |
| .equals() | Logical equality (content) | Comparing values |
Example
String a = new String("hello");
String b = new String("hello");
String c = a;
a == b // false (different objects)
a.equals(b) // true (same content)
a == c // true (same reference)
Important: Override Both Together
If you override .equals(), always override hashCode() too. Objects equal by .equals() must have the same hashCode().
class Person {
String name;
int age;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) return false;
Person other = (Person) obj;
return this.name.equals(other.name) && this.age == other.age;
}
@Override
public int hashCode() {
return Objects.hash(name, age); // Same hash for equal objects
}
}
3. What's the difference between HashMap and ConcurrentHashMap?¶
View Answer
Comparison Table
| Feature | HashMap | ConcurrentHashMap |
|---|---|---|
| Thread-safe | No | Yes (bucket-level locking) |
| Null keys/values | Allowed (1 null key) | ❌ Not allowed |
| Performance | Faster (no locking) | Slower (with locking) |
| Use case | Single-threaded | Multi-threaded |
| Iterator | Fail-fast (throws exception) | Fail-safe (weakly consistent) |
When to Use
- HashMap: Single-threaded context or well-controlled synchronization
- ConcurrentHashMap: Multi-threaded environment with concurrent reads/writes
- Avoid: Synchronized collections like
Collections.synchronizedMap()(single lock, poor performance)
Key Insight: Bucket-Level Locking
ConcurrentHashMap locks individual buckets, allowing multiple threads to access different buckets simultaneously. HashMap locks the entire map.
4. What happens when you overflow Integer.MAX_VALUE?¶
View Answer
Answer
Integer overflow wraps silently to Integer.MIN_VALUE. No exception is thrown.
Integer.MAX_VALUE // 2147483647
Integer.MAX_VALUE + 1 // -2147483648 (wrapped to MIN_VALUE)
Integer.MAX_VALUE + 2 // -2147483647
Why This Happens
Java integers use two's complement representation. When the maximum positive value increments, it overflows to the minimum negative value.
Different Behavior by Type
| Type | MAX_VALUE + 1 | Behavior |
|---|---|---|
int |
Integer.MIN_VALUE | Silently wraps |
long |
Long.MIN_VALUE | Silently wraps |
double |
Infinity | Returns Infinity |
How to Prevent
- Use long: For large numbers
- Check bounds: Before arithmetic operations
- Use BigInteger: For arbitrary precision
5. What's the output of a++ + ++a for a=5?¶
View Answer
Answer
int a = 5;
int result = a++ + ++a;
System.out.println("result: " + result + ", a: " + a);
// Output: result: 12, a: 7
Step-by-Step Trace
- a++: Returns 5 (current value), then increments a to 6 (post-increment)
- ++a: Increments a to 7, then returns 7 (pre-increment)
- 5 + 7 = 12
- a becomes 7
Why This Is Confusing
The order of evaluation for a++ + ++a is undefined behavior in many languages. The outcome depends on compiler optimization and evaluation order, making it unreliable.
Best Practice
❌ Avoid mixing increment operators in the same expression.
6. What's the difference between 0.0/0.0 and 1/0 in Java?¶
View Answer
Answer
| Expression | Result | Type |
|---|---|---|
0.0 / 0.0 | NaN (Not a Number) | float/double |
1.0 / 0.0 | Infinity | float/double |
1 / 0 | ArithmeticException | int/long |
Key Difference: Floating-Point vs Integer
- Floating-point division by zero: Returns
InfinityorNaN(IEEE 754 standard) - Integer division by zero: Throws
ArithmeticException
Example
// Floating-point (no exception)
System.out.println(1.0 / 0.0); // Infinity
System.out.println(-1.0 / 0.0); // -Infinity
System.out.println(0.0 / 0.0); // NaN
// Integer (throws exception)
System.out.println(1 / 0); // ArithmeticException: / by zero
// Check for NaN
double result = 0.0 / 0.0;
if (Double.isNaN(result)) {
System.out.println("Result is NaN");
}
Handling NaN and Infinity
7. What's the difference between fail-fast and fail-safe iterators?¶
View Answer
Comparison Table
| Property | Fail-Fast | Fail-Safe |
|---|---|---|
| Behavior on modification | Throws ConcurrentModificationException | Continues with copy |
| Examples | ArrayList, HashMap, HashSet | CopyOnWriteArrayList, ConcurrentHashMap |
| Performance | Faster (no copying) | Slower (creates copy) |
| Concurrency | Not thread-safe for modifications | Thread-safe |
| Memory | O(1) | O(n) - full copy |
Fail-Fast Example
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
Iterator<Integer> it = list.iterator();
it.next(); // 1
list.add(4); // Modifying list during iteration
it.next(); // ❌ ConcurrentModificationException
Fail-Safe Example
List<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3));
Iterator<Integer> it = list.iterator();
it.next(); // 1
list.add(4); // Modifying list during iteration
it.next(); // ✅ Works fine (sees original copy)
When to Use
- Fail-Fast: Single-threaded, want early detection of concurrent modifications
- Fail-Safe: Multi-threaded environment, safe iteration with concurrent modifications
8. What's the difference between Comparable and Comparator?¶
View Answer
Quick Comparison
| Feature | Comparable | Comparator |
|---|---|---|
| Purpose | Natural ordering of class | Custom ordering logic |
| Implementation | Implement in class: class X implements Comparable<X> |
External class: class XComparator implements Comparator<X> |
| Method | compareTo(Object obj) |
compare(Object a, Object b) |
| Orderings per class | One natural order | Multiple custom orders |
| Use with | Arrays.sort(), Collections.sort() | Arrays.sort(array, comparator) |
Comparable Example
class Student implements Comparable<Student> {
String name;
int age;
@Override
public int compareTo(Student other) {
return this.age - other.age; // Natural: sort by age
}
}
Student[] students = { ... };
Arrays.sort(students); // Uses natural order (age)
Comparator Example
// Multiple sorting options
Comparator<Student> byName = (s1, s2) -> s1.name.compareTo(s2.name);
Comparator<Student> byAge = (s1, s2) -> s1.age - s2.age;
Comparator<Student> byNameThenAge = byName.thenComparing(byAge);
Student[] students = { ... };
Arrays.sort(students, byName); // Sort by name
Arrays.sort(students, byAge); // Sort by age
Arrays.sort(students, byNameThenAge); // Sort by name, then age
When to Use
- Comparable: When there's an obvious natural order (e.g., ID, age, timestamp)
- Comparator: When you need multiple orderings or the class doesn't implement Comparable
9. Which Collections data structure stores unique employee objects, and what requirements must they fulfill?¶
View Answer
Direct Answer
Use HashSet for most cases; TreeSet for sorted order.
HashSet vs TreeSet Comparison
| Feature | HashSet | TreeSet |
|---|---|---|
| Order | No guaranteed order | Sorted (natural or custom) |
| Performance | O(1) add/remove/lookup | O(log n) add/remove/lookup |
| Memory | Lower | Higher (maintains tree) |
| Null handling | Allows one null | ❌ No nulls allowed |
| Implementation | Hash table | Red-Black tree |
| Use Case | Fast uniqueness check | Ordered iteration needed |
Entity Requirements for Set Uniqueness
public class Employee {
private Long id;
private String name;
private String email;
// ✅ MUST override equals()
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Employee)) return false;
Employee other = (Employee) obj;
return this.id.equals(other.id); // or email if email is unique
}
// ✅ MUST override hashCode() in the same way
@Override
public int hashCode() {
return Objects.hash(id); // Same fields as equals()
}
// For TreeSet: implement Comparable or provide Comparator
@Override
public int compareTo(Employee other) {
return Long.compare(this.id, other.id);
}
}
// Usage
Set<Employee> employees = new HashSet<>();
employees.add(new Employee(1L, "John", "john@example.com"));
employees.add(new Employee(1L, "Jane", "jane@example.com"));
// Set size: 1 (duplicates removed by equals/hashCode)
// For sorted access
Set<Employee> sorted = new TreeSet<>(employees);
// Iterates in order by ID
Critical Rules
- ✅ Override equals() and hashCode() together — Violations break Set contracts
- ✅ hashCode() must return same value for equal objects — Required by Set contract
- ✅ For TreeSet: implement Comparable or provide Comparator — Needed for ordering
- ❌ Don't use mutable fields in equals()/hashCode() — Changing them breaks Set lookups
10. How do you efficiently sort and filter collections of objects using modern Java features?¶
View Answer
Modern Approach: Stream API (Java 8+)
Streams provide declarative, functional filtering and sorting.
List<Employee> employees = Arrays.asList(
new Employee(1L, "John", 50000),
new Employee(2L, "Jane", 60000),
new Employee(3L, "Bob", 45000)
);
// ✅ Filter: Salary > 50000
List<Employee> highEarners = employees.stream()
.filter(e -> e.getSalary() > 50000)
.collect(Collectors.toList());
// ✅ Sort: By salary descending
List<Employee> sorted = employees.stream()
.sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary()))
.collect(Collectors.toList());
// ✅ Chain: Filter + Sort + Limit
List<Employee> top2HighEarners = employees.stream()
.filter(e -> e.getSalary() > 45000)
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed())
.limit(2)
.collect(Collectors.toList());
Comparison: Stream vs Traditional Loop
| Traditional Loop | Stream API |
|---|---|
List |
List |
Common Stream Operations
// Filter by multiple conditions
employees.stream()
.filter(e -> e.getSalary() > 50000 && e.getName().startsWith("J"))
.collect(Collectors.toList());
// Sort by multiple fields
employees.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary)
.reversed()
.thenComparingLong(Employee::getId))
.collect(Collectors.toList());
// Transform (map)
List<String> names = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
// Distinct by field
employees.stream()
.distinct() // Uses equals/hashCode
.collect(Collectors.toList());
// Group by
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
// Reduce to single value
double totalSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
Performance Considerations
- Streams are lazy: Operations don't execute until a terminal operation (collect, forEach, count)
- Single pass: Streams consume data once; can't reuse
- Parallel streams: Use
.parallelStream()for large datasets on multi-core systems - Memory: Streams can be memory-efficient with intermediate operations
Additional Topics¶
See Sample Topics for more areas to explore: - Explain the difference between class and object - What are the 4 pillars of OOP? - What's the difference between ArrayList and LinkedList? - Explain interfaces vs abstract classes - How does garbage collection work? - What causes a memory leak in Java? - Explain method overloading vs overriding - When should you use enum?
See also: Java Core Basics, OOP, Interview Questions Overview