Skip to content
Java collections 6 min read

LinkedHashSet

LinkedHashSet gives you the best of two worlds: the fast duplicate-rejection of a hash set and a predictable iteration order that matches the order you inserted elements. If you have ever been annoyed that HashSet shuffles your elements in a seemingly random order, LinkedHashSet is the fix.

What is LinkedHashSet?

LinkedHashSet<E> is part of java.util and extends HashSet. It implements the Set interface, so all the familiar rules still apply — no duplicate elements, no positional access. The key difference is that it maintains a doubly-linked list running through its entries in insertion order.

import java.util.LinkedHashSet;

public class LinkedHashSetBasics {
    public static void main(String[] args) {
        LinkedHashSet<String> languages = new LinkedHashSet<>();
        languages.add("Java");
        languages.add("Python");
        languages.add("Go");
        languages.add("Java"); // duplicate — silently ignored

        System.out.println(languages);
        System.out.println("Size: " + languages.size());
    }
}

Output:

[Java, Python, Go]
Size: 3

Notice that the output order is exactly the insertion order, and the duplicate "Java" was rejected without throwing any exception.

Creating a LinkedHashSet

You can construct a LinkedHashSet in several ways:

import java.util.LinkedHashSet;
import java.util.List;

public class CreatingLinkedHashSet {
    public static void main(String[] args) {
        // 1. Default capacity (16) and load factor (0.75)
        LinkedHashSet<Integer> set1 = new LinkedHashSet<>();

        // 2. With an initial capacity hint
        LinkedHashSet<Integer> set2 = new LinkedHashSet<>(32);

        // 3. With initial capacity and load factor
        LinkedHashSet<Integer> set3 = new LinkedHashSet<>(32, 0.9f);

        // 4. Copy from another collection (preserves iteration order)
        List<String> list = List.of("red", "green", "blue", "red");
        LinkedHashSet<String> set4 = new LinkedHashSet<>(list);
        System.out.println(set4); // [red, green, blue]
    }
}

Output:

[red, green, blue]

Tip: Initializing from a List is a popular one-liner for deduplicating a list while keeping its order — something neither a plain HashSet nor sorting can guarantee simultaneously.

Common Methods

LinkedHashSet inherits all its methods from HashSet and ultimately from Collection. Here are the ones you will use most often:

MethodDescription
add(E e)Adds element; returns false if already present
remove(Object o)Removes the element; returns false if absent
contains(Object o)Returns true if the element exists
size()Number of unique elements
isEmpty()true when the set has no elements
clear()Removes all elements
iterator()Returns an iterator in insertion order
addAll(Collection c)Adds all elements from another collection (union)
retainAll(Collection c)Keeps only elements that appear in c (intersection)
removeAll(Collection c)Removes all elements that appear in c (difference)
import java.util.LinkedHashSet;

public class LinkedHashSetMethods {
    public static void main(String[] args) {
        LinkedHashSet<String> steps = new LinkedHashSet<>();
        steps.add("Clone");
        steps.add("Build");
        steps.add("Test");
        steps.add("Deploy");

        System.out.println("Contains 'Test': " + steps.contains("Test"));
        steps.remove("Build");
        System.out.println("After remove: " + steps);
        System.out.println("Size: " + steps.size());
    }
}

Output:

Contains 'Test': true
After remove: [Clone, Test, Deploy]
Size: 3

Iterating Over a LinkedHashSet

Because the linked list preserves insertion order, iteration is fully predictable — unlike HashSet.

import java.util.Iterator;
import java.util.LinkedHashSet;

public class IteratingLinkedHashSet {
    public static void main(String[] args) {
        LinkedHashSet<String> days = new LinkedHashSet<>();
        days.add("Monday");
        days.add("Tuesday");
        days.add("Wednesday");

        // for-each (most common)
        for (String day : days) {
            System.out.println(day);
        }

        // Iterator (needed if you want to remove during traversal)
        Iterator<String> it = days.iterator();
        while (it.hasNext()) {
            String d = it.next();
            if (d.equals("Tuesday")) {
                it.remove(); // safe removal
            }
        }
        System.out.println("After removal: " + days);
    }
}

Output:

Monday
Tuesday
Wednesday
After removal: [Monday, Wednesday]

