Skip to main content

LinkedHashSet<E>

LinkedHashSet<E> is the default Set implementation in Dart. When you write var s = {'a', 'b', 'c'}, you get a LinkedHashSet. It combines a hash table for O(1) operations with a doubly-linked list to maintain insertion order.


When to Use

✅ Use LinkedHashSet<E> when you need:

  • Unique elements with predictable insertion-order iteration
  • Fast O(1) membership testing
  • The general-purpose set (almost all use cases)
  • Ordered deduplication

❌ Don't use LinkedHashSet<E> when you need:

  • Maximum speed (no ordering overhead) → use HashSet
  • Sorted elements → use SplayTreeSet
  • Index-based access → use List<E>

Key Insight: Default Set

var s = {'a', 'b', 'c'};

import 'dart:collection';
print(s is LinkedHashSet); // true

The Set literal and Set() factory both create LinkedHashSet.


Import

import 'dart:collection'; // needed for explicit LinkedHashSet type

Constructors

LinkedHashSet() (default)

Creates empty, using == and hashCode.

import 'dart:collection';

var s = LinkedHashSet<String>();
s.add('banana');
s.add('apple');
s.add('cherry');
s.add('apple'); // duplicate — ignored

print(s.toList()); // [banana, apple, cherry] — insertion order preserved!

LinkedHashSet({equals, hashCode, isValidKey})

Custom equality — identical signature to HashSet.

import 'dart:collection';

var caseInsensitive = LinkedHashSet<String>(
equals: (a, b) => a.toLowerCase() == b.toLowerCase(),
hashCode: (s) => s.toLowerCase().hashCode,
);

caseInsensitive.add('Alice');
caseInsensitive.add('ALICE'); // duplicate (case-insensitive)
caseInsensitive.add('Bob');
caseInsensitive.add('alice'); // duplicate

print(caseInsensitive.toList()); // [Alice, Bob]
// Insertion order preserved; duplicates removed

LinkedHashSet.identity()

Uses identical() for comparison, insertion order preserved.

LinkedHashSet.of(Iterable<E> elements)

Creates from an iterable preserving insertion order and removing duplicates.

var s = LinkedHashSet.of(['c', 'a', 'b', 'a', 'c']);
print(s.toList()); // [c, a, b]

LinkedHashSet.from(Iterable elements)

Like .of() but accepts Iterable<dynamic>.


Insertion Order Guarantee

The defining feature:

var s = <String>{};
s.add('banana');
s.add('apple');
s.add('cherry');
s.add('banana'); // duplicate — keeps original position

print(s.toList()); // [banana, apple, cherry] — always

// Removing and re-adding changes position
s.remove('banana');
s.add('banana');
print(s.toList()); // [apple, cherry, banana]

Methods

LinkedHashSet implements the full Set<E> interface. See Set<E> for complete method documentation.

Key methods summary:

MethodDescriptionComplexity
add(E)Add element; returns true if newO(1) avg
remove(Object?)Remove elementO(1) avg
contains(Object?)Membership testO(1) avg
union(Set)New set: this ∪ otherO(n + m)
intersection(Set)New set: this ∩ otherO(min(n,m))
difference(Set)New set: this − otherO(n)
addAll(Iterable)Add multipleO(k)
removeAll(Iterable)Remove multipleO(k)
retainAll(Iterable)Keep only commonO(n)
removeWhere(test)Remove matchingO(n)
retainWhere(test)Keep matchingO(n)
clear()Remove allO(n)
toList()Materialize in insertion orderO(n)

Real-World Examples

Example 1: Ordered Search History

import 'dart:collection';

class SearchHistory {
final int maxEntries;
final LinkedHashSet<String> _history = LinkedHashSet();

SearchHistory({this.maxEntries = 20});

void search(String query) {
// Move to most recent if already exists
_history.remove(query);
_history.add(query);

// Trim oldest if over limit
while (_history.length > maxEntries) {
_history.remove(_history.first);
}
}

List<String> get recentQueries => _history.toList().reversed.toList();

bool hasSearched(String query) => _history.contains(query);
}

void main() {
var history = SearchHistory(maxEntries: 5);
history.search('flutter');
history.search('dart collections');
history.search('riverpod');
history.search('flutter'); // moves to end
history.search('async dart');
history.search('null safety');

print(history.recentQueries);
// [null safety, async dart, flutter, riverpod, dart collections]
}

Example 2: Ordered Tag System

import 'dart:collection';

