Rust for Coding Interviews: The Standard Library Cheat Sheet

Vec,VecDeque,HashMap,BTreeMap,BinaryHeap,HashSetare the six collections you need cold before your interview.- The entry API (
*map.entry(k).or_insert(0) += 1) eliminates the two-lookup anti-pattern for frequency counting in one line. BinaryHeapis a max-heap by default. Wrap values inReverse<T>to get a min-heap for Dijkstra and k-smallest problems.usizeunderflow is the most common Rust interview bug. Cast toi64when index arithmetic might go negative.s[i]does not compile in Rust. Uses.as_bytes()[i]for O(1) ASCII access or collect toVec<char>for random access.- Rust pays off only with 50+ problems of real muscle memory. Below that threshold, switching languages hurts more than it helps.
Rust is a legitimate language for coding interviews. Cloudflare, Databricks, Oxide Computer, and a growing list of systems-heavy shops accept it, and some will visibly perk up when you say it. But it will punish you if you walk in expecting Python ergonomics. The borrow checker doesn't care about your 45-minute clock.
This guide covers the standard library data structures you actually need, the idiomatic patterns that save keystrokes, and the four gotchas that will end your interview if you don't know them cold.
Should You Use Rust in Your Coding Interview?
Only if you would reach for Rust naturally for a real project in this domain.
The friction is real. String manipulation that takes two lines in Python can take ten in Rust. Graph traversal with mutable references often fights the borrow checker hard enough that you end up rewriting your approach mid-interview. You will lose. If you have to think about &str vs String while also thinking about the algorithm, you're losing time on both fronts simultaneously.
Rust earns its keep when the company writes Rust and the interviewer knows the language. At a systems shop, showing idiomatic Rust signals more than correct Python ever could. Outside that context, fluency beats features: pick the language where your fingers don't have to think.
If you do use Rust, commit fully. Know the standard library cold. The sections below are that reference.

