In this chapter you’ll learn about Java’s Collections Framework — a set of flexible, resizable data structures that go far beyond the fixed-size arrays you’ve used so far. Instead of being limited to a single fixed-length array, you’ll work with lists, sets, and maps — each designed for different use cases.
Almost every backend endpoint you build will involve collections: returning a list of users, looking up a product by ID in a map, ensuring a set of unique tags. Mastering these structures now prepares you for the REST APIs you’ll build in Week 4.
Pre-requisites:
The Collections Framework provides these data structures as classes bundled in packages in your standard JDK installation.
<aside> 💡
JDK is the SDK for Java (Java Development Kit) and it is really a development kit for your application needs.
</aside>
The most frequently used collections are ArrayList, HashSet, and HashMap. Each implements an interface above it in the hierarchy. To understand the Collections Framework, we need to briefly cover these interfaces. Take a look at the image below:

Diagram of ArrayList,HashSet and HashMap classes’ ancestor interfaces
In the diagram above, Iterable sits at the top with the most abstract functionality. As you move down the hierarchy, classes become more specific and provide more functionality.
This hierarchical structure offers many advantages. Here are a couple:
We'll start this chapter at the top with Iterable.java, then move to Collection.java → List.java and Set.java → implementations of Lists and Sets (ArrayList and HashSet). We'll follow the same path for Map.java and its main implementation HashMap, which have their own ancestry but remain similar to Sets and use Set APIs for comparable functionality.
Iterable is an interface that enforces a contract for a few basic methods. Classes implementing this interface allow their instances to be used in for-each loops, which you learned about in Week 1 - Control Flow.
To implement this interface, classes have to implement iterator() , the interface has other methods are already implemented with a workable default:

Iterable has 2 other methods that are left out as they’re implemented by default
As you can see iterator() must return an Iterator instance. That in itself is an interface that requires next(), hasNext() :

Iterator’s forEachRemaining() and remove() methods are left out as they’re not required
Zooming out back to the Iterable interface, we can summarize that it requires two methods to be implemented:
next() method to loop over an array with a for-each loop.hasNext() method to prevent infinite loops or out-of-bounds errors, as you also need to know if there's a next element to continue or stop iterating.Can you notice how this is the bare minimum for looping? That's not a coincidence—Iterable sits at the top of the hierarchy; therefore, it should be the most general.
Iterable is very raw and rarely implemented directly. It's usually extended by other interfaces like Collection.
Collection extends Iterable with additional methods such as contains(), equals(), add(), and remove(). By implementing these methods, classes gain access to useful APIs that operate on Collections—like the Stream API, which you'll learn about in Stream APIs chapter.
<aside> ⌨️
Hands-on: Find the Collection interface in your Java installation. First, locate your Java/JDK installation directory.
On my machine (macOS), the Java installation directory is: /Users/Ali/Library/Java/azul-25.0.2. Below are instructions for different platforms:
Take a look at the interface. Don't worry about understanding everything—just explore and find questions to ask 🙂
There's a much easier way to read this file directly in your IDE. You rarely need to navigate to it manually, but it helps to understand the structure first.
</aside>
Just like the Iterable interface, the Collection interface is still quite general and is rarely implemented directly; it is again extended by other interfaces as seen in the Diagram of Collection Hierarchy above.
Let’s cover the two main extensions of Collection, List and Set interfaces.
Lists are ordered collections that allow duplicate elements. The main implementations are ArrayList and LinkedList.
We'll focus only on ArrayList here. LinkedList offers the same functionality with the main difference that ArrayList recreates the list in memory with each resize, while LinkedList doesn't, trading faster modifications for slower reads.
<aside> ❗
"Ordered" should not be confused with "Sorted". Ordered means the collection tracks where each element sits within the collection. In plain English: there's an index for each element, so you can retrieve, add, update, or remove elements precisely using an index rather than looping through the entire array.
</aside>
ArrayList is a direct implementation of the List interface. Since it's a fully implemented class, we can create an instance of (AKA, instantiate) and directly use it in our code.
Initializing an ArrayList and adding elements:
void main() {
List<String> groceries = new ArrayList<String>();
groceries.add("Croissant");
groceries.add("Espresso");
IO.println(groceries);
}
<aside> 💡
IO.println is a utility shorthand that works like System.out.println(). We use it throughout these examples for brevity. If your project doesn’t have an IO class, simply use System.out.println() instead.
</aside>
Notice how we assign the new ArrayList instance to the List interface? It's good practice—now we can work with our array through the List interface, and if we later need a different List implementation, we simply change the initialization statement. All code expecting a List, like groceries.add("Croissant") continues to work.
Read: We can retrieve elements from the ArrayList using an index: IO.println(groceries.get(0)) outputs Croissant.
Update:
groceries.set(0, "Choco-Croissant");
IO.println(groceries);
Delete:
groceries.remove(1);
IO.println(groceries);
<aside> ⌨️
Hands on: Modify the grocery list code above to add 3 more items, then print the size of the list using groceries.size().
</aside>
Breakdown of ArrayList:
ArrayList signature in ArrayList.java is:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList<E>: means it is a class that takes a type E which is just a placeholder name for a type. Most likely the placeholder was named E for element-type. When we instantiated the array above, we decided that its element-type would be String in the statement new ArrayList<String> . You’ll learn much more about this in the Week 3 - Generics chapter.extends AbstractList<E>: AbstractList is an abstract class that implements some convenient methods for List. It is not complete (Abstract) and can’t be instantiated on its own.implements List<E>: Direct implementation of List to allow list functionalityimplements: Will not be covered here but they allow other functionalitiesSince <E> is already known to be a String at the declaration part of our statement, before the equal sign in List<String> groceries = new ArrayList<String>() , we no longer need to add it at the second part and that’ll be inferred by the compiler so List<String> groceries = new ArrayList<>() is equally valid.
Sets are collections that contain no duplicate elements.
HashSet is the main implementation of Set. It's called HashSet because it uses a hash table—an internal structure that converts each value into a numeric code (a "hash") for near-instant lookup, like using an index in a book. HashSet doesn't guarantee an order which means there’s no index for retrieving elements.
HashSet is a fully implemented class that you can instantiate and directly use in your code:
Initializing a set and adding elements:
void main() {
Set<Integer> userIds = new HashSet<>();
userIds.add(40012);
userIds.add(30001);
userIds.add(40011);
IO.println(userIds);
}
Read: As sets are unordered collections, there’s no get() method for retrieval.
However, reading sets should be thought of differently than for lists. When reading sets, you shouldn’t need to retrieve by index, if you do, then you’re using the wrong collection. To give one use case as an example:
void main() {
Set<Integer> whitelistUserIds = new HashSet<>();
whitelistUserIds.add(40012);
whitelistUserIds.add(30001);
whitelistUserIds.add(40011);
String response = whitelistUserIds.contains(40011) ? "User is whitelisted" : "User is not whitelisted";
IO.println(response);
}
Update/Delete:
Sets also don’t provide set() method like lists. So updates should be thought of as removing and adding an element.
userIds.remove(40011);
userIds.add(20011);
IO.println(userIds);
Breakdown of HashSet:
Signature is almost the same as ArrayList
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
No breakdown needed—just notice that it implements the Set interface. Also, AbstractSet is like AbstractList: an abstract class with convenience methods for sets.
💬 When would you choose a Set over a List? Think of a real-world example where duplicates would be a problem.