| |

Dart 16 🎯 Core Collections: Set for Uniqueness and Set Operations

A Set is an unordered collection of unique elements. Where a List preserves insertion order and allows duplicates, a Set guarantees that each value appears at most once and provides fast membership tests. The two collections answer different questions: a List is for sequences where order and repetition matter, and a Set is for membership and uniqueness. Dart provides Set as a first-class collection with literal syntax, the standard set algebra operations — union, intersection, difference — and the performance characteristics that make it the right tool for deduplication, tagging, and membership checks.

The default Set implementation is LinkedHashSet, which preserves insertion order while guaranteeing uniqueness. This is a deliberate design choice: most uses of a set in application code want the uniqueness guarantee, and preserving insertion order makes the iteration predictable. A HashSet trades ordering for a small performance gain, and a SplayTreeSet sorts the elements by their natural order or by a comparator. The three implementations share the same interface and differ in their ordering and performance characteristics.

This chapter covers set creation, the uniqueness guarantee, the three implementations, the set algebra operations, the patterns for deduplication, membership, and tags, and the differences between sets and lists.

Key point: A Set is an unordered collection of unique elements. Dart’s default Set is a LinkedHashSet, which preserves insertion order. HashSet is unordered and faster for membership; SplayTreeSet sorts its elements. Use set algebra — union, intersection, difference — to combine sets, and Set.from(list) to deduplicate a list.


Why Set exists alongside List

The uniqueness problem. A list can contain duplicates. When the requirement is that each element appears once — a set of tags, a set of permissions, a set of visited URLs — the list must be checked before every insertion, which is expensive. The Set enforces uniqueness at insertion time and provides fast membership.

The membership problem. Checking whether a list contains a value requires a linear scan. Checking whether a set contains a value is a hash lookup, which is constant time on average. For collections that are checked frequently, the set is the correct choice.

The algebra problem. Combining two collections of unique elements — the union, the intersection, the difference — is awkward with lists and natural with sets. The set algebra operations are the standard tools for permissions, tags, and filters.

The ordering problem. Some uses of a set need predictable iteration order, and some do not. The LinkedHashSet preserves insertion order, the HashSet does not, and the SplayTreeSet sorts. The three implementations let the developer choose the trade-off.

The equality problem. Two sets are equal if they contain the same elements, regardless of order. A set comparison is a membership comparison, not an order comparison. This is the correct semantics for collections of unique elements.


a. Creating sets

A set literal uses curly braces. The elements inside are unique.

var numbers = {1, 2, 3, 4, 5};
var names = {'Alice', 'Bob', 'Carol'};
var empty = <int>{};

The type is inferred from the elements. An explicit type can be given with the angle bracket syntax, which is required for an empty set to avoid ambiguity with a map literal.

var numbers = <int>{1, 2, 3};
var empty = <String>{};

A set can be created from a list, which removes the duplicates:

var list = [1, 2, 2, 3, 3, 3];
var set = Set<int>.from(list);  // {1, 2, 3}
var alsoSet = {...list};         // {1, 2, 3}
var another = list.toSet();      // {1, 2, 3}

The spread operator in a set literal adds the elements of another iterable, and the duplicates are removed by the set itself.

var a = {1, 2, 3};
var b = {3, 4, 5};
var combined = {...a, ...b};  // {1, 2, 3, 4, 5}

A set can also be created from a range:

var range = Set<int>.from([for (var i = 1; i <= 5; i++) i]);

b. The uniqueness guarantee

A set contains each element at most once. Adding an element that already exists has no effect.

var tags = {'dart', 'flutter'};
tags.add('dart');  // no effect
print(tags);        // {dart, flutter}

The add method returns true if the element was added and false if it was already present. The addAll method adds multiple elements and ignores the ones that are already present.

var added = tags.add('dart');  // false
var added2 = tags.add('web');   // true

The uniqueness is determined by the element’s == and hashCode. For the built-in types, this is value equality. For custom types, the class must override == and hashCode for the set to detect duplicates correctly.

class Point {
  final int x;
  final int y;
  Point(this.x, this.y);

  @override
  bool operator ==(Object other) =>
      other is Point && other.x == x && other.y == y;

  @override
  int get hashCode => Object.hash(x, y);
}

var points = {Point(1, 2), Point(1, 2)};  // {Point(1, 2)}

Without the overrides, the two Point instances are different objects and both are added.


c. The three implementations

Dart provides three Set implementations, each with different ordering and performance characteristics.

ImplementationOrderingPerformanceUse case
LinkedHashSetInsertion orderGoodDefault
HashSetUnorderedFastestMembership-heavy
SplayTreeSetSortedSlowerSorted iteration

LinkedHashSet is the default. It preserves the order in which elements were inserted, which makes iteration predictable. The cost is slightly more memory for the linked list that maintains the order.