Two lines in Python. Twenty-five in Rust. The extra twenty-three are not insights.
The Collections You Will Actually Use
Vec
Your default container. push and pop are O(1) amortized. insert(i, v) and remove(i) are O(n). Indexing is bounds-checked in both debug and release builds, so a bad index panics cleanly instead of corrupting memory.
let mut v: Vec<i32> = Vec::new(); v.push(1); v.push(2); v.pop(); // Some(2) v.sort(); // stable sort, O(n log n) v.sort_unstable(); // faster in practice v.reverse(); v.dedup(); // removes consecutive duplicates (sort first) v.retain(|&x| x > 0);
For two-pointer problems, index directly. Rust will block two &mut references into the same vec simultaneously, but v[i] and v[j] as index expressions are fine.
HashMap
The entry API is the most important thing to learn before your interview. It eliminates the two-lookup anti-pattern and handles "initialize if absent" in one line. Without it, you will write three lines of boilerplate for every frequency count.
use std::collections::HashMap; let mut freq: HashMap<char, i32> = HashMap::new(); // Bad: two lookups if freq.contains_key(&c) { *freq.get_mut(&c).unwrap() += 1; } else { freq.insert(c, 1); } // Good: entry API *freq.entry(c).or_insert(0) += 1; // Grouping let mut groups: HashMap<i32, Vec<i32>> = HashMap::new(); groups.entry(key).or_default().push(value); // Lookup freq.get(&c); // Option<&i32> freq.contains_key(&c); // bool freq.remove(&c); // Option<i32>
Iteration order is random. Never rely on it. If you need sorted keys, use BTreeMap.
BTreeMap
Same API as HashMap but keys are always sorted. O(log n) for all operations. Use it when you need range queries or ordered traversal.
use std::collections::BTreeMap; let mut map: BTreeMap<i32, i32> = BTreeMap::new(); map.insert(3, 30); map.insert(1, 10); // Range queries for (k, v) in map.range(1..=2) { /* sorted */ } // Nearest key lookups map.range(..target).next_back(); // largest key < target map.range(target..).next(); // smallest key >= target
Hash map time complexity covers when the O(1) vs O(log n) tradeoff actually matters. For most interview problems, reach for BTreeMap the moment you need sorted order.
BinaryHeap
Rust's BinaryHeap is a max-heap. To get a min-heap, wrap values in std::cmp::Reverse. This surprises almost every Python developer on their first Dijkstra in Rust.
use std::collections::BinaryHeap; use std::cmp::Reverse; let mut max_heap: BinaryHeap<i32> = BinaryHeap::new(); max_heap.push(3); max_heap.push(1); max_heap.peek(); // Some(&3) max_heap.pop(); // Some(3) // Min-heap via Reverse let mut min_heap: BinaryHeap<Reverse<i32>> = BinaryHeap::new(); min_heap.push(Reverse(3)); min_heap.push(Reverse(1)); min_heap.pop(); // Some(Reverse(1)) -- smallest first
For Dijkstra's or "k smallest elements" problems, this is what you want. The heap data structure article explains why max-heap is the default.
VecDeque
BFS queues. push_back and pop_front are both O(1) amortized. Never use Vec::remove(0) for a queue. That's O(n) per dequeue, which means your O(V+E) BFS is quietly O(V^2).
use std::collections::VecDeque; let mut queue: VecDeque<i32> = VecDeque::new(); queue.push_back(1); queue.push_back(2); queue.pop_front(); // Some(1) queue.front(); // peek without removing
HashSet and BTreeSet
HashSet<T> for O(1) membership checks. BTreeSet<T> when you need sorted order or range iteration. Both have insert, contains, and remove.
use std::collections::HashSet; let mut seen: HashSet<i32> = HashSet::new(); seen.insert(1); seen.contains(&1); // true // Set operations let a: HashSet<i32> = [1, 2, 3].into(); let b: HashSet<i32> = [2, 3, 4].into(); let intersection: HashSet<&i32> = a.intersection(&b).collect();
Sorting: The Part That Bites
sort is stable (Timsort). sort_unstable is faster and uses pattern-defeating quicksort. Prefer sort_unstable unless you specifically need stability.
let mut v = vec![3, 1, 4, 1, 5]; v.sort_unstable(); // ascending v.sort_unstable_by(|a, b| b.cmp(a)); // descending v.sort_unstable_by_key(|&x| std::cmp::Reverse(x));
Floats are the trap. f64 does not implement Ord because NaN != NaN. Calling .sort() on a Vec<f64> won't compile. Use total_cmp, stabilized in Rust 1.62:
let mut floats = vec![3.0_f64, 1.0, f64::NAN, 2.0]; floats.sort_by(|a, b| a.total_cmp(b)); // or if you're certain there's no NaN: floats.sort_by(|a, b| a.partial_cmp(b).unwrap());
Reaching for .partial_cmp(b).unwrap() on real data containing NaN panics at runtime, not compile time. The compiler will not save you here. The interviewer will watch it happen.
The Gotchas That Will End Your Interview
usize Will Underflow and Betray You
usize is unsigned. Subtracting below zero panics in debug mode and wraps silently in release. This hits constantly in index arithmetic, binary search, and Dutch National Flag problems. The first time it happens in an interview you will stare at the panic message for thirty seconds before your brain processes what "attempt to subtract with overflow" actually means.
let i: usize = 0; let j = i - 1; // panic: attempt to subtract with overflow // Fix: checked_sub, or cast to i64 when the value might go negative let j = i.checked_sub(1); // Option<usize> let j = (i as i64) - 1; // safe arithmetic, cast back when needed
The single most common Rust interview bug is usize underflow. Cast to i64 early and convert back with as usize once you've confirmed the value is non-negative.
Borrow Checker in Graph Traversal
You cannot hold a reference into a Vec while also pushing to it.
// This won't compile: for neighbor in &graph[node] { graph[node].push(new_node); // cannot borrow `graph` as mutable } // Fix: clone the neighbor list, or use indices let neighbors = graph[node].clone(); for &neighbor in &neighbors { ... }
Prefer index-based traversal over reference-based traversal. Store nodes as integer indices, not as references or pointers. This completely sidesteps the borrow checker.

