#179 Jun 3, 2026

179. Iterator::max_by_key — Find the Best Element Without a Manual Fold

Finding the “best” item in a collection — longest string, heaviest order, latest timestamp — is one of those tasks where a hand-rolled fold keeps showing up. Iterator::max_by_key does the same job in one call, and min_by_key is right there next to it.

The fold you keep writing

You have a slice of strings and want the longest one. The DIY version looks something like this:

1
2
3
4
5
6
7
8
9
let words = ["pear", "raspberry", "fig", "kiwi"];

let longest = words.iter().fold(None, |best, w| match best {
    None => Some(w),
    Some(b) if w.len() > b.len() => Some(w),
    other => other,
});

assert_eq!(longest, Some(&"raspberry"));

That’s a lot of code for a one-liner concept. max_by_key collapses the whole pattern:

1
2
3
let words = ["pear", "raspberry", "fig", "kiwi"];
let longest = words.iter().max_by_key(|w| w.len());
assert_eq!(longest, Some(&"raspberry"));

You hand it a closure that extracts the key — the thing you want to maximise — and it returns the item that produced the largest key.

Works on anything Ord

The key doesn’t have to be a number. It just has to implement Ord, which means strings, tuples, dates, your own types — anything totally ordered:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[derive(Debug, PartialEq)]
struct Order { id: u32, total_cents: u64 }

let orders = vec![
    Order { id: 1, total_cents: 1299 },
    Order { id: 2, total_cents: 4500 },
    Order { id: 3, total_cents: 800 },
];

let biggest = orders.iter().max_by_key(|o| o.total_cents);
assert_eq!(biggest, Some(&Order { id: 2, total_cents: 4500 }));

Tuples are where this really shines — you get multi-key sorting for free, because (A, B): Ord compares lexicographically:

1
2
3
4
5
let words = ["pear", "fig", "kiwi", "lime"];

// Longest word, then alphabetically last among ties.
let pick = words.iter().max_by_key(|w| (w.len(), *w));
assert_eq!(pick, Some(&"pear"));

Same trick with min_by_key — the entire _by_key family follows the same shape.

The tie-break rule, and why it matters

When two elements produce equal keys, max_by_key returns the last one and min_by_key returns the first. That asymmetry is in the docs and it bites people:

1
2
3
4
5
6
7
let nums = [3, 1, 4, 1, 5, 9, 2, 6, 5];

let max = nums.iter().max_by_key(|&&n| n);
let min = nums.iter().min_by_key(|&&n| n);

assert_eq!(max, Some(&9));
assert_eq!(min, Some(&1)); // the first 1, not the second

If you need a specific tie-break — “longest word, but earliest in the list when tied” — encode it in the key itself with a tuple, instead of relying on iteration order.

Floats need min_by / max_by

f32 and f64 don’t implement Ord (because NaN), so max_by_key won’t compile if your key is a float. Reach for max_by and pass a comparator instead:

1
2
3
4
5
6
let measurements = [1.2_f64, 3.4, 0.5, 2.8];

let biggest = measurements.iter()
    .max_by(|a, b| a.partial_cmp(b).unwrap());

assert_eq!(biggest, Some(&3.4));

Or wrap your floats in something like ordered_float::OrderedFloat and stay with max_by_key.

Takeaway

Any time you find yourself folding to track the “best so far,” check whether max_by_key or min_by_key says it in one line. The closure extracts the score; the iterator returns the winner. For ties you control the rule by shaping the key.

← Previous 178. Ord::clamp — Stop Writing min(max, max(min, x))