var set = <String>{};
set.add('b');
set.add('a');
set.add('c');
print(set);  // {b, a, c} — insertion order

HashSet does not guarantee any order. It is faster for insertion and membership because it does not maintain the linked list. Use it when the order does not matter and performance is the priority.

import 'dart:collection';
var set = HashSet<String>();
set.add('b');
set.add('a');
print(set);  // order not guaranteed

SplayTreeSet keeps its elements sorted. It requires the elements to be comparable, either by their natural order (Comparable) or by a comparator passed to the constructor. The performance is slower than the hash-based sets, but the iteration is in sorted order.

import 'dart:collection';
var set = SplayTreeSet<int>((a, b) => b.compareTo(a));  // descending
set.addAll([3, 1, 4, 1, 5]);
print(set);  // {5, 4, 3, 1}

The SplayTreeSet is the right choice when the set must be iterated in a specific order, such as a leaderboard or a sorted list of tags.


d. Set algebra operations

The set operations combine two sets and produce a new set.

OperationMethodResult
Uniona.union(b)All elements in either set
Intersectiona.intersection(b)Elements in both sets
Differencea.difference(b)Elements in a but not b
var a = {1, 2, 3, 4};
var b = {3, 4, 5, 6};

print(a.union(b));          // {1, 2, 3, 4, 5, 6}
print(a.intersection(b));   // {3, 4}
print(a.difference(b));     // {1, 2}
print(b.difference(a));     // {5, 6}

The union is the combination. The intersection is the overlap. The difference is what remains when the other set is removed.

These operations are not symmetric. a.difference(b) and b.difference(a) produce different results unless the sets are equal. The union and intersection are symmetric, but the difference is directional.

The operations can be chained:

var result = a.union(b).difference({2, 5});
// {1, 3, 4, 6}

The chain computes the union of a and b, then removes the elements 2 and 5.

The subset and superset relations are tested with methods:

var small = {1, 2};
var large = {1, 2, 3, 4};

print(small.isSubsetOf(large));   // true
print(large.isSupersetOf(small)); // true

A set is a subset of another if every element of the first is in the second. It is a superset if every element of the second is in the first.


e. Deduplication and membership

The most common uses of a set are deduplication and membership checks.

Deduplication. Converting a list to a set removes the duplicates, and converting the set back to a list gives a list with unique elements.

var list = [1, 2, 2, 3, 3, 3];
var unique = list.toSet().toList();  // [1, 2, 3]

The order is preserved if the set is a LinkedHashSet, which is the default.

Membership. The contains method checks whether an element is in the set in constant time on average.

var tags = {'dart', 'flutter', 'web'};
print(tags.contains('dart'));  // true
print(tags.contains('python')); // false

For a collection that is checked frequently, the set is the right choice. The list’s contains is a linear scan; the set’s is a hash lookup.

Counting unique elements. The length of a set is the number of unique elements.

var words = ['apple', 'banana', 'apple', 'cherry'];
var uniqueCount = words.toSet().length;  // 3

Finding common and unique elements. The algebra operations find the common elements (intersection) and the elements that appear in one set but not the other (difference).

var userPermissions = {'read', 'write'};
var requiredPermissions = {'read', 'write', 'delete'};

var missing = requiredPermissions.difference(userPermissions);
print(missing);  // {delete}

f. Sets and lists compared

The two collections differ in ordering, uniqueness, and performance.

AspectListSet
OrderingPreservedPreserved (LinkedHashSet)
DuplicatesAllowedNot allowed
MembershipLinearConstant (average)
Index accessYesNo
IterationBy index or elementBy element only
AlgebraNoUnion, intersection, difference
Literal[1, 2, 3]{1, 2, 3}

The list is the right choice when order and index access matter. The set is the right choice when uniqueness and membership matter. The two are convertible: list.toSet() deduplicates, and set.toList() produces a list.

var list = [3, 1, 2, 1, 3];
var set = list.toSet();      // {3, 1, 2}
var back = set.toList();     // [3, 1, 2]

The conversion preserves the insertion order when the set is a LinkedHashSet, which is the default.


Complete Example Session

// ============================================
// PART 1: CREATE A SET
// ============================================
void main() {
  var numbers = {1, 2, 3, 4, 5};
  var names = <String>{'Alice', 'Bob'};
  var empty = <int>{};

  print(numbers);  // {1, 2, 3, 4, 5}
  print(names);    // {Alice, Bob}
  print(empty);    // {}
}
// ============================================
// PART 2: UNIQUENESS
// ============================================
void main() {
  var tags = {'dart', 'flutter'};
  tags.add('dart');  // no effect
  print(tags);        // {dart, flutter}

  var added = tags.add('web');
  print(added);      // true
}
// ============================================
// PART 3: DEDUPLICATE A LIST
// ============================================
void main() {
  var list = [1, 2, 2, 3, 3, 3];
  var unique = list.toSet().toList();
  print(unique);  // [1, 2, 3]
}
// ============================================
// PART 4: INSERTION ORDER
// ============================================
void main() {
  var set = <String>{};
  set.add('b');
  set.add('a');
  set.add('c');
  print(set);  // {b, a, c}
}
// ============================================
// PART 5: HASHSET (UNORDERED)
// ============================================
import 'dart:collection';