class Post {
final String title;
final LinkedHashSet<String> tags;

Post(this.title, Iterable<String> tags)
: tags = LinkedHashSet.of(tags);

void addTag(String tag) => tags.add(tag);
void removeTag(String tag) => tags.remove(tag);

bool hasTag(String tag) => tags.contains(tag);

// Tags in order they were added
List<String> get orderedTags => tags.toList();
}

void main() {
var post = Post('Dart Collections', ['dart', 'programming', 'tutorial']);
post.addTag('flutter');
post.addTag('dart'); // duplicate — ignored

print(post.orderedTags); // [dart, programming, tutorial, flutter]
print(post.hasTag('flutter')); // true
}

Example 3: Processing Queue (Unique, Ordered)

import 'dart:collection';

class UniqueJobQueue {
final LinkedHashSet<String> _pending = LinkedHashSet();
final LinkedHashSet<String> _processing = LinkedHashSet();

void enqueue(String jobId) => _pending.add(jobId);

String? dequeue() {
if (_pending.isEmpty) return null;
final job = _pending.first;
_pending.remove(job);
_processing.add(job);
return job;
}

void complete(String jobId) => _processing.remove(jobId);

bool isQueued(String jobId) => _pending.contains(jobId);
bool isProcessing(String jobId) => _processing.contains(jobId);

List<String> get pendingJobs => _pending.toList();
}

Example 4: Recently Visited Files

import 'dart:collection';

class RecentFiles {
final int maxFiles;
final LinkedHashSet<String> _files = LinkedHashSet();

RecentFiles({this.maxFiles = 10});

void open(String filePath) {
_files.remove(filePath); // remove if exists
_files.add(filePath); // re-add at end (most recent)
if (_files.length > maxFiles) {
_files.remove(_files.first); // remove oldest
}
}

List<String> get mostRecentFirst => _files.toList().reversed.toList();
List<String> get oldest => _files.toList();
}

Example 5: Feature Flags (Ordered)

import 'dart:collection';

class FeatureFlags {
final LinkedHashSet<String> _enabled = LinkedHashSet();

void enable(String feature) => _enabled.add(feature);
void disable(String feature) => _enabled.remove(feature);
bool isEnabled(String feature) => _enabled.contains(feature);

// All enabled features in activation order
List<String> get enabledFeatures => _enabled.toList();

// Features enabled in both flag sets
Set<String> commonWith(FeatureFlags other) =>
_enabled.intersection(other._enabled);
}

Flutter Example: Multi-Select with Order

import 'dart:collection';

class MultiSelectWidget extends StatefulWidget {
final List<String> options;
const MultiSelectWidget({required this.options, super.key});


State<MultiSelectWidget> createState() => _MultiSelectWidgetState();
}

class _MultiSelectWidgetState extends State<MultiSelectWidget> {
// LinkedHashSet preserves selection order
final LinkedHashSet<String> _selected = LinkedHashSet();


Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...widget.options.map((option) => CheckboxListTile(
title: Text(option),
value: _selected.contains(option),
onChanged: (checked) => setState(() =>
checked! ? _selected.add(option) : _selected.remove(option)),
)),
const Divider(),
Text(
'Selected (in order): ${_selected.join(', ')}',
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
);
}
}

Performance Comparison

OperationLinkedHashSetHashSetSplayTreeSet
add()O(1) avgO(1) avgO(log n)
remove()O(1) avgO(1) avgO(log n)
contains()O(1) avgO(1) avgO(log n)
IterationO(n), insertion orderO(n), randomO(n), sorted
MemoryMediumLowestHighest

Common Mistakes

❌ Expecting first to be the "smallest" element

var s = <String>{'banana', 'apple', 'cherry'};
print(s.first); // 'banana' — insertion order, NOT alphabetical!

// ✅ For sorted first:
print(s.toList()..sort()..first); // 'apple'
// Or use SplayTreeSet

❌ Thinking update changes position

var s = <String>{'a', 'b', 'c'};
s.add('a'); // No-op — 'a' stays at its original position
print(s.toList()); // [a, b, c]

// To move to end:
s.remove('a');
s.add('a');
print(s.toList()); // [b, c, a]

Best Practices

  • Use LinkedHashSet (the default Set literal) for virtually all set use cases.
  • Exploit insertion order for features like search history, recently viewed, and ordered tags.
  • Switch to HashSet only when you've profiled and need the raw speed.
  • Switch to SplayTreeSet when you need elements in sorted order.