Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
91: Mock Coding Interview Walkthrough in Rust
Coding interviews aren't just about getting the right answer; they're about how you communicate your thought process while navigating a language's constraints. In Rust, this is particularly interesting because the borrow checker often forces you to be more explicit about your data ownership than you would be in Python or Java. Let's walk through a classic: Merge Intervals.
The problem is simple: given a collection of intervals (like [1, 3] and [2, 6]), merge all overlapping intervals (resulting in [1, 6]). If I were sitting in an interview right now, here is exactly how I'd handle it.
Talking through the strategy
Before I touch the keyboard, I'd tell the interviewer: "The trick here is that if the intervals are sorted by their start times, we only need to look at the last interval we've processed to know if the current one overlaps." I'm establishing a linear time complexity after a sort, which is O(n log n). In Rust, I'll want to use a Vec to store my results because we don't know the final size of the merged list upfront.
Handling the initial sort
First, I need to make sure the input is sorted. I'll assume the input is a Vec<Vec<i32>>. I'll use sort_by_key because it's more concise than a full closure when I only care about the first element.
fn merge_intervals(mut intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
if intervals.is_empty() {
return vec![];
}
// Sort by the start time of each interval
intervals.sort_by_key(|i| i[0]);
let mut merged: Vec<Vec<i32>> = Vec::new();
// ... logic goes here
}
The logic loop and a common borrow-checker stumble
Now, I'll iterate through the sorted intervals. My goal is to check if the current interval starts before the previous one ends.
for interval in intervals {
if merged.is_empty() || interval[0] > merged.last().unwrap()[1] {
merged.push(interval);
} else {
// Update the end time of the last merged interval
merged.last_mut().unwrap()[1] = merged.last().unwrap()[1].max(interval[1]);
}
}
merged
Wait. If I try to compile this, I'm going to hit a wall. Look at that else block. I'm calling merged.last_mut() and then immediately calling merged.last(). I'm trying to borrow the same vector as both mutable and immutable at the same time. The compiler will scream at me.
This is a great "interview moment." Instead of panicking, I'll explain to the interviewer: "I've accidentally created a overlapping borrow. I'm trying to read the end time while I'm mutating it. I can fix this by capturing the value I need first."
Refining the mutation
To fix this, I'll simply calculate the new end time before I perform the mutable borrow, or just use the mutable reference to perform the update directly. Let's do it the clean way:
for interval in intervals {
if merged.is_empty() || interval[0] > merged.last().unwrap()[1] {
merged.push(interval);
} else {
// Get a mutable reference to the last element
let last = merged.last_mut().unwrap();
// Update it in place using the max of the current end and the new end
last[1] = last[1].max(interval[1]);
}
}
By assigning merged.last_mut().unwrap() to a variable last, I'm holding a single mutable borrow. I can then access last[1] to both read and write. This is much more idiomatic. I've avoided the double-borrow, and the logic remains O(n) for the traversal.
Final Polish
In a real interview, I'd finish by discussing edge cases. What if the input is empty? (I handled that at the top). What if an interval is entirely contained within another? (The .max() call handles that). I'd also mention that if I wanted to avoid allocating a new Vec, I could potentially do this in-place, but that would make the code significantly more complex to read, and in an interview, readability usually wins over micro-optimizations unless specifically asked.
📋 Practical Task
Implement a Meeting Room Conflict Checker
Using the logic we discussed in the merge intervals walkthrough, write a function called has_conflict. This function should take a Vec<Vec<i32>> representing meeting start and end times. Instead of merging them, the function should return true the moment it detects that any two meetings overlap, and false if all meetings are distinct. Remember to sort the meetings first!
There are no comments for now.