The compiler is not being difficult. It is being correct. This does not make it less annoying at minute 38 of a 45-minute interview.
String Handling
String is owned. &str is a borrowed slice. For interview problems, String is almost always what you want.
let s = String::from("hello"); let bytes = s.as_bytes(); // &[u8], fast, ASCII-safe let chars: Vec<char> = s.chars().collect(); // Unicode-safe s.chars().nth(i); // O(i), not O(1) -- collect to Vec<char> if you need random access let result: String = chars.iter().collect(); let result = format!("{}{}", a, b); let result: String = parts.join("");
s[i] does not compile. Use s.as_bytes()[i] for O(1) ASCII access, or collect to Vec<char> once at the top of the function and index freely from there.
The * You Will Definitely Forget Under Pressure
or_insert returns &mut V, not V. Forgetting the dereference produces a cryptic type error. The error message is not helpful. The fix takes three seconds once you know it, and zero seconds to prevent.
// or_insert returns &mut V, not V let count = *freq.entry(c).or_insert(0); // deref to get the value *freq.entry(c).or_insert(0) += 1; // deref to mutate
Patterns That Save Keystrokes
// Two-pointer swap v.swap(left, right); // Max/min of a vec v.iter().max(); // Option<&i32> v.iter().min(); // Enumerate for (i, &val) in v.iter().enumerate() { ... } // Sliding window slices for window in v.windows(k) { /* &[i32] of length k */ } // Prefix sums let prefix: Vec<i64> = std::iter::once(0) .chain(v.iter().scan(0_i64, |acc, &x| { *acc += x as i64; Some(*acc) })) .collect(); // Count distinct v.sort_unstable(); v.dedup(); let distinct = v.len();
When Rust Is Worth It
Rust makes sense when the compiler is helping, not blocking. The ownership model catches bugs you'd introduce in C++ without noticing. The type system forces you to handle None explicitly. The standard library is smaller than Java's but precise.
It is not worth it when your interviewer doesn't know Rust, or when the problem is string-heavy. A "group anagrams" problem that's eight lines in Python is twenty-five in Rust, and the extra lines are boilerplate, not insight. Go ahead and count them sometime. The number is demoralizing. If a recruiter says "pick any language," that means pick your fastest one, not your favorite.
A reasonable threshold: 50+ LeetCode problems solved in Rust with solid muscle memory for the entry API and Reverse wrapper. Below that, one session of voice-based mock practice in your actual interview language will do more for your score than switching to Rust for credibility. SpaceComplexity runs realistic, rubric-based DSA interviews so you can confirm your muscle memory is actually there before the real thing.
Quick Reference
| Need | Use |
|---|---|
| Dynamic array | Vec<T> |
| Queue (BFS) | VecDeque<T> |
| Hash map | HashMap<K, V> + entry API |
| Sorted map / range queries | BTreeMap<K, V> |
| Max-heap | BinaryHeap<T> |
| Min-heap | BinaryHeap<Reverse<T>> |
| Hash set | HashSet<T> |
| Sorted set | BTreeSet<T> |
| Sort ascending | v.sort_unstable() |
| Sort descending | v.sort_unstable_by(|a, b| b.cmp(a)) |
| Sort floats | v.sort_by(|a, b| a.total_cmp(b)) |
| Frequency map | *map.entry(k).or_insert(0) += 1 |
| Safe subtraction | i.checked_sub(j) or cast to i64 |
| Char iteration | s.chars() |
| Byte iteration (ASCII) | s.as_bytes() |
Further Reading
- std::collections documentation - The canonical reference for every collection type and when to use each
- The Rust Programming Language, Chapter 8 - The official book's collections chapter
- Rust std::cmp::Reverse - Official docs for the min-heap wrapper
- f64::total_cmp - Stabilized in 1.62, the right way to sort floats
- The Rustonomicon: Ownership - When you want to understand exactly why the borrow checker blocks what it blocks