Warning: Never call set.remove() directly while iterating with a for-each loop — it will throw ConcurrentModificationException. Always use iterator.remove() instead.

Practical Example: Deduplicating a List While Keeping Order

A classic real-world use case — imagine processing user-submitted tags where duplicates may appear but order matters for display:

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;

public class DeduplicateList {
    public static void main(String[] args) {
        List<String> rawTags = Arrays.asList(
            "java", "oop", "java", "collections", "oop", "generics"
        );

        // Deduplicate, preserving first-seen order
        LinkedHashSet<String> uniqueTags = new LinkedHashSet<>(rawTags);
        System.out.println(uniqueTags);
    }
}

Output:

[java, oop, collections, generics]

LinkedHashSet with Custom Objects

When you store custom objects, you must override both equals() and hashCode(). Without them, two logically identical objects will be treated as distinct because Object’s default implementations compare by memory address.

import java.util.LinkedHashSet;
import java.util.Objects;

class Student {
    String name;
    int id;

    Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Student s)) return false;
        return id == s.id && Objects.equals(name, s.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }

    @Override
    public String toString() {
        return name + "(" + id + ")";
    }
}

public class LinkedHashSetCustom {
    public static void main(String[] args) {
        LinkedHashSet<Student> roster = new LinkedHashSet<>();
        roster.add(new Student("Alice", 1));
        roster.add(new Student("Bob", 2));
        roster.add(new Student("Alice", 1)); // duplicate — rejected

        System.out.println(roster);
    }
}

Output:

[Alice(1), Bob(2)]

Note: See equals() and hashCode() for the full contract and why both methods must always be overridden together.

LinkedHashSet vs HashSet vs TreeSet

Picking the right Set implementation is straightforward once you know what each one prioritises:

FeatureHashSetLinkedHashSetTreeSet
Iteration orderRandom (unpredictable)Insertion orderSorted (natural / Comparator)
Allows null?One nullOne nullNo null
Performance (add/contains/remove)O(1) avgO(1) avg (tiny overhead)O(log n)
Memory overheadLowestSmall (extra linked-list pointers)Higher (tree nodes)
Extra navigational methods?NoNoYes (first(), headSet(), …)

Use LinkedHashSet when:

  • You need uniqueness and a predictable iteration order.
  • You are deduplicating a list and must preserve the original encounter order.
  • You are building a cache or LRU-like structure where insertion order carries meaning.

Use HashSet when order does not matter and you want the absolute minimum memory footprint.

Use TreeSet when you need elements sorted rather than ordered by insertion.

Under the Hood

Internally, LinkedHashSet does almost nothing itself — its entire body is a single constructor that forwards to LinkedHashMap. All the real work lives in LinkedHashMap, which extends HashMap and adds one extra pair of pointers per entry (before and after) that form a doubly-linked list across all map entries in insertion order.

When you call add("Java"):

  1. The element’s hashCode() is computed and used to find the correct bucket in the backing LinkedHashMap.
  2. If a key equal to "Java" already exists, the add is a no-op and false is returned.
  3. Otherwise a new LinkedHashMap.Entry is created, placed in the bucket, and appended to the tail of the linked list.

When you iterate, the iterator walks the linked list — not the hash table buckets — so you always see elements in insertion order regardless of hash distribution.

Memory cost: each entry carries two extra object references compared to a plain HashMap entry. For millions of entries this can add up; if order is truly irrelevant, prefer HashSet.

Thread safety: LinkedHashSet is not thread-safe. If multiple threads access it concurrently and at least one modifies it, synchronize externally or wrap it:

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

Set<String> safeSet = Collections.synchronizedSet(new LinkedHashSet<>());

For high-concurrency scenarios, consider the concurrent collections like ConcurrentHashMap.newKeySet() — though that one does not preserve insertion order.

  • HashSet — the fastest Set when you don’t need a guaranteed iteration order
  • TreeSetSet that keeps elements in sorted order with rich navigational methods
  • Set Interface — the contract all Set implementations share
  • HashMap — the map sibling that HashSet (and LinkedHashSet) is built on
  • LinkedHashMap — the underlying map that powers LinkedHashSet’s ordering
  • Comparable — how TreeSet sorts custom objects naturally
Last updated June 13, 2026
Was this helpful?