void main() {
  var set = HashSet<String>();
  set.addAll(['b', 'a', 'c']);
  print(set);  // order not guaranteed
}
// ============================================
// PART 6: SPLAYTREESET (SORTED)
// ============================================
import 'dart:collection';

void main() {
  var set = SplayTreeSet<int>();
  set.addAll([3, 1, 4, 1, 5]);
  print(set);  // {1, 3, 4, 5}
}
// ============================================
// PART 7: UNION, INTERSECTION, DIFFERENCE
// ============================================
void main() {
  var a = {1, 2, 3, 4};
  var b = {3, 4, 5, 6};

  print(a.union(b));         // {1, 2, 3, 4, 5, 6}
  print(a.intersection(b));  // {3, 4}
  print(a.difference(b));    // {1, 2}
}
// ============================================
// PART 8: SUBSET AND SUPERSET
// ============================================
void main() {
  var small = {1, 2};
  var large = {1, 2, 3, 4};

  print(small.isSubsetOf(large));    // true
  print(large.isSupersetOf(small));  // true
}
// ============================================
// PART 9: MEMBERSHIP AND PERMISSIONS
// ============================================
void main() {
  var userPermissions = {'read', 'write'};
  var requiredPermissions = {'read', 'write', 'delete'};

  var missing = requiredPermissions.difference(userPermissions);
  print(missing);  // {delete}
}
// ============================================
// PART 10: CUSTOM EQUALITY
// ============================================
class Point {
  final int x;
  final int y;
  Point(this.x, this.y);

  @override
  bool operator ==(Object other) =>
      other is Point && other.x == x && other.y == y;

  @override
  int get hashCode => Object.hash(x, y);
}

void main() {
  var points = {Point(1, 2), Point(1, 2), Point(3, 4)};
  print(points.length);  // 2
}

These ten parts cover creating a set, the uniqueness guarantee, deduplication, insertion order, HashSet, SplayTreeSet, the algebra operations, subset and superset, membership and permissions, and custom equality.


Quick Reference

Set Creation

FormExample
Literal{1, 2, 3}
Typed empty<int>{}
From listSet<int>.from(list)
Spread{...a, ...b}
From list methodlist.toSet()

The Three Implementations

ImplementationOrderingImport
LinkedHashSetInsertionDefault
HashSetUnordereddart:collection
SplayTreeSetSorteddart:collection

Set Operations

OperationMethod
Uniona.union(b)
Intersectiona.intersection(b)
Differencea.difference(b)
Subseta.isSubsetOf(b)
Superseta.isSupersetOf(b)

Common Methods

MethodPurpose
add(element)Add one, returns bool
addAll(iterable)Add many
remove(element)Remove one
contains(element)Membership check
containsAll(iterable)Check all
clear()Remove all
toList()Convert to list

Set vs List

AspectListSet
DuplicatesAllowedNot allowed
MembershipLinearConstant
Index accessYesNo
AlgebraNoYes
Literal[...]{...}

Conversion

FromToMethod
List → SetDeduplicatelist.toSet()
Set → ListOrdered listset.toList()
Iterable → SetUniqueSet.from(iterable)

Best Practices

✅ Do This:

// Use a set for unique elements
var tags = {'dart', 'flutter'};

// Deduplicate with toSet()
var unique = list.toSet().toList();

// Use set algebra
var missing = required.difference(actual);

// Use SplayTreeSet for sorted iteration
var sorted = SplayTreeSet<int>()..addAll([3, 1, 2]);

// Override == and hashCode for custom types
class Point {
  @override
  bool operator ==(Object other) => ...;
  @override
  int get hashCode => ...;
}

❌ Don’t Do This:

// Use a list for membership checks
if (list.contains(x)) { }  // ❌ use a set

// Rely on HashSet ordering
var set = HashSet<int>()..addAll([1, 2, 3]);  // ❌ order not guaranteed

// Forget to override == and hashCode
var points = {Point(1, 2), Point(1, 2)};  // ❌ both added

// Use a set when index access is needed
var set = {1, 2, 3};
set[0];  // ❌ sets are not indexable

// Convert back and forth unnecessarily
list.toSet().toList().toSet();  // ❌ redundant

Common Pitfalls

