
You can iterate over collections using Iterator or enhanced for loop.
Using Iterator:
1 | Iterator<String> iterator = fruits.iterator(); |
Using Enhanced for-loop:
1 | for (String fruit : fruits) { |
The Collections class provides several utility methods to work with collections, such as:
A List is an ordered collection (or sequence) that allows duplicate elements. Elements in a List are indexed, meaning each element is positioned at a specific index and can be accessed via its index.
List:Implementations of the List Interface:
get() and set() operations).1 | List<String> fruits = new ArrayList<>(); |
1 | List<String> fruits = new LinkedList<>(); |
| Feature | ArrayList | LinkedList |
|---|---|---|
| Memory structure | Stored in a contiguous block of memory (like an array). | Stored as nodes in heap memory, each holding data + pointers. |
| Resizing | Automatically resizes when full by copying elements to a new, 1.5x larger array. | No resizing — just adds/removes nodes, updating pointers. |
| Memory overhead | Less overhead (stores only data). | More overhead (stores data + pointers for next/prev nodes). |
| Cache performance | Cache-friendly — elements are stored sequentially, helping CPU fetch data faster. | Cache-unfriendly — nodes are scattered in memory, causing pointer chasing. |
| Insertion/Deletion | Slower — needs shifting elements when adding/removing (except at the end). | Faster — adding/removing nodes is quick (only pointer updates). |
| Access time | Faster (O(1)) — can directly access any index. | Slower (O(n)) — must traverse nodes from the head/tail to find an element. |
| Best use case | Frequent access to elements by index. | Frequent additions/removals (especially at the start/middle). |
push(), pop(), and peek() for stack operations.1 | Stack<String> stack = new Stack<>(); |
In Java, a Queue is a collection used to store elements in a particular order, typically following the FIFO (First-In-First-Out) principle. This means the first element added to the queue will be the first one to be removed.
Queue:add(E e): Adds the specified element to the queue. If the element cannot be added (due to capacity restrictions), it throws an IllegalStateException.offer(E e): Adds the specified element to the queue. Unlike add(), it does not throw an exception if the queue is full; it simply returns false if the element cannot be added.remove(): Removes and returns the head of the queue. Throws NoSuchElementException if the queue is empty.poll(): Removes and returns the head of the queue. Returns null if the queue is empty.element(): Returns the head of the queue without removing it. Throws NoSuchElementException if the queue is empty.peek(): Returns the head of the queue without removing it. Returns null if the queue is empty.LinkedList:
LinkedList implements the Queue interface and can be used as a basic queue. It is efficient for adding/removing elements from both ends.1 | Queue<String> queue = new LinkedList<>(); |
PriorityQueue:
PriorityQueue orders its elements based on their natural ordering or a specified comparator. It does not follow FIFO strictly; instead, it processes elements based on their priority.1 | Queue<Integer> queue = new PriorityQueue<>(); |
In Java, a Set is a collection that does not allow duplicate elements and does not guarantee any specific order of elements. It models the mathematical set abstraction, where each element in the collection must be unique.
add(E e): Adds the specified element to the set. If the element already exists, the set remains unchanged, and it returns false.
remove(Object o): Removes the specified element from the set. If the element exists, it is removed, and the method returns true. Otherwise, it returns false.
contains(Object o): Returns true if the set contains the specified element, otherwise false.
size(): Returns the number of elements in the set.
isEmpty(): Returns true if the set contains no elements, otherwise false.
clear(): Removes all elements from the set.
iterator(): Returns an iterator over the elements in the set. The iteration order is typically not predictable (unless using LinkedHashSet or TreeSet).
1 | Iterator<String> iterator = set.iterator(); |
HashSet:
Set interface and does not maintain any order of its elements.1 | Set<String> set = new HashSet<>(); |
LinkedHashSet:
Set interface and maintains the order in which elements were inserted.1 | Set<String> set = new LinkedHashSet<>(); |
TreeSet:
Set interface and stores elements in a sorted order according to their natural ordering or a specified comparator.1 | Set<Integer> set = new TreeSet<>(); |
A Map in Java is a collection that stores key-value pairs — kind of like a dictionary! It’s part of the java.util package but does not extend Collection. Let’s break it down into key points:
Stores key-value pairs: Each key is unique, and each key maps to one value.
No duplicate keys: If you insert a new entry with an existing key, it overwrites the old value.
Allows null values
(depending on the implementation).
HashMap allows 1 null key and multiple null values.TreeMap and Hashtable don’t allow null keys.Here are the most used Map implementations and their differences:
| Implementation | Ordering | Null Keys/Values | Thread-Safe | Performance |
|---|---|---|---|---|
| HashMap | ❌ Unordered | ✅ 1 null key, multiple null values | ❌ No | ⚡ Fast (O(1) get/put) |
| LinkedHashMap | ✅ Insertion order | ✅ Same as HashMap |
❌ No | ⚡ Slightly slower than HashMap |
| TreeMap | ✅ Sorted (natural/comparator) | ❌ No null keys, ✅ null values | ❌ No | 🐢 Slower (O(log n)) due to sorting |
| Hashtable | ❌ Unordered | ❌ No null keys/values | ✅ Yes | 🐌 Slower (legacy) |
| ConcurrentHashMap | ❌ Unordered | ❌ No null keys/values | ✅ Yes (better than Hashtable) | ⚡ Good for concurrency |
| Method | Description |
|---|---|
put(key, value) |
Inserts or updates a key-value pair |
get(key) |
Returns the value for the given key |
remove(key) |
Removes the key-value pair |
containsKey(key) |
Checks if the map has the given key |
containsValue(value) |
Checks if the map has the given value |
keySet() |
Returns a Set of all keys |
values() |
Returns a Collection of all values |
entrySet() |
Returns a Set of Map.Entry (key-value pairs) |
putIfAbsent(key, value) |
Inserts only if the key doesn’t exist |
replace(key, value) |
Replaces the value for an existing key |
Here are 3 popular ways to loop through a map:
Using for-each and entrySet()
1 | for (Map.Entry<String, Integer> entry : map.entrySet()) { |
Using keySet() (keys only)
1 | for (String key : map.keySet()) { |
Using forEach() (Java 8 Lambda)
1 | map.forEach((key, value) -> System.out.println(key + " : " + value)); |
Immutable Maps (Java 9+)
1 | Map<String, String> map = Map.of("key1", "value1", "key2", "value2"); |
Sorted Maps:
TreeMap keeps keys sorted by natural order or a custom Comparator.
Thread-safe Maps:
Hashtable (legacy, slow).ConcurrentHashMap (modern, faster alternative).HashCode()hashCode() is a method in Java that returns an integer value that is used to identify objects in hash-based collections (like HashMap, HashSet, Hashtable).hashCode() is to efficiently map objects to hash buckets for fast lookups.Why Override hashCode()
equals() method, you must override hashCode() as well. This ensures that objects that are considered equal by equals() will return the same hash code.The General Contract of hashCode()
a.equals(b) is true, then a.hashCode() must be equal to b.hashCode().hashCode() value should remain consistent throughout the lifetime of the object, unless a property used in equals() and hashCode() is modified.Array of Buckets: A HashMap is implemented using an array of buckets (typically an array of Node or Entry objects). Each bucket is a linked list (or a tree if collisions become too high) that stores key-value pairs.
When a key is added, the HashMap computes a hash code for the key using the key’s hashCode() method.
The hash code is then transformed into an index for the bucket array using a hash function (e.g., index = hashCode % array.length).
Collision Handling:
HashMap uses chaining, where each bucket stores a linked list of key-value pairs. Each node in the list contains the key, value, and a reference to the next node in the same bucket.TreeNode) for better performance (O(log n) instead of O(n) in case of long chains).When the number of elements exceeds a certain threshold (based on the load factor, typically 0.75), the HashMap doubles the array size and rehashes the existing entries to new bucket locations.
In Java, both Comparable and Comparator are functional interfaces used for comparing objects, but they serve different purposes and are used in different contexts.
Comparable InterfacePurpose: Used to define the natural ordering of objects. It allows a class to specify how its instances should be compared.
Location: It is implemented by the class whose instances need to be compared.
Method: It has a single method:
1 | int compareTo(T o); |
this) with another object (o).this is less than o.this is equal to o.this is greater than o.Example:
1 | class Student implements Comparable<Student> { |
When to Use: Use Comparable when the class has a natural order (e.g., sorting by age, name, or any primary field).
Comparator InterfacePurpose: Used to define custom ordering of objects, separate from the natural order defined by Comparable. A Comparator is useful when you want to compare objects based on multiple fields or in different ways.
Location: A separate class implements the Comparator interface, not the object being compared.
Method: It has two methods:
int compare(T o1, T o2);boolean equals(Object obj); (optional for Comparator interface, not always used)The compare method compares two objects and returns:
o1 is less than o2.o1 is equal to o2.o1 is greater than o2.Example:
1 | class Student { |
When to Use: Use Comparator when you need to define multiple ways to compare objects or if you want to compare objects based on fields that don’t represent the natural ordering.
| Feature | Comparable |
Comparator |
|---|---|---|
| Implemented by | The class whose instances are being compared. | A separate class or anonymous class. |
| Purpose | Defines the natural ordering of objects. | Defines custom orderings of objects. |
| Method | compareTo(T o) |
compare(T o1, T o2) |
| When to Use | When you want to define a default ordering (natural order). | When you need multiple or different ways to compare objects. |
A functional interface in Java is an interface that has exactly one abstract method, but it can have multiple default or static methods. Functional interfaces are used primarily in lambda expressions and method references.
The @FunctionalInterface annotation is optional, but it helps to clarify that the interface is intended to be functional. The compiler will also check that the interface conforms to the rules of a functional interface.
Predicate<T>boolean test(T t): Evaluates the predicate on the given argument.1 | javaCopy codeimport java.util.function.Predicate; |
Function<T, R>T and returns a result of type R. It is useful for transformations and mappings.R apply(T t): Applies the function to the given argument.default <V> Function<T, V> andThen(Function<? super R, ? extends V> after): Composes the function to apply after the current function.default <V> Function<V, R> compose(Function<? super V, ? extends T> before): Composes the function to apply before the current function.1 | javaCopy codeimport java.util.function.Function; |
Consumer<T>void accept(T t): Performs the operation on the given argument.default Consumer<T> andThen(Consumer<? super T> after): Combines two Consumer operations where the second is executed after the first one.1 | javaCopy codeimport java.util.function.Consumer; |
Supplier<T>T. It is often used to generate or supply values.T get(): Returns a result.1 | javaCopy codeimport java.util.function.Supplier; |

