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

PropertyDescription
Main purposeStore unique elements in insertion order
InterfaceImplements Set<E>
Duplicate elementsNot permitted
Iteration orderOrder of first insertion
Average add, remove, containsO(1), assuming well-distributed hash codes
IterationO(n) in the number of elements
Null valuesAllows one null element
Thread safetyNot synchronized by default
Main trade-offUses 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:

  1. Add "Notebook" — it becomes the first element.
  2. Add "Pen" — it becomes the second element.
  3. Add "Eraser" — it becomes the third element.
  4. Add "Pen" again — no new element is added, and its position does not change.
  5. 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.

Whiteboard
Loading diagram...


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.

FeatureHashSetLinkedHashSet
DuplicatesRejectedRejected
Iteration orderNot guaranteedInsertion order
Average lookup and updateO(1)O(1)
Extra order bookkeepingNo insertion-order listDoubly linked list
Memory useUsually lowerUsually higher
Best fitOrder does not matterOriginal 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:

  1. If a.equals(b) is true, then a.hashCode() and b.hashCode() must be equal.
  2. The hash code should remain stable while the fields used to determine equality remain unchanged.
  3. 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.

Whiteboard
Loading diagram...


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 final when the design permits it.
  • Do not modify fields used by equals() or hashCode() 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.

Whiteboard
Loading diagram...


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.

ConstructorPurpose
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

OperationTypical complexityExplanation
add(element)O(1) averageHashing locates a likely bucket; insertion also updates order links
contains(element)O(1) averageSearches the hash-table structure
remove(element)O(1) averageFinds the entry and unlinks it from the order list
Iterate through all elementsO(n)Follows the linked list once
Resize and redistribute entriesO(n) for the resize workExisting 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

PropertyHashSetLinkedHashSetTreeSet
DuplicatesNot allowedNot allowedNot allowed under its ordering comparison
Iteration orderNo guaranteed orderInsertion orderSorted order
Typical add, remove, containsO(1) averageO(1) averageO(log n)
Main backing structureHash table / HashMapHash table plus linked order / LinkedHashMapBalanced search tree / TreeMap
Relative memoryUsually lowest of theseHigher than HashSetTree-node overhead
Null handlingAllows one nullAllows one nullGenerally does not accept null with natural ordering; comparator behavior can vary
Best useUniqueness without order needsUniqueness with insertion orderUniqueness 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() without hashCode(): 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

  1. Select LinkedHashSet only when preserving first-insertion order is part of the requirement.
  2. Implement equals() and hashCode() together for value-based objects.
  3. Keep equality-defining fields stable while elements are stored in the set.
  4. Use add()'s boolean result to detect whether an element was new.
  5. Use the collection constructor to deduplicate existing ordered data while preserving first occurrences.
  6. Provide an appropriate initial capacity when a large expected size is known and performance measurements justify it.
  7. Choose TreeSet instead when sorted iteration is required.
  8. 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 false and 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() and hashCode() implementations.
  • Mutable fields used for hashing or equality can make stored elements difficult to find or remove.
  • Average add(), remove(), and contains() 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.