ZetCode

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.

basic_comparator.dart
// 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.

object_comparator.dart
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.

multi_field_comparator.dart
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.

comparator_utilities.dart
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.

comparator_factories.dart
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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.