The Rust I learned from LeetCode
Disclaimer: This article is co-authored with Claude as I revisited my notes from the past year. We picked out patterns that kept showing up and seemed worth sharing.
None of what’s below is really “idiomatic”. But I like functional programming and the expressiveness it permits. Not all of it results in a superior solution, but I found some that looked lovely through some very interesting use. I hope you find these useful if you’re also a fellow enjoyer of Rust.
try_fold and early returns
In JavaScript, if I need to return early from a loop, I can’t use .reduce() — I have to drop into imperative code. Rust has the same limitation with .fold().
But try_fold solves it by repurposing Result as a control flow signal:
nums.iter()
.enumerate()
.try_fold(HashMap::new(), |mut seen, (i, &num)| {
match seen.get(&(target - num)) {
Some(&j) => Err(vec![j as i32, i as i32]), // done, bail out
None => {
seen.insert(num, i);
Ok(seen) // keep going
}
}
})Err doesn’t mean “error” here — it means “I found what I was looking for, stop iterating.” That’s a novel way to think about Result. It’s not just for error handling; it’s a general-purpose “continue or stop” signal.
bool.then() and treating booleans like monads
Rust lets you attach methods to primitives, and bool has .then():
// turn a bool into Option
c.is_ascii_alphanumeric().then(|| c.to_ascii_lowercase())
// true → Some(c.to_ascii_lowercase())
// false → NoneThis is kind of rad. Rust treats bool like it can interop with the Option/Result world. It’s a small thing, but it means you can stay in iterator chains instead of dropping into if-else:
s.chars().filter_map(|c|
// Rust strings are unicode and lowercasing is really just an ASCII concept, so we need to test first!
c.is_ascii_alphanumeric().then(|| c.to_ascii_lowercase())
)Iterator .eq() for comparison
Most languages only let you compare iterables if you convert them to strings first — or you write the comparison loop yourself. Rust’s iterators have .eq() built in:
let chars = s.chars()
.filter_map(|c| c.is_ascii_alphanumeric().then(|| c.to_ascii_lowercase()));
chars.clone().eq(chars.clone().rev()) // palindrome checkElement-by-element comparison, lazy, short-circuits on first mismatch. No allocation, no string conversion. Pretty rad.
Range for two-pointer state
Two-pointer problems usually have two variables and an invariant you have to remember:
let mut left = 0;
let mut right = nums.len() - 1;
while left < right { ... }I started using Range to bundle them:
let mut range = 0..nums.len();
while !range.is_empty() {
let left = range.start;
let right = range.end - 1;
// shrink by adjusting range.start or range.end
}One variable, and is_empty() captures the termination condition. It makes the “shrinking search space” idea more explicit.
Reverse for min-heaps
Rust’s BinaryHeap is a max-heap. Instead of implementing a min-heap, you wrap values:
use std::cmp::Reverse;
min_heap.push(Reverse(5));
min_heap.pop() // gives Reverse(smallest)Reverse just flips the Ord implementation. Zero runtime cost. The type encodes the behavior change.
The common thread
Looking back, the patterns I like share something: they encode information in the type system.
Resultintry_foldencodes “continue vs. stop”bool.then()bridges booleans intoOptionRangeencodes “a search space that shrinks”Reverseencodes “flip the ordering”
Rust rewards this way of thinking. And LeetCode, without the pressure of production code, is a good place to explore it.
What have you learned from unusual practice?