Comparator in Dart
last modified May 25, 2025
This tutorial explores Dart's Comparator<T>
type,
which enables custom sorting of collections. You'll learn to create reusable
comparison functions for both built-in types and custom objects, with practical
examples demonstrating various sorting scenarios.
Comparator Overview
A Comparator<T>
is a function type that defines how two objects
of type T
should be ordered. It follows the standard comparison
contract used in many programming languages, returning an integer that indicates
the relative ordering of two items.
The comparator function signature is:
typedef Comparator<T> = int Function(T a, T b);
It must return:
Return Value | Meaning |
---|---|
Negative integer | a comes before b |
Zero | a and b are equivalent |
Positive integer | a comes after b |
Dart's Comparable
interface provides a compareTo
method
that follows this same contract. Many built-in types implement Comparable
,
making their instances naturally comparable.
Basic Comparator Usage
This example demonstrates basic comparator usage with both inline and external comparison functions. We'll sort numbers and strings using different approaches.
// External comparator function int compareNumbers(int a, int b) { return a.compareTo(b); } void main() { // Sorting numbers with external comparator List<int> numbers = [5, 2, 9, 1, 7]; numbers.sort(compareNumbers); print('Sorted numbers: $numbers'); // Sorting strings with inline comparator List<String> words = ['apple', 'banana', 'cherry']; words.sort((a, b) => a.compareTo(b)); print('Sorted words: $words'); // Reverse sorting with external comparator int reverseStringComparator(String a, String b) => b.compareTo(a); words.sort(reverseStringComparator); print('Reverse sorted words: $words'); }
The example shows three sorting scenarios: numbers with an external comparator, strings with an inline comparator, and reverse string sorting with another external comparator. Each demonstrates the standard comparison pattern.
External comparators like compareNumbers
can be reused across
your codebase, while inline comparators are useful for one-off sorting needs.
The compareTo
method provides natural ordering for many types.
$ dart run basic_comparator.dart Sorted numbers: [1, 2, 5, 7, 9] Sorted words: [apple, banana, cherry] Reverse sorted words: [cherry, banana, apple]
Sorting Custom Objects
This example shows how to sort custom objects using comparators. We'll define
a Person
class and create comparators for different properties.
class Person { final String name; final int age; final double height; Person(this.name, this.age, this.height); @override String toString() => '$name ($age years, ${height}m)'; } // External comparators int compareByAge(Person a, Person b) => a.age.compareTo(b.age); int compareByName(Person a, Person b) => a.name.compareTo(b.name); int compareByHeight(Person a, Person b) => a.height.compareTo(b.height); void main() { List<Person> people = [ Person('Alice', 30, 1.65), Person('Bob', 25, 1.80), Person('Charlie', 35, 1.75) ]; // Sort by age people.sort(compareByAge); print('By age:'); people.forEach(print); // Sort by name people.sort(compareByName); print('\nBy name:'); people.forEach(print); // Sort by height (inline comparator) people.sort((a, b) => compareByHeight(a, b)); print('\nBy height:'); people.forEach(print); }
We've defined three external comparators for the Person
class,
each sorting by a different property. The example demonstrates how easily you
can change sorting criteria by passing different comparator functions.
The toString
override provides readable output, and we use
forEach(print)
to display the sorted lists. Notice how we can
use the external compareByHeight
function inline as well.
$ dart run object_comparator.dart By age: Bob (25 years, 1.8m) Alice (30 years, 1.65m) Charlie (35 years, 1.75m) By name: Alice (30 years, 1.65m) Bob (25 years, 1.8m) Charlie (35 years, 1.75m) By height: Alice (30 years, 1.65m) Charlie (35 years, 1.75m) Bob (25 years, 1.8m)
Multi-field Sorting
For more complex sorting needs, you can compare multiple fields. This example shows how to create comparators that sort by primary and secondary keys.
class Product { final String category; final String name; final double price; Product(this.category, this.name, this.price); @override String toString() => '$category / $name: \$$price'; } // Compare by category, then by price int compareProducts(Product a, Product b) { var categoryCompare = a.category.compareTo(b.category); if (categoryCompare != 0) return categoryCompare; return a.price.compareTo(b.price); } // Compare by price descending, then by name int compareByPriceDescThenName(Product a, Product b) { var priceCompare = b.price.compareTo(a.price); // Descending if (priceCompare != 0) return priceCompare; return a.name.compareTo(b.name); } void main() { List<Product> products = [ Product('Electronics', 'Laptop', 999.99), Product('Food', 'Apple', 0.99), Product('Electronics', 'Phone', 699.99), Product('Food', 'Banana', 0.59), Product('Electronics', 'Tablet', 499.99), ]; // Sort by category then price products.sort(compareProducts); print('By category then price:'); products.forEach(print); // Sort by price (descending) then name products.sort(compareByPriceDescThenName); print('\nBy price (desc) then name:'); products.forEach(print); }
The example demonstrates two multi-field sorting strategies. The first sorts products by category (primary key) and then by price (secondary key). The second sorts by descending price first, then by product name for items with the same price.
Each comparator first checks the primary field - if those values differ, it returns that comparison result immediately. Only if the primary fields are equal does it compare the secondary field. This pattern can be extended to any number of fields.
$ dart run multi_field_comparator.dart By category then price: Electronics / Tablet: $499.99 Electronics / Phone: $699.99 Electronics / Laptop: $999.99 Food / Banana: $0.59 Food / Apple: $0.99 By price (desc) then name: Electronics / Laptop: $999.99 Electronics / Phone: $699.99 Electronics / Tablet: $499.99 Food / Apple: $0.99 Food / Banana: $0.59
Comparator Utilities
Dart provides useful comparator utilities in the package:collection
library. This example demonstrates compareNatural
,
compareAsciiUpperCase
, and composing comparators.
import 'package:collection/collection.dart'; class Student { final String name; final int grade; final DateTime birthDate; Student(this.name, this.grade, this.birthDate); @override String toString() => '$name (Grade $grade, DOB: ${birthDate.year})'; } void main() { // Natural string comparison (case sensitive) List<String> names = ['alice', 'Bob', 'Charlie', 'dave']; names.sort(compareNatural); print('Natural sort: $names'); // Case-insensitive comparison names.sort(compareAsciiUpperCase); print('Case-insensitive sort: $names'); // Composing comparators List<Student> students = [ Student('Alice', 10, DateTime(2007, 5, 15)), Student('Bob', 10, DateTime(2007, 3, 20)), Student('Charlie', 11, DateTime(2006, 8, 10)), Student('Alice', 9, DateTime(2008, 2, 5)), ]; // Sort by grade, then name, then birthdate var studentComparator = Comparator<Student>((a, b) { var gradeCompare = a.grade.compareTo(b.grade); if (gradeCompare != 0) return gradeCompare; var nameCompare = compareNatural(a.name, b.name); if (nameCompare != 0) return nameCompare; return a.birthDate.compareTo(b.birthDate); }); students.sort(studentComparator); print('\nSorted students:'); students.forEach(print); }
The package:collection
library provides several built-in
comparators. compareNatural
performs case-sensitive string
comparison, while compareAsciiUpperCase
provides case-insensitive
sorting by converting strings to uppercase before comparing.
For the Student
class, we compose a custom comparator that sorts
by grade first, then name, then birthdate. This shows how to build complex
sorting logic while keeping the code readable.
$ dart run comparator_utilities.dart Natural sort: [Bob, Charlie, alice, dave] Case-insensitive sort: [alice, Bob, Charlie, dave] Sorted students: Alice (Grade 9, DOB: 2008) Bob (Grade 10, DOB: 2007) Alice (Grade 10, DOB: 2007) Charlie (Grade 11, DOB: 2006)
Comparator Factories
For maximum flexibility, you can create comparator factories that generate comparison functions based on parameters. This example demonstrates a factory that creates comparators for any comparable property.
class Book { final String title; final String author; final int year; final double rating; Book(this.title, this.author, this.year, this.rating); @override String toString() => '$title by $author ($year) ★$rating'; } // Comparator factory for any Comparable property Comparator<T> compareBy<T, V extends Comparable<V>>(V Function(T) selector) { return (a, b) => selector(a).compareTo(selector(b)); } // Comparator factory for descending order Comparator<T> compareByDescending<T, V extends Comparable<V>>(V Function(T) selector) { return (a, b) => selector(b).compareTo(selector(a)); } void main() { List<Book> books = [ Book('Dart in Action', 'Manning', 2023, 4.5), Book('Flutter Basics', 'OReilly', 2022, 4.2), Book('Dart in Action', 'Manning', 2021, 4.7), Book('Advanced Flutter', 'Apress', 2023, 4.8), ]; // Sort by title books.sort(compareBy<Book, String>((b) => b.title)); print('By title:'); books.forEach(print); // Sort by year descending then rating descending books.sort((a, b) { var yearCompare = compareByDescending<Book, num>((b) => b.year)(a, b); if (yearCompare != 0) return yearCompare; return compareByDescending<Book, num>((b) => b.rating)(a, b); }); print('\nBy year (desc) then rating (desc):'); books.forEach(print); // Sort by author then title books.sort(compareBy<Book, String>((b) => b.author) .thenCompare(compareBy<Book, String>((b) => b.title))); print('\nBy author then title:'); books.forEach(print); } extension ComparatorExtensions<T> on Comparator<T> { Comparator<T> thenCompare(Comparator<T> other) { return (a, b) { var result = this(a, b); if (result != 0) return result; return other(a, b); }; } }
The compareBy
factory creates comparators that extract a
comparable property from objects. The thenCompare
extension
method chains comparators together, similar to thenComparing in Java.
This approach provides maximum flexibility - you can easily create comparators for any property without writing repetitive comparison logic. The example shows sorting books by different combinations of properties with minimal code.
$ dart run comparator_factories.dart By title: Advanced Flutter by Apress (2023) ★4.8 Dart in Action by Manning (2023) ★4.5 Dart in Action by Manning (2021) ★4.7 Flutter Basics by OReilly (2022) ★4.2 By year (desc) then rating (desc): Advanced Flutter by Apress (2023) ★4.8 Dart in Action by Manning (2023) ★4.5 Flutter Basics by OReilly (2022) ★4.2 Dart in Action by Manning (2021) ★4.7 By author then title: Advanced Flutter by Apress (2023) ★4.8 Dart in Action by Manning (2023) ★4.5 Dart in Action by Manning (2021) ★4.7 Flutter Basics by OReilly (2022) ★4.2
Source
Dart Comparator API
Dart Comparable API
Dart Collection Package
Dart's Comparator<T>
provides powerful, flexible sorting
capabilities. By creating reusable comparator functions and leveraging
Dart's functional features, you can implement complex sorting logic while
keeping your code clean and maintainable. The techniques shown scale from
simple property comparisons to sophisticated multi-field sorting scenarios.
Author
List all Dart tutorials.