1. Introduction to LinkedHashSet
LinkedHashSet is an implementation of Java's Set interface that stores unique elements while preserving their insertion order. It combines two useful properties:
- Uniqueness: adding an element that is already present does not create a duplicate.
- Insertion-order iteration: iterating over the set visits elements in the order in which each unique element was first added.
For example, suppose a shopping application records the products a customer views:
Notebook → Pen → Eraser → Pen → Backpack
A LinkedHashSet retains:
Notebook → Pen → Eraser → Backpack
The second occurrence of Pen is ignored, and the original position of Pen remains unchanged.
LinkedHashSet is useful when an application needs both deduplication and a stable, meaningful display order. Examples include recently viewed products, unique categories browsed, coupon codes entered, and deterministic test data.
2. LinkedHashSet at a Glance
| Property | Description |
|---|---|
| Main purpose | Store unique elements in insertion order |
| Interface | Implements Set<E> |
| Duplicate elements | Not permitted |
| Iteration order | Order of first insertion |
Average add, remove, contains | O(1), assuming well-distributed hash codes |
| Iteration | O(n) in the number of elements |
| Null values | Allows one null element |
| Thread safety | Not synchronized by default |
| Main trade-off | Uses more memory than a plain HashSet to preserve order |
LinkedHashSet is not a sorted set. If values are inserted as 30, 10, and 20, iteration preserves that sequence rather than arranging the numbers numerically.
3. How Insertion Order Works
The defining behavior of LinkedHashSet is that iteration follows the order in which elements were first successfully inserted.
Consider these operations:
- Add
"Notebook"— it becomes the first element. - Add
"Pen"— it becomes the second element. - Add
"Eraser"— it becomes the third element. - Add
"Pen"again — no new element is added, and its position does not change. - Add
"Backpack"— it becomes the last element.
The resulting order is Notebook, Pen, Eraser, Backpack.
The return value of add() can tell the caller whether the set changed. It returns true when the element was newly added and false when an equal element was already present.
Important: LinkedHashSet preserves insertion order, not the order of every attempted insertion. A duplicate insertion is not a new insertion.
4. Internal Structure: Hashing Plus a Linked List
LinkedHashSet is built on the same general hashing idea as HashSet, but it also maintains a linked structure to remember element order. In common Java implementations, its backing map is a LinkedHashMap.
The internal design can be understood as two cooperating structures:
- Hash table: helps locate elements efficiently using their hash codes.
- Doubly linked list: connects entries in insertion order so the set can iterate from the earliest entry to the latest.
Each entry participates in the hash-table organization and has links to the preceding and following entries in the order list. When a new unique element is added, it is connected at the end of the list. When an element is removed, its links are disconnected.
This dual structure explains the main trade-off: the hash table supports fast membership operations, while the linked list provides predictable insertion-order iteration.
5. LinkedHashSet and HashSet
Both classes reject duplicates and typically provide average constant-time add(), remove(), and contains() operations. Their key difference is the iteration-order guarantee.
| Feature | HashSet | LinkedHashSet |
|---|---|---|
| Duplicates | Rejected | Rejected |
| Iteration order | Not guaranteed | Insertion order |
| Average lookup and update | O(1) | O(1) |
| Extra order bookkeeping | No insertion-order list | Doubly linked list |
| Memory use | Usually lower | Usually higher |
| Best fit | Order does not matter | Original insertion order matters |
Do not rely on a particular order observed from a HashSet. Its output can appear stable for a particular run or collection of values, but that behavior is not a contract.
Choose LinkedHashSet when preserving the first-seen order is part of the requirement. If order does not matter, HashSet is often the simpler and more memory-efficient choice.
6. Duplicate Handling and the Set Contract
A Set treats two elements as duplicates when the collection's equality rules identify them as equal. For ordinary objects, correct implementations of equals() and hashCode() are therefore important.
When add(element) is called, the implementation uses the element's hash code to locate a likely bucket, then uses equality checks as needed to determine whether an equal element already exists. If an equal element is found, the set does not add another entry.
This also affects order. If "Pen" is inserted first and later added again, it stays in its original position. If it is removed and then added again, the new insertion places it at the end.
Understanding the return value of add()
true: the set changed because a new element was added.false: an equal element was already present, so the set remained unchanged.
This return value can be more efficient and clearer than calling contains() first and then add(). A separate check is often unnecessary.
7. The equals() and hashCode() Contract
For objects that represent values, the equals() and hashCode() methods must agree:
- If
a.equals(b)istrue, thena.hashCode()andb.hashCode()must be equal. - The hash code should remain stable while the fields used to determine equality remain unchanged.
- Equal hash codes do not prove that two objects are equal. Different objects can share a hash code; this is called a collision.
If a class overrides equals() but not hashCode(), two logically equal objects can be directed to different hash-table buckets. The set may then fail to recognize them as duplicates.
Use the same equality-defining fields in both methods. For a product identified by a product code, for example, equality and hashing should both be based on that code if it is the complete identity rule.
8. Mutable Elements: A Subtle Bug
A dangerous problem occurs when an element is added to a LinkedHashSet and then a field used by its equals() or hashCode() implementation changes.
The entry was placed in a hash bucket using the earlier hash code. If the field changes, a later lookup may calculate a different hash code and search another bucket. As a result, contains() or remove() may fail even though the object is still physically present in the set. The insertion-order list does not repair this hashing inconsistency.
How to avoid the problem
- Prefer immutable value objects for set elements.
- Make equality-defining fields
finalwhen the design permits it. - Do not modify fields used by
equals()orhashCode()while an object is in the set. - If the identity of an element must change, remove it first, modify it, and then add it again.
A field can remain mutable if it does not participate in equality or hashing and does not otherwise violate the class's equality contract.
9. Common Use Cases
9.1 Recently viewed unique products
A storefront can record product IDs as customers browse. LinkedHashSet removes repeat views while preserving the order in which each product was first seen. The interface can then display a meaningful browsing sequence without a separate deduplication step.
9.2 Unique browsed categories
An application may want to show the categories a customer visited, such as Books, Bags, and Electronics, in browsing order. LinkedHashSet preserves the sequence while avoiding repeated categories.
9.3 Coupon codes entered at checkout
A checkout flow can track unique coupon codes in entry order. The return value from add() indicates whether a code was newly recorded, allowing the application to distinguish a new entry from a duplicate without first calling contains().
9.4 Deduplicating a list while preserving first occurrence
A list such as ["Pen", "Notebook", "Pen", "Eraser", "Notebook"] can be passed to a LinkedHashSet constructor. The resulting set contains Pen, Notebook, and Eraser in that order.
Remember that a set is not a list: it offers uniqueness and set operations, but it does not provide index-based access such as get(0).
9.5 Stable test fixtures
Tests and demonstrations often benefit from repeatable iteration order. LinkedHashSet is useful when a test needs a Set's uniqueness behavior but also needs output in a predictable insertion sequence.
10. Constructors and Initial Capacity
LinkedHashSet provides constructors for creating an empty set, specifying an initial capacity and load factor, or copying elements from an existing collection.
| Constructor | Purpose |
|---|---|
new LinkedHashSet<>() | Creates an empty set using default sizing behavior |
new LinkedHashSet<>(initialCapacity) | Supplies an initial-capacity hint |
new LinkedHashSet<>(initialCapacity, loadFactor) | Supplies both capacity and load-factor settings |
new LinkedHashSet<>(collection) | Adds elements from a collection in that collection's iteration order |
The default load factor commonly used by these hash-based collections is 0.75. In modern Java implementations, the internal table is typically allocated lazily rather than necessarily allocating its full bucket array at the instant the set object is constructed. Exact implementation details can vary by Java version.
Copying a collection
The collection constructor visits the source collection in iteration order. Each first-seen unique element is added to the set; later equal elements are ignored. Consequently, copying a list into a LinkedHashSet preserves the order of the first occurrence of each unique element.
This behavior is especially useful for cleaning up duplicate data without losing its original sequence.
11. Capacity, Load Factor, and Resizing
The initial capacity and load factor influence the hash table's growth behavior.
- Initial capacity is a hint about the starting size of the internal bucket table.
- Load factor controls how full the table is allowed to become before it grows.
- Threshold is based on the table capacity multiplied by the load factor.
With a typical capacity of 16 and load factor of 0.75, the threshold is 12. When the number of entries passes the applicable threshold, the table grows and existing entries are redistributed into the new table. The insertion-order links continue to represent the sequence of entries.
Pre-sizing can reduce repeated resizing when the expected collection size is known. However, choosing an excessively large capacity consumes memory for unused buckets. LinkedHashSet iteration follows the linked entries, so iteration cost is proportional to the number of stored elements rather than requiring a scan of every empty bucket.
In everyday application code, defaults are usually adequate. Tune capacity when the expected size is known and measurements show that resizing or memory use matters.
12. Performance and Complexity
| Operation | Typical complexity | Explanation |
|---|---|---|
add(element) | O(1) average | Hashing locates a likely bucket; insertion also updates order links |
contains(element) | O(1) average | Searches the hash-table structure |
remove(element) | O(1) average | Finds the entry and unlinks it from the order list |
| Iterate through all elements | O(n) | Follows the linked list once |
| Resize and redistribute entries | O(n) for the resize work | Existing entries must be accommodated in the new table |
These average-case claims assume hash codes are distributed reasonably well. Heavy collisions can make lookup operations slower; in pathological cases, they can approach linear behavior, though modern Java implementations have additional collision-handling strategies for certain circumstances.
LinkedHashSet usually needs more memory per element than HashSet because it maintains the extra previous/next references for order. The extra memory buys predictable iteration and is worthwhile when insertion order is a requirement.
13. LinkedHashSet vs HashSet vs TreeSet
| Property | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| Duplicates | Not allowed | Not allowed | Not allowed under its ordering comparison |
| Iteration order | No guaranteed order | Insertion order | Sorted order |
Typical add, remove, contains | O(1) average | O(1) average | O(log n) |
| Main backing structure | Hash table / HashMap | Hash table plus linked order / LinkedHashMap | Balanced search tree / TreeMap |
| Relative memory | Usually lowest of these | Higher than HashSet | Tree-node overhead |
| Null handling | Allows one null | Allows one null | Generally does not accept null with natural ordering; comparator behavior can vary |
| Best use | Uniqueness without order needs | Uniqueness with insertion order | Uniqueness with sorted traversal and range-oriented operations |
A simple selection rule is:
- Choose HashSet when uniqueness matters but iteration order does not.
- Choose LinkedHashSet when uniqueness and first-insertion order both matter.
- Choose TreeSet when elements must be kept sorted according to natural ordering or a comparator.
TreeSet's uniqueness is determined by its ordering comparison: values for which the comparison returns zero are treated as the same set element. Therefore, its comparison logic should be consistent with the intended equality semantics.
14. Null Values, Generics, and Type Safety
LinkedHashSet permits one null element because a Set cannot contain duplicate values and only one null value can be present. Whether null is useful depends on the application; in many domains, explicit values or Optional-based designs communicate intent more clearly.
Generics specify the element type, for example LinkedHashSet<String> for strings or LinkedHashSet<Integer> for integer values. This allows the compiler to catch many accidental type mismatches and reduces the need for casting.
A set does not guarantee thread safety. If multiple threads modify the same LinkedHashSet without suitable coordination, the application must provide appropriate synchronization or choose a collection designed for its concurrency requirements. Wrapping a collection for synchronization also requires following the wrapper's iteration-synchronization guidance.
15. Common Mistakes and Best Practices
Common mistakes
- Assuming sorted order: insertion order is not alphabetical or numeric order.
- Expecting duplicates to move to the end: adding an existing element does not change its position.
- Overriding
equals()withouthashCode(): equal objects may not deduplicate correctly. - Changing hash-relevant fields after insertion: membership checks and removal can stop working as expected.
- Using LinkedHashSet when order is irrelevant: the additional links consume memory without providing a needed benefit.
- Using it as an indexed list: Set does not provide list-style positional access.
- Assuming thread safety: LinkedHashSet is not synchronized by default.
Best practices
- Select LinkedHashSet only when preserving first-insertion order is part of the requirement.
- Implement
equals()andhashCode()together for value-based objects. - Keep equality-defining fields stable while elements are stored in the set.
- Use
add()'s boolean result to detect whether an element was new. - Use the collection constructor to deduplicate existing ordered data while preserving first occurrences.
- Provide an appropriate initial capacity when a large expected size is known and performance measurements justify it.
- Choose TreeSet instead when sorted iteration is required.
- Do not rely on a collection's behavior beyond the guarantees of its documented contract.
16. Quick Revision
- LinkedHashSet is a Set implementation that preserves insertion order.
- It rejects duplicates and keeps the position of an element's first successful insertion.
- Adding an existing element returns
falseand does not move that element. - Removing and then re-adding an element places it at the end of the insertion sequence.
- Its hashing behavior depends on correct
equals()andhashCode()implementations. - Mutable fields used for hashing or equality can make stored elements difficult to find or remove.
- Average
add(),remove(), andcontains()operations are O(1); full iteration is O(n). - Its linked-order bookkeeping costs extra memory compared with HashSet.
- Use HashSet when order does not matter and TreeSet when sorted order is needed.
17. Conclusion
LinkedHashSet fills a practical gap in Java's Collections Framework: it provides the uniqueness of a Set while retaining the order in which unique elements first appear. Its combination of hash-table lookup and a doubly linked insertion-order list makes it well suited to recently viewed items, unique browsing histories, ordered coupon tracking, deduplication of lists, and stable test fixtures.
The most important decision is to match the collection to the required order. Use HashSet for uniqueness without an order guarantee, LinkedHashSet for insertion order, and TreeSet for sorted order. For reliable behavior, also ensure that equality and hashing are implemented consistently and that fields affecting them remain stable while elements are stored.