Java LinkedHashSet
Java LinkedHashSet
java.util.LinkedHashSet<E> solves a very specific problem that HashSet cannot: it maintains the order in which elements were inserted while still rejecting duplicates and providing O(1) contains(). When you need a collection that is simultaneously unique, fast for membership checks, and predictable in its iteration order, LinkedHashSet is the correct default.
The distinction matters in production more than beginners expect. API responses, audit logs, and deduplication pipelines where the "first seen" order of items is meaningful — these all need LinkedHashSet, not HashSet.
What Is Java LinkedHashSet?
LinkedHashSet<E> is a concrete class in java.util that extends HashSet<E> and implements Set<E>. It adds one guarantee that HashSet cannot provide: iteration always produces elements in the order they were first inserted. Duplicates are still rejected, contains() is still O(1) average, and add() is still O(1) average — the only cost over HashSet is a small memory overhead per element for the linked list that tracks insertion order.
The diagram below shows where LinkedHashSet sits in the Collections hierarchy and how it compares to the other two Set implementations.
java.lang.Iterable<E>
└── java.util.Collection<E>
└── java.util.Set<E> ← uniqueness contract
├── java.util.HashSet<E> ← hash table, NO order guarantee
│ └── LinkedHashSet ← extends HashSet, INSERTION ORDER
└── java.util.SortedSet<E>
└── NavigableSet<E>
└── TreeSet ← Red-Black tree, SORTED ORDER
KEY FACTS:
Package : java.util
Since : Java 1.4
Extends : HashSet<E>
Backed by : LinkedHashMap<E, Object> internally
Ordering : insertion order — elements iterate in the order they were first added
Null : allows exactly one null element
Duplicates : rejected — add() returns false for existing elements
Thread : NOT thread-safe
Initial capacity: 16 buckets (default)
Load factor : 0.75
The Three Set Implementations — Key Differences
FEATURE COMPARISON: Feature HashSet LinkedHashSet TreeSet ---------------------- ---------------- ------------------- ------------------ Ordering None Insertion order Natural/Comparator add() / contains() O(1) average O(1) average O(log n) Backed by HashMap LinkedHashMap TreeMap Memory per element Moderate Slightly higher Higher (tree node) Null allowed Yes (1) Yes (1) No floor/ceiling/headSet No No Yes Best for Pure speed Order + uniqueness Sorted unique set
When to Use LinkedHashSet
The decision between the three Set implementations follows a clear priority order.
CHOOSE LinkedHashSet WHEN:
1. Elements must be unique AND iteration order must match insertion order
— e.g., deduplicated list of recently visited pages, ordered unique tags
2. Producing deterministic output from a Set for display, logging, or API responses
— HashSet iteration order is undefined and can change between JVM runs
3. "First seen wins" deduplication where the position of first occurrence matters
— e.g., first unique city in a stream of user locations
CHOOSE HashSet INSTEAD WHEN:
- Order is completely irrelevant AND maximum speed is the priority
- HashSet is marginally faster than LinkedHashSet — no linked list to update
CHOOSE TreeSet INSTEAD WHEN:
- Sorted order is required (ascending or via Comparator)
- Range operations are needed: headSet(), tailSet(), floor(), ceiling()
- The cost of O(log n) per operation is acceptable for sorted access
CHOOSE ArrayList or LinkedHashMap INSTEAD WHEN:
- Duplicates are needed alongside ordered access (use ArrayList)
- Key-value pairs are needed with insertion order (use LinkedHashMap)
A pattern that appears in production code:
Use HashSet during processing (fastest contains and add),
then wrap in new LinkedHashSet<>(hashSet) when deterministic output is needed.
This costs one O(n) conversion instead of paying the LinkedHashSet overhead for
the entire processing phase.
How LinkedHashSet Works Internally
LinkedHashSet is implemented as a thin wrapper around LinkedHashMap — just like HashSet wraps HashMap. The crucial difference is the backing map.
HASHSET internals:
HashMap<E, Object> map
— hash table only, no ordering
LINKEDHASHSET internals:
LinkedHashMap<E, Object> map
— hash table PLUS a doubly-linked list threading all entries in insertion order
Every add(e) call maps to:
map.put(e, PRESENT)
LinkedHashMap.put() does two things:
1. Adds the key to the hash table bucket (same as HashMap)
2. Appends the entry to the tail of the linked list
STRUCTURE AFTER:
set.add("C"); set.add("A"); set.add("B"); set.add("A"); // duplicate
HASH TABLE:
bucket[2]: Entry("C") → null
bucket[7]: Entry("A") → null
bucket[4]: Entry("B") → null
DOUBLY-LINKED LIST (tracks insertion order):
HEAD → [Entry("C")] ↔ [Entry("A")] ↔ [Entry("B")] → null
TAIL
Iteration traverses the linked list — always C, A, B.
"A" duplicate was rejected: map.put("A", PRESENT) returned PRESENT (old value),
so add() returned false and NO new entry was appended to the linked list.
set.size() → 3 (C, A, B)
iteration → C, A, B (insertion order preserved)
The diagram below shows the full internal structure for a LinkedHashSet with four elements.
LINKEDHASHSET WITH ELEMENTS ["Mumbai", "Delhi", "Chennai", "Bengaluru"]:
HASH TABLE (array of buckets):
bucket[3]: Entry("Mumbai") → null
bucket[7]: Entry("Delhi") → null
bucket[1]: Entry("Chennai") → Entry("Bengaluru") → null (collision)
...rest: null
LINKED LIST (insertion order):
HEAD
↓
[before=HEAD | "Mumbai" | after=Delhi]
↓
[before=Mumbai | "Delhi" | after=Chennai]
↓
[before=Delhi | "Chennai" | after=Bengaluru]
↓
[before=Chennai | "Bengaluru" | after=TAIL]
↓
TAIL
Each Entry has FOUR references:
hash, key, value (PRESENT) ← HashMap fields
before, after ← LinkedHashMap linked-list fields
Iterator traverses before→after pointers — O(1) per step, O(n) total.
Result: Mumbai, Delhi, Chennai, Bengaluru — always, regardless of bucket layout.
Core Operations with Examples
add() — Insertion Order Guaranteed
1// File: LinkedHashSetAddDemo.java
2
3import java.util.HashSet;
4import java.util.LinkedHashSet;
5import java.util.Set;
6
7public class LinkedHashSetAddDemo {
8
9 public static void main(String[] args) {
10
11 LinkedHashSet<String> visitedPages = new LinkedHashSet<>();
12
13 // add() returns true for new elements, false for duplicates
14 System.out.println("=== add() return values ===");
15 System.out.println("add(/home) : " + visitedPages.add("/home")); // true
16 System.out.println("add(/products) : " + visitedPages.add("/products")); // true
17 System.out.println("add(/cart) : " + visitedPages.add("/cart")); // true
18 System.out.println("add(/home) : " + visitedPages.add("/home")); // false — duplicate
19 System.out.println("add(/checkout) : " + visitedPages.add("/checkout")); // true
20 System.out.println("add(/products) : " + visitedPages.add("/products")); // false — duplicate
21 System.out.println("add(/confirmation): " + visitedPages.add("/confirmation"));// true
22
23 System.out.println("\nInsertion order preserved: " + visitedPages);
24 System.out.println("Size (unique only): " + visitedPages.size()); // 5, not 7
25
26 System.out.println();
27
28 // Contrast with HashSet — same elements, different iteration order
29 Set<String> hashSet = new HashSet<>(visitedPages);
30 System.out.println("=== Iteration order comparison ===");
31 System.out.println("LinkedHashSet (insertion order): " + visitedPages);
32 System.out.println("HashSet (no order) : " + hashSet);
33
34 System.out.println();
35
36 // null is allowed — one null element
37 LinkedHashSet<String> withNull = new LinkedHashSet<>();
38 withNull.add("first");
39 withNull.add(null);
40 withNull.add("third");
41 withNull.add(null); // duplicate null — rejected
42 System.out.println("=== Null element ===");
43 System.out.println("Set with null: " + withNull); // [first, null, third]
44 System.out.println("null at its insertion position: index 1");
45 }
46}Output:
=== add() return values ===
add(/home) : true
add(/products) : true
add(/cart) : true
add(/home) : false — duplicate
add(/checkout) : true
add(/products) : false — duplicate
add(/confirmation): true
Insertion order preserved: [/home, /products, /cart, /checkout, /confirmation]
Size (unique only): 5
=== Iteration order comparison ===
LinkedHashSet (insertion order): [/home, /products, /cart, /checkout, /confirmation]
HashSet (no order) : [/cart, /home, /checkout, /products, /confirmation]
=== Null element ===
Set with null: [first, null, third]
null at its insertion position: index 1
contains(), remove(), and Bulk Operations
1// File: LinkedHashSetOperationsDemo.java
2
3import java.util.LinkedHashSet;
4import java.util.Set;
5
6public class LinkedHashSetOperationsDemo {
7
8 public static void main(String[] args) {
9
10 LinkedHashSet<String> activeFeatures = new LinkedHashSet<>();
11 activeFeatures.add("DarkMode"); activeFeatures.add("BetaSearch");
12 activeFeatures.add("NewCheckout"); activeFeatures.add("LiveTracking");
13 activeFeatures.add("AIRecommendations");
14
15 System.out.println("=== contains() — O(1) average ===");
16 System.out.println("contains(DarkMode) : " + activeFeatures.contains("DarkMode")); // true
17 System.out.println("contains(ChatSupport): " + activeFeatures.contains("ChatSupport")); // false
18
19 System.out.println("\n=== remove() — preserves order of remaining ===");
20 System.out.println("Before : " + activeFeatures);
21 activeFeatures.remove("BetaSearch");
22 System.out.println("After remove(BetaSearch): " + activeFeatures);
23 // Order of remaining elements is unchanged
24
25 System.out.println("\n=== removeIf() — insertion order preserved ===");
26 activeFeatures.removeIf(feature -> feature.startsWith("New"));
27 System.out.println("After removeIf(starts with New): " + activeFeatures);
28
29 System.out.println();
30
31 // Set operations — intersection, difference, union — order preserved on result
32 LinkedHashSet<String> userPermissions = new LinkedHashSet<>(
33 Set.of("READ", "WRITE", "DELETE", "ADMIN", "EXPORT")
34 );
35 LinkedHashSet<String> rolePermissions = new LinkedHashSet<>(
36 Set.of("READ", "WRITE", "COMMENT")
37 );
38
39 // Intersection — what both sets share
40 LinkedHashSet<String> granted = new LinkedHashSet<>(userPermissions);
41 granted.retainAll(rolePermissions); // keeps only elements in BOTH
42 System.out.println("=== Set operations ===");
43 System.out.println("User permissions: " + userPermissions);
44 System.out.println("Role permissions: " + rolePermissions);
45 System.out.println("Granted (intersection): " + granted);
46
47 // Difference — permissions user has but role does not
48 LinkedHashSet<String> exclusive = new LinkedHashSet<>(userPermissions);
49 exclusive.removeAll(rolePermissions);
50 System.out.println("Exclusive to user : " + exclusive);
51
52 // Union — all permissions
53 LinkedHashSet<String> allPerms = new LinkedHashSet<>(userPermissions);
54 allPerms.addAll(rolePermissions); // duplicates ignored
55 System.out.println("Union (all) : " + allPerms);
56 }
57}Output:
=== contains() — O(1) average ===
contains(DarkMode) : true
contains(ChatSupport): false
=== remove() — preserves order of remaining ===
Before : [DarkMode, BetaSearch, NewCheckout, LiveTracking, AIRecommendations]
After remove(BetaSearch): [DarkMode, NewCheckout, LiveTracking, AIRecommendations]
=== removeIf() — insertion order preserved ===
After removeIf(starts with New): [DarkMode, LiveTracking, AIRecommendations]
=== Set operations ===
User permissions: [READ, WRITE, DELETE, ADMIN, EXPORT]
Role permissions: [READ, WRITE, COMMENT]
Granted (intersection): [READ, WRITE]
Exclusive to user : [DELETE, ADMIN, EXPORT]
Union (all) : [READ, WRITE, DELETE, ADMIN, EXPORT, COMMENT]
Deduplication While Preserving First-Occurrence Order
This is the most common production use case for LinkedHashSet — converting a list with duplicates into a list of unique elements in the order they first appeared.
1// File: LinkedHashSetDeduplicationDemo.java
2
3import java.util.ArrayList;
4import java.util.Arrays;
5import java.util.LinkedHashSet;
6import java.util.List;
7
8public class LinkedHashSetDeduplicationDemo {
9
10 public static void main(String[] args) {
11
12 // Deduplication — first occurrence order preserved
13 List<String> rawSearchTerms = Arrays.asList(
14 "laptop", "laptop bag", "laptop", "laptop stand",
15 "laptop cooling pad", "laptop bag", "laptop", "laptop sleeve"
16 );
17 System.out.println("Raw (with duplicates): " + rawSearchTerms);
18
19 // new LinkedHashSet<>(list) — O(n) pass, first occurrences in order
20 LinkedHashSet<String> uniqueTerms = new LinkedHashSet<>(rawSearchTerms);
21 System.out.println("Deduplicated (first-seen order): " + uniqueTerms);
22
23 // Convert back to List if needed
24 List<String> deduplicatedList = new ArrayList<>(uniqueTerms);
25 System.out.println("Back to List: " + deduplicatedList);
26
27 System.out.println();
28
29 // Comparison: HashSet deduplication loses order
30 java.util.HashSet<String> hashDedup = new java.util.HashSet<>(rawSearchTerms);
31 System.out.println("HashSet dedup (no order) : " + hashDedup);
32 System.out.println("LinkedHashSet dedup (ordered) : " + uniqueTerms);
33
34 System.out.println();
35
36 // Streaming deduplication with Collectors.toCollection
37 List<Integer> scores = Arrays.asList(92, 85, 92, 78, 85, 100, 78, 95);
38 System.out.println("Raw scores: " + scores);
39
40 LinkedHashSet<Integer> uniqueScores = scores.stream()
41 .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
42 System.out.println("Unique scores (first-seen order): " + uniqueScores);
43
44 // distinct() in streams produces encounter order (source order for List)
45 List<Integer> distinctList = scores.stream()
46 .distinct()
47 .collect(java.util.stream.Collectors.toList());
48 System.out.println("Stream distinct (same result) : " + distinctList);
49 }
50}Output:
Raw (with duplicates): [laptop, laptop bag, laptop, laptop stand, laptop cooling pad, laptop bag, laptop, laptop sleeve]
Deduplicated (first-seen order): [laptop, laptop bag, laptop stand, laptop cooling pad, laptop sleeve]
Back to List: [laptop, laptop bag, laptop stand, laptop cooling pad, laptop sleeve]
HashSet dedup (no order) : [laptop stand, laptop bag, laptop, laptop cooling pad, laptop sleeve]
LinkedHashSet dedup (ordered) : [laptop, laptop bag, laptop stand, laptop cooling pad, laptop sleeve]
Raw scores: [92, 85, 92, 78, 85, 100, 78, 95]
Unique scores (first-seen order): [92, 85, 78, 100, 95]
Stream distinct (same result) : [92, 85, 78, 100, 95]
Real-World Example — Razorpay Payment Method Selector
A payment gateway like Razorpay tracks which payment methods a user has used, in the order they were first used. The most recently used methods should appear at the top of the selector — but each method must appear only once, and the order must reflect when the user first used each method. LinkedHashSet solves this in a single data structure.
1// File: PaymentMethod.java
2
3import java.util.Objects;
4
5public class PaymentMethod {
6
7 private final String methodId;
8 private final String type; // UPI, CARD, NETBANKING, WALLET
9 private final String label; // display name
10 private final String provider; // GPay, PhonePe, HDFC, etc.
11
12 public PaymentMethod(String methodId, String type, String label, String provider) {
13 this.methodId = methodId;
14 this.type = type;
15 this.label = label;
16 this.provider = provider;
17 }
18
19 public String getMethodId() { return methodId; }
20 public String getType() { return type; }
21 public String getLabel() { return label; }
22 public String getProvider() { return provider; }
23
24 // Identity is methodId — same payment method regardless of display label
25 @Override
26 public boolean equals(Object obj) {
27 if (!(obj instanceof PaymentMethod other)) return false;
28 return Objects.equals(this.methodId, other.methodId);
29 }
30
31 @Override
32 public int hashCode() {
33 return Objects.hash(methodId);
34 }
35
36 @Override
37 public String toString() {
38 return String.format("%-12s %-10s %s", type, provider, label);
39 }
40}1// File: PaymentMethodSelector.java
2
3import java.util.ArrayList;
4import java.util.LinkedHashSet;
5import java.util.List;
6
7public class PaymentMethodSelector {
8
9 // LinkedHashSet: unique methods, insertion order = order first used
10 private final LinkedHashSet<PaymentMethod> usedMethods = new LinkedHashSet<>();
11
12 // All available methods in the system
13 private final List<PaymentMethod> allMethods = new ArrayList<>();
14
15 public void registerAvailableMethod(PaymentMethod method) {
16 allMethods.add(method);
17 }
18
19 // Called when user successfully pays with a method
20 public void recordUsage(PaymentMethod method) {
21 boolean isNew = usedMethods.add(method);
22 System.out.println(" " + (isNew ? "FIRST USE " : "REPEAT USE ") + ": " + method);
23 }
24
25 // Returns payment options: recently used first, then remaining available
26 public List<PaymentMethod> getOrderedOptions() {
27 List<PaymentMethod> options = new ArrayList<>(usedMethods); // used methods first
28
29 // Append available methods not yet used
30 for (PaymentMethod available : allMethods) {
31 if (!usedMethods.contains(available)) { // O(1) check
32 options.add(available);
33 }
34 }
35 return options;
36 }
37
38 public void displaySelector() {
39 System.out.println("\n" + "=".repeat(58));
40 System.out.println(" RAZORPAY — SELECT PAYMENT METHOD");
41 System.out.println("=".repeat(58));
42 System.out.println(" -- Recently Used --");
43 int i = 1;
44 for (PaymentMethod m : usedMethods) {
45 System.out.printf(" %d. %s%n", i++, m);
46 }
47 System.out.println(" -- Other Options --");
48 for (PaymentMethod m : allMethods) {
49 if (!usedMethods.contains(m)) {
50 System.out.printf(" %d. %s%n", i++, m);
51 }
52 }
53 System.out.println("=".repeat(58));
54 }
55
56 public static void main(String[] args) {
57
58 PaymentMethodSelector selector = new PaymentMethodSelector();
59
60 // Register all available payment methods
61 PaymentMethod gpay = new PaymentMethod("UPI-GPAY", "UPI", "Google Pay", "GPay");
62 PaymentMethod phonepe = new PaymentMethod("UPI-PPE", "UPI", "PhonePe", "PhonePe");
63 PaymentMethod hdfc = new PaymentMethod("CARD-HDFC", "CARD", "HDFC Debit", "HDFC");
64 PaymentMethod icici = new PaymentMethod("CARD-ICICI", "CARD", "ICICI Credit", "ICICI");
65 PaymentMethod paytm = new PaymentMethod("WALLET-PTM", "WALLET", "Paytm Wallet", "Paytm");
66 PaymentMethod netbank = new PaymentMethod("NB-SBI", "NETBANKING", "SBI NetBanking","SBI");
67
68 selector.registerAvailableMethod(gpay);
69 selector.registerAvailableMethod(phonepe);
70 selector.registerAvailableMethod(hdfc);
71 selector.registerAvailableMethod(icici);
72 selector.registerAvailableMethod(paytm);
73 selector.registerAvailableMethod(netbank);
74
75 System.out.println("--- User transaction history ---");
76 selector.recordUsage(hdfc); // first use — added to LinkedHashSet
77 selector.recordUsage(gpay); // first use
78 selector.recordUsage(hdfc); // repeat use — LinkedHashSet rejects, position unchanged
79 selector.recordUsage(paytm); // first use
80 selector.recordUsage(gpay); // repeat use — rejected, GPay stays at position 2
81 selector.recordUsage(hdfc); // repeat use again
82
83 selector.displaySelector();
84
85 System.out.println("\n--- User adds new method ---");
86 selector.recordUsage(icici); // first use — appended after paytm
87 selector.displaySelector();
88 }
89}Output:
--- User transaction history ---
FIRST USE : CARD HDFC HDFC Debit
FIRST USE : UPI GPay Google Pay
REPEAT USE : CARD HDFC HDFC Debit
FIRST USE : WALLET Paytm Paytm Wallet
REPEAT USE : UPI GPay Google Pay
REPEAT USE : CARD HDFC HDFC Debit
==========================================================
RAZORPAY — SELECT PAYMENT METHOD
==========================================================
-- Recently Used --
1. CARD HDFC HDFC Debit
2. UPI GPay Google Pay
3. WALLET Paytm Paytm Wallet
-- Other Options --
4. UPI PhonePe PhonePe
5. CARD ICICI ICICI Credit
6. NETBANKING SBI SBI NetBanking
==========================================================
--- User adds new method ---
FIRST USE : CARD ICICI ICICI Credit
==========================================================
RAZORPAY — SELECT PAYMENT METHOD
==========================================================
-- Recently Used --
1. CARD HDFC HDFC Debit
2. UPI GPay Google Pay
3. WALLET Paytm Paytm Wallet
4. CARD ICICI ICICI Credit
-- Other Options --
5. UPI PhonePe PhonePe
6. NETBANKING SBI SBI NetBanking
==========================================================
LinkedHashSet handles the two requirements simultaneously: no duplicate payment method can appear in the "Recently Used" list, and methods appear in exactly the order they were first used. A HashSet would solve uniqueness but break the ordering. An ArrayList would preserve order but require manual duplicate checking.
Performance Considerations
| Operation | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| add(e) | O(1) avg | O(1) avg | O(log n) |
| remove(e) | O(1) avg | O(1) avg | O(log n) |
| contains(e) | O(1) avg | O(1) avg | O(log n) |
| Iteration | O(n + capacity) | O(n) | O(n) |
| Memory per element | ~48 bytes (Node) | ~64 bytes (Node + 2 pointers) | ~56 bytes (TreeNode) |
| first() / last() | Not available | Not available | O(log n) |
| floor() / ceiling() | Not available | Not available | O(log n) |
Iteration difference: LinkedHashSet iterates in O(n) by following linked-list before/after pointers — it does not scan empty buckets. HashSet iterates in O(n + capacity) because it scans all bucket slots including empty ones. For a HashSet pre-sized to 10,000 that holds 5 elements, iteration touches 10,000 slots. LinkedHashSet touches only the 5 elements.
Memory cost: Each entry in LinkedHashSet carries two extra references compared to HashSet — the before and after pointers for the doubly-linked list. On a 64-bit JVM with compressed references, this is approximately 8 extra bytes per entry. For most applications this is negligible. For extremely large sets in memory-constrained environments, HashSet is the leaner choice.
Thread safety: LinkedHashSet is not thread-safe. For concurrent ordered-set access, there is no direct concurrent equivalent. The common workaround is Collections.synchronizedSet(new LinkedHashSet<>()) or, for high-concurrency, use ConcurrentHashMap.newKeySet() (which provides no ordering) and manage ordering separately.
Best Practices
Declare the variable as Set<E> or LinkedHashSet<E> based on whether ordered iteration is part of the contract. If a method signature uses Set<E>, callers can pass any Set implementation. If ordered iteration is required by the caller — for display, for deterministic test output, for serialisation — declare LinkedHashSet<E> explicitly so the ordering guarantee is visible in the API. Using Set<E> for a variable that must preserve insertion order hides a contract that callers may rely on.
Use new LinkedHashSet<>(collection) for order-preserving deduplication. It processes the entire collection in one O(n) pass, preserves the first occurrence of each element, and is cleaner than manual loop-based deduplication. new ArrayList<>(new LinkedHashSet<>(list)) is the idiomatic one-liner for converting a list with duplicates to a deduplicated list in first-occurrence order.
Prefer LinkedHashSet over HashSet when writing unit tests that compare set contents. Tests that assert assertEquals(expectedSet, actualSet) with HashSet may produce confusing failure messages — the order of elements in the string representation is non-deterministic. LinkedHashSet produces consistent string output, making test failures readable. Wrap the actual result in new LinkedHashSet<>(actual) in assertions where order matters.
Do not use LinkedHashSet as a substitute for List when duplicates are intentional. LinkedHashSet is a Set — it rejects duplicates. If the business logic needs to count occurrences, maintain position including duplicates, or allow the same element at multiple positions, use a List. LinkedHashSet is only correct when uniqueness and ordered iteration are both required simultaneously.
Common Mistakes
Mistake 1 — Expecting Re-insertion to Update Position
1LinkedHashSet<String> recentItems = new LinkedHashSet<>();
2recentItems.add("Item-A"); // position 0
3recentItems.add("Item-B"); // position 1
4recentItems.add("Item-C"); // position 2
5
6// WRONG assumption: re-adding "Item-A" moves it to the end
7recentItems.add("Item-A"); // add() returns false — Item-A stays at position 0!
8
9System.out.println(recentItems); // [Item-A, Item-B, Item-C] — unchanged
10
11// CORRECT for LRU-style "move to end on re-access":
12recentItems.remove("Item-A");
13recentItems.add("Item-A"); // now at position 2 (end)
14System.out.println(recentItems); // [Item-B, Item-C, Item-A]Mistake 2 — Using LinkedHashSet When TreeSet Is Needed for Sorted Order
1// WRONG for sorted output — LinkedHashSet preserves insertion order, NOT sorted order
2LinkedHashSet<String> cities = new LinkedHashSet<>();
3cities.add("Pune"); cities.add("Mumbai"); cities.add("Ahmedabad");
4System.out.println("LinkedHashSet: " + cities); // [Pune, Mumbai, Ahmedabad] — insertion order
5
6// CORRECT for alphabetically sorted unique cities
7java.util.TreeSet<String> sortedCities = new java.util.TreeSet<>();
8sortedCities.add("Pune"); sortedCities.add("Mumbai"); sortedCities.add("Ahmedabad");
9System.out.println("TreeSet : " + sortedCities); // [Ahmedabad, Mumbai, Pune]Mistake 3 — Mutating an Element's hashCode-Relevant Field After Insertion
1import java.util.LinkedHashSet;
2import java.util.Objects;
3
4// Same contract as HashSet — mutating a field used in hashCode() loses the element
5class Tag {
6 String name;
7 Tag(String n) { this.name = n; }
8
9 @Override public boolean equals(Object o) { return o instanceof Tag t && name.equals(t.name); }
10 @Override public int hashCode() { return Objects.hash(name); }
11 @Override public String toString() { return name; }
12}
13
14LinkedHashSet<Tag> tags = new LinkedHashSet<>();
15Tag t = new Tag("java");
16tags.add(t);
17
18t.name = "kotlin"; // bucket index changes — element lost in wrong bucket
19
20System.out.println(tags.contains(t)); // false — t now hashes to a different bucket
21System.out.println(tags.size()); // 1 — entry exists but is unreachable
22// Use immutable fields in equals/hashCode to avoid thisMistake 4 — Using LinkedHashSet for Thread-Safe Ordered Sets Without Synchronisation
1// WRONG — LinkedHashSet is not thread-safe
2LinkedHashSet<String> sharedSet = new LinkedHashSet<>();
3// Thread 1 adds elements, Thread 2 iterates — can corrupt the linked list
4
5// CORRECT — synchronise external to the set
6Set<String> syncSet = java.util.Collections.synchronizedSet(new LinkedHashSet<>());
7// External synchronisation required during iteration:
8synchronized (syncSet) {
9 for (String item : syncSet) {
10 process(item);
11 }
12}Interview Questions
Q1. What is LinkedHashSet in Java and how does it differ from HashSet?
LinkedHashSet extends HashSet and adds one guarantee: iteration always produces elements in the order they were inserted. HashSet makes no ordering guarantee — elements can iterate in any order, and that order may change after rehashing. Internally, LinkedHashSet is backed by LinkedHashMap instead of HashMap. LinkedHashMap maintains a doubly-linked list threading all entries in insertion order alongside the hash table. This linked list is what LinkedHashSet's iterator traverses, giving O(n) iteration with predictable order. Both HashSet and LinkedHashSet provide O(1) average add(), remove(), and contains().
Q2. What is the internal structure of LinkedHashSet?
LinkedHashSet is backed by a LinkedHashMap<E, Object> where each element becomes a key and a shared static PRESENT dummy object is the value. LinkedHashMap extends HashMap and maintains a doubly-linked list of all entries using before and after pointers on each Entry. When add(element) is called, LinkedHashMap.put(element, PRESENT) adds the entry to both the hash table bucket and the tail of the doubly-linked list. Iteration follows the linked list from head to tail, producing insertion order. Removal updates both the hash table and the linked list pointers in O(1).
Q3. Does re-inserting an existing element change its position in LinkedHashSet?
No. add(element) on an already-present element returns false and makes no structural change — the element stays at its original insertion position. If you need the element to move to the end (as in an LRU-style structure), you must explicitly remove(element) first and then add(element) again. This is a common misconception and a common interview follow-up question — the answer is: LinkedHashSet preserves the position of the first insertion, not the most recent access.
Q4. Why does LinkedHashSet iterate faster than HashSet for full traversals?
HashSet iteration is O(n + capacity) because it scans all bucket slots in the backing HashMap array, including empty slots. For a HashSet with capacity 10,000 but only 5 elements, iteration examines 10,005 slots. LinkedHashSet iteration is O(n) because it follows the doubly-linked list pointers — it touches only the n live entries, skipping all empty buckets entirely. For sets with low load relative to capacity, LinkedHashSet's iterator is measurably faster despite the slightly higher per-entry memory cost.
Q5. When would you choose LinkedHashSet over a List for storing unique ordered elements?
Use LinkedHashSet when contains() or remove() is called frequently and the number of elements is large enough that O(n) list-scan would be slow. LinkedHashSet.contains() is O(1); ArrayList.contains() is O(n). For displaying a deduplicated stream of events in arrival order, or checking membership in a "visited" set while maintaining traversal order, LinkedHashSet handles both in one data structure. Use ArrayList when duplicates are intentional or when index-based access (get(i)) is needed — LinkedHashSet has no get(index) method.
Q6. How does LinkedHashSet handle the equals() and hashCode() contract?
Identically to HashSet — because it inherits the same HashMap-based lookup mechanism through LinkedHashMap. Two elements that are equal by equals() must produce the same hashCode(). If hashCode() is not overridden alongside equals(), two logically equal objects land in different buckets, and the set stores both — silently violating uniqueness. The linked list in LinkedHashMap does not help with this — it only tracks insertion order of keys that the hash table has already accepted. Correct equals() and hashCode() implementation is as critical for LinkedHashSet as it is for HashSet.
FAQs
Does LinkedHashSet maintain sorted order?
No. LinkedHashSet maintains insertion order — the order elements were first added. For sorted order (alphabetical or numeric), use TreeSet. For insertion order with uniqueness, use LinkedHashSet. These are distinct guarantees. A LinkedHashSet populated with {"B", "A", "C"} iterates as B, A, C. A TreeSet with the same elements iterates as A, B, C.
How do you convert a List with duplicates to a List with unique elements in first-occurrence order?
new ArrayList<>(new LinkedHashSet<>(originalList)) is the idiomatic one-liner. new LinkedHashSet<>(originalList) deduplicates in one O(n) pass, preserving first-occurrence order. Wrapping in new ArrayList<>() gives back a list with index access. Alternatively, originalList.stream().distinct().collect(Collectors.toList()) achieves the same result for List sources.
What is the memory difference between HashSet and LinkedHashSet?
Each entry in LinkedHashSet carries two additional references compared to HashSet: the before and after pointers for the doubly-linked list. On a 64-bit JVM with compressed references, this adds approximately 8 bytes per entry. For a set of 1,000,000 elements, the overhead difference is roughly 8 MB. For most applications this is negligible; for memory-constrained services with very large sets, HashSet is the leaner choice.
Is LinkedHashSet thread-safe?
No. LinkedHashSet is not thread-safe. Concurrent modification from multiple threads can corrupt both the hash table and the doubly-linked list. For thread-safe ordered set access, wrap it: Collections.synchronizedSet(new LinkedHashSet<>()). All iteration must be done inside a synchronized block on the wrapper. For high-concurrency scenarios, there is no direct concurrent LinkedHashSet equivalent — consider ConcurrentHashMap.newKeySet() (unordered, faster) or managing a separate ordering structure alongside a concurrent set.
Can LinkedHashSet be used as a queue with unique elements?
Yes — this is a practical use case. LinkedHashSet supports add() at the tail (end), iteration in insertion order, and iterator.remove() for removal during traversal. For a queue where each item should be processed at most once, LinkedHashSet prevents re-queuing the same item and guarantees FIFO processing order. To remove and process the first element: Iterator<E> it = set.iterator(); E first = it.next(); it.remove();. For high-throughput concurrent scenarios, LinkedBlockingQueue combined with a Set is more appropriate.
How does LinkedHashSet compare to a List for tracking visited items in order?
A List preserves insertion order and allows contains(), but contains() is O(n) — scanning every element. For a visited-items tracker that checks membership on every incoming item, O(n) per check produces O(n²) total. LinkedHashSet.contains() is O(1) average. Use LinkedHashSet when both "have I seen this before?" checks and ordered traversal are needed on the same collection. Use a List only when duplicates are intentional or index-based access is required.
Summary
LinkedHashSet<E> is the correct Set implementation when two requirements must be satisfied simultaneously: no duplicate elements and a predictable iteration order that matches insertion sequence. Backed by LinkedHashMap, it maintains a doubly-linked list of all entries alongside the hash table. add(), remove(), and contains() are O(1) average — identical to HashSet. The only cost is slightly more memory per entry and a marginally slower add() due to linked-list pointer maintenance.
The canonical use case: order-preserving deduplication. new LinkedHashSet<>(list) deduplicates a list in one pass, keeping the first occurrence of each element in position. new ArrayList<>(new LinkedHashSet<>(list)) gets back a list.
For interviews: explain the LinkedHashMap backing and the doubly-linked before/after pointers, clarify that re-insertion does not move an element's position, describe the O(n) iteration advantage over HashSet due to linked-list traversal versus bucket scanning, and distinguish insertion order (LinkedHashSet) from sorted order (TreeSet).
What to Read Next
| Topic | Link |
|---|---|
| How HashSet provides the faster unordered alternative that LinkedHashSet extends | Java HashSet → |
| How LinkedHashMap provides the insertion-order hash table that backs LinkedHashSet | Java HashMap → |
| How TreeSet provides sorted unique elements as an alternative to LinkedHashSet | Java TreeMap → |
| How the Collections Framework organises all Set implementations in one hierarchy | Java Collections Framework → |
| How Generics make LinkedHashSet type-safe for any element type at compile time | Java Generics → |