Back to Blog
Java

Java List vs Set: When to Use Each Collection

Understand the behavioral differences between Java List and Set, including order, duplicates, implementations, conversion, and performance tradeoffs.

Java CollectionsList InterfaceSet InterfaceData StructuresCollection Framework
Illustration comparing Java List and Set collections, showing an ordered sequence with duplicates versus a group of unique elements.

A common question in Java development is when to use a List versus a Set. Both extend the Collection interface and both hold a group of objects, but they make fundamentally different guarantees. A List maintains insertion order and permits duplicates. A Set rejects duplicates and, depending on the implementation, may or may not preserve order. Choosing the wrong one can produce subtle bugs that only surface with specific input.

The Core Behavioral Difference

The core distinction is what can be stored. A List is an ordered sequence: elements have positions, and the same object can appear multiple times. A Set is a mathematical set: no duplicate elements are allowed. When you attempt to add a duplicate to a Set, the element is not added and add returns false.

List<String> list = new ArrayList<>(); list.add("apple"); list.add("banana"); list.add("apple"); System.out.println(list.size()); // 3 Set<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); set.add("apple"); System.out.println(set.size()); // 2

The first block prints 3 because ArrayList accepts the second "apple". The second block prints 2 because HashSet rejects the duplicate. This is the most important behavioral distinction between the two interfaces, and it drives most of the other differences.

List: Order, Indexing, and Duplicates

A List is defined by positional access. You can retrieve an element by index, insert at a specific position, and iterate in the order elements were added. The interface includes methods such as get(int index), set(int index, E element), and add(int index, E element) that have no counterpart in Set.

List<String> names = new ArrayList<>(); names.add("alice"); names.add("bob"); names.add(1, "carol"); // insert at position 1 System.out.println(names.get(0)); // alice System.out.println(names); // [alice, carol, bob]

The ability to address elements by position makes List the right choice when the order of elements carries meaning, such as a queue of tasks to process in sequence or a history of user actions. Duplicates are often intentional in these scenarios: the same task may appear twice, or the same product may appear multiple times in a shopping cart.

Set: Uniqueness and the Role of equals and hashCode

Hash-based Set implementations enforce uniqueness through the equals and hashCode methods of their elements. (TreeSet, described below, uses natural ordering or a Comparator instead.) When you add an element to a HashSet, the implementation checks whether an equal element already exists. For HashSet, this check is backed by a hash table, so the hashCode contract must be respected: equal objects must have equal hash codes. If you mutate an object after adding it to a HashSet in a way that changes its hash code, the set can no longer locate it correctly.

public class User { private String email; public User(String email) { this.email = email; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof User)) return false; return email.equals(((User) o).email); } @Override public int hashCode() { return email.hashCode(); } }

With proper equals and hashCode implementations, the set correctly deduplicates users by email. Without them, two User objects with the same email are treated as distinct because Object.equals compares references. This is a common source of bugs when developers use a Set without overriding these methods.

Implementations and Their Tradeoffs

Choosing a concrete implementation is also important, because different implementations have different ordering and performance characteristics even within the same interface.

ArrayList is the default List implementation. It stores elements in a resizable array, offering constant-time positional access and amortized constant-time append. Insertion or removal in the middle requires shifting elements, which is O(n).

LinkedList implements List with a doubly linked list. Insertion and removal at either end are constant time, but positional access is O(n) because the list must be traversed. In practice, LinkedList is rarely the right choice unless you specifically need efficient insertion at both ends.

HashSet is the default Set implementation. It offers constant-time add, remove, and contains on average, but iteration order is unspecified and can change when the set is resized.

LinkedHashSet maintains insertion order while preserving the uniqueness guarantee. It is useful when you need deduplication and predictable iteration order.

TreeSet stores elements in sorted order using a red-black tree. The core add, remove, and contains operations are O(log n), and iteration yields elements in natural or comparator-defined order. The elements must be Comparable, or the set must be constructed with a Comparator.

ImplementationOrderingDuplicatesTypical operation cost
ArrayListInsertion orderAllowedO(1) append; O(n) contains
LinkedListInsertion orderAllowedO(1) add/remove at ends; O(n) contains
HashSetUnspecifiedRejectedO(1) average
LinkedHashSetInsertion orderRejectedO(1) average
TreeSetSortedRejectedO(log n)

Converting Between List and Set

A common need is converting one collection type to the other. Most collection classes provide a constructor that accepts another collection, so conversion is straightforward.

List<String> list = Arrays.asList("x", "y", "x", "z"); Set<String> set = new HashSet<>(list); // deduplicates System.out.println(set); // [x, y, z] or similar order List<String> backToList = new ArrayList<>(set); System.out.println(backToList); // order depends on set implementation

When converting from List to Set, duplicates are lost. When converting from Set to List, the resulting order depends on the set implementation: HashSet gives no order guarantee, LinkedHashSet preserves insertion order, and TreeSet yields sorted order. If you need a specific order after conversion, choose the set implementation accordingly.

Performance Considerations

Performance differences between List and Set are most visible in membership checks. A List must scan linearly to determine whether it contains an element, which is O(n). A HashSet performs the same check in constant time on average. For a collection with thousands of elements, repeated contains calls on a List can become a measurable bottleneck.

List<String> list = new ArrayList<>(largeData); if (list.contains("target")) { // O(n) per call // ... } Set<String> set = new HashSet<>(largeData); if (set.contains("target")) { // O(1) average // ... }

The tradeoff is memory. A HashSet maintains a hash table with extra storage overhead compared to the compact array of an ArrayList. If the collection is small or membership checks are rare, the memory overhead may not be justified. If membership checks dominate the workload, the Set is the better choice.

Another consideration is iteration. Iterating over an ArrayList is fast because elements are contiguous in memory. Iterating over a HashSet involves traversing the hash table, which is slower per element due to cache misses. If you iterate far more often than you check membership, the List may be faster despite the linear contains.

Choosing Between List and Set

The decision comes down to two questions. First, does the order of elements carry meaning? If yes, use a List, or a LinkedHashSet if uniqueness also matters. Second, can the same element appear more than once? If duplicates are valid data, a Set cannot represent them.

Use a List when you need positional access, when duplicates are meaningful, or when iteration order must match insertion order. Use a Set when uniqueness is a business rule, when membership checks are frequent, or when you want to eliminate duplicates before further processing.

A practical pattern is to use a Set for deduplication during data ingestion and then convert to a List when the final ordered result is needed. This combines the strengths of both interfaces without sacrificing correctness.

Java List vs Set: Practical Usage and Code Examples | RYUSLOG DEV