Basic Questions
22 questions
1. What is the Java Collections Framework? What are its main benefits?
The Java Collections Framework is a set of interfaces, implementations, and utility classes designed to store, organize, and process groups of objects. Instead of building custom data structures for every problem, developers use standard collection types such as lists, sets, queues, and maps with well-defined behavior and performance characteristics.
Its main benefits are reusability, consistency, and abstraction. It gives developers proven implementations like ArrayList, HashSet, and HashMap, plus common algorithms for sorting, searching, and synchronization. It also helps teams write cleaner code because behavior is expressed through interfaces first and concrete implementations can be swapped based on performance or ordering needs.
2. What is the difference between Collection and Collections?
Collection is a root interface in the Java Collections Framework that represents a group of elements. Classes such as ArrayList, HashSet, and LinkedList implement this interface either directly or indirectly. It defines common behaviors like adding, removing, iterating, and checking size.Collections, on the other hand, is a utility class. It contains static helper methods such as sort(), reverse(), shuffle(), binarySearch(), and synchronized or unmodifiable wrappers. So one is part of the type hierarchy, while the other is a helper toolkit for working with those types.
| Aspect | Collection | Collections |
|---|---|---|
| Type | Root interface | Utility class |
| Purpose | Represents a group of elements | Provides helper methods for collections |
| Examples | List, Set, Queue implementations | sort(), reverse(), synchronizedList() |
| Instantiation | Implemented by concrete classes | Not instantiated, used statically |
3. Explain the hierarchy of the Java Collections Framework. Name the root interfaces.
The hierarchy begins with the Iterable interface, which supports enhanced for loops. Under that, the main root interface for collections is Collection. List, Set, and Queue extend Collection. The Deque interface extends Queue for double-ended operations.Map is also a major root abstraction in the framework, but it does not extend Collection because it stores key-value pairs instead of single elements. So the two main roots developers work with are Collection and Map, while List, Set, Queue, and Deque sit under the Collection side of the hierarchy.
4. What is the difference between List, Set, and Map?
List stores elements in an ordered sequence and allows duplicates. It is the right choice when position matters or when the same value may appear multiple times. Set stores unique elements only, so it is used when duplicates are not allowed and membership checks matter more than position.Map is different because it stores key-value pairs instead of standalone elements. Keys are unique, and each key maps to one value. The practical difference is why each abstraction exists: List for order, Set for uniqueness, and Map for lookup by key.
| Aspect | List | Set | Map |
|---|---|---|---|
| Stores | Elements | Unique elements | Key-value pairs |
| Duplicates | Allowed | Not allowed | Keys unique, values may repeat |
| Ordering | Usually preserves sequence | Depends on implementation | Depends on implementation |
| Common Use | Indexed access and ordered data | Uniqueness and membership | Fast lookup by key |
5. What are the main implementations of List, Set, and Map?
For List, the most common implementations are ArrayList, LinkedList, and sometimes Vector. For Set, common implementations are HashSet, LinkedHashSet, and TreeSet. For Map, the most frequently used ones are HashMap, LinkedHashMap, TreeMap, Hashtable, and ConcurrentHashMap.
The reason behind these choices matters more than the names alone. ArrayList is usually preferred for general list usage, HashSet and HashMap are common when fast average-case lookup matters, LinkedHashMap or LinkedHashSet are useful when order should be preserved, and tree-based implementations are chosen when sorted ordering is required.
6. What is the difference between ArrayList and LinkedList?
ArrayList is backed by a dynamic array, so it provides fast random access through indexes and performs well when reads are common and inserts mostly happen at the end. LinkedList is backed by nodes connected through references, so indexed access is slower, but insertions or deletions in the middle can be done without shifting the remaining elements.
In practice, ArrayList is the default choice for most applications because CPU cache locality and simple memory layout usually outweigh the theoretical insertion benefits of LinkedList. LinkedList only becomes attractive when the access pattern truly matches frequent structural changes at known positions.
| Aspect | ArrayList | LinkedList |
|---|---|---|
| Internal Structure | Dynamic array | Doubly linked list |
| Random Access | Fast, O(1) | Slow, O(n) |
| Insert or Delete in Middle | Expensive due to shifting | Cheaper after locating node |
| Memory Overhead | Lower | Higher due to node references |
| Typical Default Choice | Yes | Only for specific patterns |
7. What is the difference between HashSet, LinkedHashSet, and TreeSet?
HashSet stores unique elements with no guaranteed iteration order and gives fast average-case insert and lookup. LinkedHashSet is similar but also preserves insertion order by maintaining a linked structure internally. TreeSet stores unique elements in sorted order, usually using natural ordering or a supplied comparator.
The trade-off is between speed, predictable order, and sorted order. If order does not matter, HashSet is usually enough. If stable iteration order matters, LinkedHashSet is better. If sorted output matters, TreeSet is the correct choice even though it is typically slower than hash-based sets for basic operations.
| Aspect | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| Ordering | No guaranteed order | Insertion order | Sorted order |
| Internal Basis | Hash table | Hash table plus linked list | Red-Black tree |
| Duplicates | Not allowed | Not allowed | Not allowed |
| Typical Performance | Fast average-case lookup | Slightly more overhead than HashSet | O(log n) operations |
8. What is the difference between HashMap, LinkedHashMap, and TreeMap?
HashMap stores key-value pairs with no guaranteed iteration order and is optimized for fast average-case lookup. LinkedHashMap extends that model by preserving insertion order, and it can also be configured to maintain access order, which is useful for cache-like behavior. TreeMap stores entries sorted by key using natural ordering or a comparator.
The real decision is about lookup speed versus ordering behavior. HashMap is the usual default, LinkedHashMap is useful when stable iteration or LRU-like behavior is needed, and TreeMap is the right fit when range queries or sorted keys are part of the requirement.
| Aspect | HashMap | LinkedHashMap | TreeMap |
|---|---|---|---|
| Ordering | No guaranteed order | Insertion or access order | Sorted by key |
| Internal Basis | Hash table | Hash table plus linked list | Red-Black tree |
| Null Keys | One allowed | One allowed | Not allowed with natural ordering |
| Typical Performance | Fast average-case lookup | Slight overhead for order tracking | O(log n) operations |
9. Can ArrayList or HashMap store null values? Explain for each.
ArrayList can store null as an element, and it can contain multiple null values because lists allow duplicates. HashMap can also store null, but the rules are different: it allows one null key and multiple null values.
This matters because collection behavior around null is implementation-specific rather than universal. For example, TreeMap with natural ordering does not allow a null key because it must compare keys, while HashMap can handle one because hashing logic treats it specially. The important part is not only knowing the rule, but also knowing why the rule exists.
10. What is an Iterator? How does it differ from Enumeration?
An Iterator is an interface used to traverse a collection one element at a time. It supports methods such as hasNext(), next(), and remove(). It is the standard traversal mechanism for most modern collection implementations.Enumeration is an older traversal interface used mainly with legacy classes such as Vector and Hashtable. It supports hasMoreElements() and nextElement(), but it does not support element removal. The most important distinction is that Iterator is part of the modern collections design and offers better control over traversal and modification.
| Aspect | Iterator | Enumeration |
|---|---|---|
| Era | Modern collections API | Legacy API |
| Methods | hasNext(), next(), remove() | hasMoreElements(), nextElement() |
| Removal Support | Yes | No |
| Typical Usage | Most collection implementations | Vector, Hashtable |
11. What is the difference between Iterator and ListIterator?
Iterator can move only in the forward direction through a collection and supports optional removal of the current element. ListIterator is more powerful, but it works only with List implementations. It can move forward and backward, access previous and next indexes, update elements with set(), add elements during iteration, and remove elements safely.
So the difference is not just more methods. ListIterator exists because lists are positional structures, and bidirectional traversal plus in-place updates are useful there. If an interview question asks when to use it, the right answer is when you need list-specific navigation or controlled modification during iteration.
| Aspect | Iterator | ListIterator |
|---|---|---|
| Supported Collections | Most collections | Lists only |
| Direction | Forward only | Forward and backward |
| Modification Methods | remove() | remove(), set(), add() |
| Index Awareness | No | Yes |
12. What is fail-fast behavior in iterators? Give an example.
Fail-fast behavior means an iterator detects structural modification of the collection after the iterator has been created and throws a ConcurrentModificationException. This usually happens when one part of the code changes the collection directly while another part is still iterating over it.
For example, if you loop over an ArrayList with an iterator and then call list.add() inside the loop instead of using the iterator’s own remove() method, the iterator will usually fail fast. The main goal is not thread safety, but early detection of unsafe modification patterns that could otherwise produce unpredictable results.
13. What is the default initial capacity of ArrayList and HashMap?
For ArrayList, the default constructor creates an empty list, and the backing array is allocated lazily when the first element is added. Once allocation happens, the default capacity becomes 10. For HashMap, the default initial capacity is 16 and the default load factor is 0.75.
This matters because collections are not just abstract containers. Capacity and resizing behavior affect memory usage and performance. If you know the expected size in advance, setting capacity explicitly can reduce resizing overhead in performance-sensitive paths.
14. Explain the equals() and hashCode() contract. Why is it important for collections?
The contract says that if two objects are considered equal by equals(), they must return the same value from hashCode(). The reverse is not required: two different objects can still produce the same hash code. This contract is critical for hash-based collections such as HashMap and HashSet because hashing is used to decide where elements or keys belong.
If the contract is broken, collections can behave incorrectly. For example, a logically equal key may not be found in a HashMap, or duplicates may appear in a HashSet unexpectedly. Consistent implementations are especially important for domain objects used as keys.
15. What is the difference between Comparable and Comparator?
Comparable defines a natural ordering inside the class itself through the compareTo() method. It is used when objects of that type have one standard default order. Comparator is a separate object that defines ordering externally, which makes it useful when multiple sorting strategies are needed.
The practical difference is flexibility. Comparable is good when the class should always have a default order, such as sorting employees by ID. Comparator is better when the same class may need different orderings, such as by name, salary, or joining date depending on the use case.
| Aspect | Comparable | Comparator |
|---|---|---|
| Where Defined | Inside the class | External class or lambda |
| Main Method | compareTo() | compare() |
| Number of Orderings | One natural ordering | Multiple custom orderings |
| Typical Use | Default sort behavior | Alternative sort strategies |
16. How does Collections.sort() work? What if you want custom sorting?
Collections.sort() sorts a List in place. If the elements implement Comparable, it uses their natural ordering. For object lists, Java uses a stable sorting algorithm called TimSort in modern versions, which performs well on partially ordered real-world data and preserves the relative order of equal elements.
If you want custom sorting, you pass a Comparator to Collections.sort(list, comparator) or use list.sort(comparator). That is the standard way to express alternative business rules such as sorting names alphabetically, tasks by priority, or employees by descending salary.
It is also worth remembering that sorting works on lists, not on arbitrary collections, and that custom comparators are usually preferred over changing the domain class whenever the ordering rule is scenario-specific rather than universal.
List<String> names = new ArrayList<>(Arrays.asList("Mia", "Alexander", "Bo"));
Collections.sort(names); // natural ordering
List<String> byLength = new ArrayList<>(names);
byLength.sort((a, b) -> Integer.compare(a.length(), b.length()));17. What is the difference between Vector and ArrayList? Why is Vector rarely used now?
Vector and ArrayList are both dynamic-array-based lists, but Vector is synchronized by default, while ArrayList is not. Because every method call on Vector carries synchronization overhead, it is usually slower for single-threaded code and often slower than more modern concurrency choices in multi-threaded code as well.Vector is rarely used now because modern Java offers better options depending on the access pattern. If you just need a normal resizable list, ArrayList is the standard choice. If you need thread safety, developers usually prefer Collections.synchronizedList(), CopyOnWriteArrayList, or another concurrent structure that matches the workload more precisely.
A strong interview answer should not treat Vector as wrong or deprecated by definition. It is still valid Java, but it is considered a legacy design because its built-in synchronization is too coarse for many modern applications.
| Aspect | Vector | ArrayList |
|---|---|---|
| Synchronization | Synchronized by default | Not synchronized |
| Performance | More overhead | Faster in most cases |
| Legacy Status | Older legacy class | Modern default list choice |
| Common Use Today | Rare | Very common |
18. What is a Queue? Name some implementations like PriorityQueue.
A Queue is a collection designed primarily for holding elements before processing. The most common behavior is FIFO, where the first inserted element is the first removed, but different implementations can apply other ordering rules. Queues are useful in task scheduling, buffering, producer-consumer systems, and breadth-first processing.
Common implementations include LinkedList, ArrayDeque, PriorityQueue, and blocking queues such as ArrayBlockingQueue or LinkedBlockingQueue. PriorityQueue is especially important because it does not process by insertion order. Instead, it removes elements based on natural order or a comparator, which makes it useful for priority-based scheduling.
19. What does Arrays.asList() return? Can you add elements to it?
Arrays.asList() returns a fixed-size list backed by the original array. That means you can read elements and replace existing values with set(), but you cannot change the size of the list by calling add() or remove().
Because the list is backed by the array, changes can reflect in both directions. That behavior surprises many people because the return type is still List, so it looks more flexible than it really is.
This is a very common trap. If you need a truly resizable list, the safe pattern is to wrap it in a new ArrayList.
List<String> fixed = Arrays.asList("A", "B", "C");
fixed.set(1, "BB"); // allowed
// fixed.add("D"); // throws UnsupportedOperationException
List<String> modifiable = new ArrayList<>(Arrays.asList("A", "B", "C"));
modifiable.add("D"); // allowed20. How do you create a synchronized collection?
You can create synchronized wrappers using utility methods from Collections, such as Collections.synchronizedList(), Collections.synchronizedSet(), and Collections.synchronizedMap(). These wrappers make individual method calls thread-safe by synchronizing access internally.
However, that does not automatically make compound operations safe. For example, iterating over a synchronized list still requires external synchronization on the list during iteration, otherwise another thread may still interfere with traversal.
A strong interview answer should also mention that synchronized wrappers are only one option. They are simple and valid, but concurrent collections like ConcurrentHashMap, ConcurrentLinkedQueue, or CopyOnWriteArrayList are often better when scalability, weakly consistent iteration, or read-heavy concurrency is important.
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
Map<Integer, String> safeMap = Collections.synchronizedMap(new HashMap<>());
synchronized (safeList) {
for (String item : safeList) {
System.out.println(item);
}
}21. What is the difference between fail-fast and fail-safe iterators?
Fail-fast iterators immediately throw a ConcurrentModificationException if they detect that the collection has been structurally modified after the iterator was created, unless the modification was made through the iterator’s own remove() method. They operate directly on the collection itself.
Fail-safe iterators (often weakly consistent) do not throw this exception because they operate on a clone of the collection or tolerate modifications gracefully, as seen in CopyOnWriteArrayList and ConcurrentHashMap.
The main trade-off is performance versus consistency. Fail-fast is cheaper but strictly blocks concurrent writes. Fail-safe handles concurrent writes safely, but reading a clone may mean the iterator does not reflect the absolute latest state of the collection.
| Aspect | Fail-Fast Iterator | Fail-Safe Iterator |
|---|---|---|
| Throws Exception | ConcurrentModificationException on structural change | Does not throw exception |
| Internal Mechanism | Works directly on collection | Works on clone or handles changes gracefully |
| Examples | ArrayList, HashMap, HashSet | CopyOnWriteArrayList, ConcurrentHashMap |
| Memory Overhead | Low | Higher (due to cloning or tracking) |
22. What is the difference between poll() and remove() in a Queue?
Both methods are used to retrieve and remove the head of a Queue, but they handle an empty queue differently.
If the queue is empty, poll() returns null. On the other hand, remove() throws a NoSuchElementException.
This distinction is important because it dictates how you write the surrounding logic. You use remove() when an empty queue indicates a bug or an exceptional state in your logic. You use poll() when polling an empty queue is a normal, expected condition and returning null is an acceptable signal to stop processing.
| Method | Behavior when Queue is not empty | Behavior when Queue is empty |
|---|---|---|
poll() | Returns and removes head | Returns null |
remove() | Returns and removes head | Throws NoSuchElementException |