PitfallWhy It HappensFix
Duplicates not removed== and hashCode not overriddenOverride both
Order unexpectedUsed HashSetUse the default LinkedHashSet
Index access failsSets are not indexableConvert to a list
Membership slowUsed a listUse a set
Sorted iteration not workingSplayTreeSet not usedImport and use it
Empty {} is a mapCurly braces are map literals by defaultUse <int>{} for an empty set

Real-World Examples

1. Unique Tags

var tags = {'dart', 'flutter', 'web'};

2. Deduplicate a List

var unique = [1, 2, 2, 3].toSet().toList();

3. Membership Check

if (permissions.contains('admin')) { }

4. Union of Two Sets

var all = setA.union(setB);

5. Intersection

var common = setA.intersection(setB);

6. Difference

var missing = required.difference(actual);

7. Subset Check

if (small.isSubsetOf(large)) { }

8. Sorted Set

var scores = SplayTreeSet<int>()..addAll([85, 92, 78]);

9. Unique Words

var uniqueWords = text.split(' ').toSet();

10. Custom Equality

var points = {Point(1, 2), Point(1, 2)};  // one element

Visual

Set Uniqueness

┌──────────────────────────────────────────────────────────────┐
│  List: [1, 2, 2, 3, 3, 3]                                    │
│       │                                                      │
│       ▼  toSet()                                             │
│  Set: {1, 2, 3}                                              │
│       │                                                      │
│       ▼  toList()                                            │
│  List: [1, 2, 3]                                             │
└──────────────────────────────────────────────────────────────┘

Set Algebra

┌──────────────────────────────────────────────────────────────┐
│  a = {1, 2, 3, 4}                                            │
│  b = {3, 4, 5, 6}                                            │
│                                                              │
│  UNION:         {1, 2, 3, 4, 5, 6}                           │
│  INTERSECTION:  {3, 4}                                       │
│  DIFFERENCE:    a.difference(b) = {1, 2}                     │
│                 b.difference(a) = {5, 6}                     │
└──────────────────────────────────────────────────────────────┘

The Three Implementations

┌──────────────────────────────────────────────────────────────┐
│  LinkedHashSet (default):                                    │
│  └── Insertion order preserved                               │
│      {b, a, c} stays {b, a, c}                               │
│                                                              │
│  HashSet:                                                    │
│  └── No guaranteed order                                     │
│      Fastest for membership                                  │
│                                                              │
│  SplayTreeSet:                                               │
│  └── Sorted by natural order or comparator                   │
│      {3, 1, 4, 1, 5} becomes {1, 3, 4, 5}                    │
└──────────────────────────────────────────────────────────────┘

Custom Equality

┌──────────────────────────────────────────────────────────────┐
│  WITHOUT == and hashCode:                                    │
│  var points = {Point(1, 2), Point(1, 2)};                    │
│  └── Two different objects → {Point(1, 2), Point(1, 2)}      │
│                                                              │
│  WITH == and hashCode:                                       │
│  var points = {Point(1, 2), Point(1, 2)};                    │
│  └── Equal by value → {Point(1, 2)}                          │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
SetUnordered collection of unique elements
Default implementationLinkedHashSet
OrderingInsertion order (default)
UniquenessEnforced by == and hashCode
MembershipConstant time on average
Algebraunion, intersection, difference
Subset testisSubsetOf, isSupersetOf
Deduplicationlist.toSet()
Sorted setSplayTreeSet
Unordered setHashSet
Empty set literal<int>{}

Key takeaways:

  • A set is an unordered collection of unique elements. The uniqueness is enforced at insertion time, so adding a duplicate has no effect. The add method returns a boolean indicating whether the element was added.
  • The default Set is a LinkedHashSet. It preserves insertion order, which makes iteration predictable. The HashSet is unordered and faster for membership; the SplayTreeSet keeps its elements sorted.
  • Uniqueness is determined by == and hashCode. For the built-in types, this is value equality. For custom types, the class must override both for the set to detect duplicates.
  • The set algebra operations combine two sets. union is the combination, intersection is the overlap, and difference is what remains when the other set is removed. difference is directional; the others are symmetric.
  • Deduplication is list.toSet(). The result is a set with the unique elements, and toList() converts it back to a list with the insertion order preserved.
  • Membership is fast. The contains method uses a hash lookup, which is constant time on average, versus the linear scan of a list.
  • The empty set literal requires a type. {} is an empty map, not an empty set. Use <int>{} or <String>{} to create an empty set.

Remember: The Set is the collection for uniqueness and membership. It guarantees that each element appears once, provides constant-time membership checks, and supports the set algebra operations that combine collections of unique elements. The default LinkedHashSet preserves insertion order; the HashSet trades ordering for speed; the SplayTreeSet sorts. The choice among them is a trade-off between ordering and performance, and the default is right for most application code. Use a set when uniqueness matters, a list when order and index access matter, and convert between them when the requirements change.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!