Java LinkedHashSet: Order and Performance
Java LinkedHashSet preserves insertion order while retaining HashSet's hash-based performance. See practical examples and when to use it instead of HashSet or TreeSet.
java.util.LinkedHashSet is a Set implementation that preserves insertion order while providing the hash-based constant-time operations of HashSet in the average case. It is useful for caching, deduplication with order retention, and any scenario where the sequence of first occurrence matters.
How LinkedHashSet Maintains Insertion Order
LinkedHashSet extends HashSet and uses a hash table internally, but it also maintains a doubly linked list that connects all entries in the order they were inserted. This linked list is what gives the set its predictable iteration order. Unlike LinkedHashMap, which can be configured for access order, LinkedHashSet always uses insertion order. Re-adding an element that already exists does not change its position in the iteration order; the set simply ignores the duplicate.
The internal structure means that iteration order is deterministic and matches the order of first insertion. This is not a sorting order; it is purely the sequence in which elements were added. If you remove an element and then add it again, it appears at the end, because the removal breaks the link and the re-insertion creates a new link.
Creating and Using a LinkedHashSet
Using LinkedHashSet is straightforward. The class provides the same constructors as HashSet, including a default constructor, one with initial capacity, one with initial capacity and load factor, and one that accepts another collection.
import java.util.LinkedHashSet; import java.util.Set; Set<String> visitedUrls = new LinkedHashSet<>(); visitedUrls.add("/home"); visitedUrls.add("/products"); visitedUrls.add("/cart"); visitedUrls.add("/home"); // duplicate, ignored System.out.println(visitedUrls); // Output: [/home, /products, /cart]
The iteration order reflects the first occurrence of each element. This is useful when you need to process unique items in the order they were first seen, such as building a list of unique page views or preserving the order of user actions.
You can iterate using an enhanced for loop or an iterator:
for (String url : visitedUrls) { System.out.println("Visited: " + url); }
The iterator is fail-fast: if the set is structurally modified after the iterator is created (except through the iterator's own remove method), a ConcurrentModificationException is thrown. This is consistent with other collection iterators in the Java Collections Framework.
LinkedHashSet vs HashSet vs TreeSet
The choice among these three Set implementations depends on whether you need ordering and what kind of ordering. The table below summarizes the key differences.
| Feature | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| Iteration order | Unspecified | Insertion order | Sorted (natural or comparator) |
| Internal structure | Hash table | Hash table + doubly linked list | Red-black tree |
| Time complexity | O(1) average | O(1) average | O(log n) |
| Memory overhead | Lowest | Moderate (linked list) | Higher (tree nodes) |
| Null handling | Allows null | Allows null | Does not allow null under natural ordering |
Use HashSet when order does not matter and you want the lowest memory footprint. Use TreeSet when you need elements sorted according to a comparator or natural order, and you are willing to accept O(log n) operations. Use LinkedHashSet when you need to preserve insertion order without sacrificing the constant-time performance of a hash-based set.
Performance and Memory Considerations
LinkedHashSet provides O(1) average time for add, remove, and contains, just like HashSet. The extra linked list adds a constant overhead per element: each entry must store references to the previous and next nodes. This increases memory consumption compared to HashSet, but the impact is usually modest unless you are storing millions of elements.
Iterating over a LinkedHashSet follows the linked list in insertion order. That is different from HashSet, whose iteration order is unspecified and depends on the hash table layout. Do not use iteration microbenchmarks as the main selection criterion; the deciding factor should be whether you need a deterministic order.
When initializing a LinkedHashSet with a known large number of elements, setting an appropriate initial capacity can reduce rehashing overhead. The same guidance applies as for HashSet: choose an initial capacity that is roughly the expected number of elements divided by the load factor (default 0.75) to avoid repeated resizing.
Common Pitfalls and Edge Cases
One common mistake is assuming that LinkedHashSet sorts elements. It does not. If you need sorted iteration, use TreeSet or explicitly sort the elements after extraction.
Another pitfall is using mutable objects as keys. If an element's hashCode() changes after it is inserted, the set will not be able to locate it correctly, and behavior becomes unpredictable. This applies to all hash-based sets, not just LinkedHashSet. Ensure that elements are effectively immutable or that their hash code does not change while they are stored.
The equals and hashCode contract is critical. Two elements that are equal must have the same hash code. If you override equals without hashCode, the set may contain duplicates or fail to find elements. Use Objects.equals and Objects.hash for reliable implementations.
Null handling: LinkedHashSet permits one null element, just like HashSet. If you attempt to add a second null, it is ignored because the set already contains null. This is fine for most use cases. Be aware that TreeSet with natural ordering does not allow null elements; a custom comparator may allow them, depending on its implementation.
When to Choose LinkedHashSet
Choose LinkedHashSet when you need to deduplicate elements while preserving the order of their first appearance. Typical use cases include:
- Maintaining a list of unique user actions in the order they occurred.
- Caching keys when eviction order matters; for true LRU eviction, use
LinkedHashMapwith access-order mode. - Producing a unique list of configuration names or options in the order they were defined.
For example, suppose you are processing a stream of events and want to collect the distinct event types in the order they first appear:
List<String> events = List.of("LOGIN", "SEARCH", "LOGIN", "PURCHASE", "SEARCH"); Set<String> uniqueEvents = new LinkedHashSet<>(events); System.out.println(uniqueEvents); // [LOGIN, SEARCH, PURCHASE]
The constructor that accepts a Collection copies the elements and preserves the order of the source collection, which is convenient for deduplication.
If you need to remove the oldest element when the set reaches a certain size, a LinkedHashSet alone does not provide that capability. You would need to combine it with a Deque or use LinkedHashMap with access order. The set's insertion order is static; it does not change when elements are accessed.
In concurrent environments, LinkedHashSet is not thread-safe. Use Collections.synchronizedSet(new LinkedHashSet<>()) if you need synchronized access, or use a ConcurrentSkipListSet if you need a thread-safe sorted set. The synchronized wrapper maintains the insertion order but requires external synchronization during iteration to avoid ConcurrentModificationException.