while
while
Level 2 — Control Flow & Data Structures A conditional loop that runs while a predicate is true.
1. Prerequisites
loop— The unconditional loop that runs forever.if/else— The branching logic that evaluates a true/false condition (whichwhilealso does).
2. Term Category
Rust Control Flow (conditional loop execution): The while loop is a fundamental construct found in almost every programming language (C, Java, Python, JavaScript) to repeat code based on a condition.
3. Explanation
(1) Design Motivation — "Why did we design this?"
The loop keyword is fantastic for infinite processes, but very often, you want a loop to stop naturally when a specific condition is no longer met.
You could achieve this by writing a loop, putting an if statement at the very top, and calling break if the condition is false. However, doing this every time is verbose and clunky.
The while loop was designed as a cleaner alternative. It combines a loop and a condition into a single, elegant line of code. It checks a true/false condition (a predicate) before every iteration. If it's true, it runs the block. If it's false, it skips the block and moves on to the rest of the program.
(2) Reality Metaphor
A while loop is like filling up your car's gas tank.
You squeeze the pump handle (execute the loop body) while the tank is not full. The moment the sensor detects that the tank is full (the condition becomes false), the pump automatically stops, and you move on with your day.
(3) Rust Code Examples
Short Snippet
let mut countdown = 3;
// The loop runs as long as countdown is greater than 0.
// Notice there are no parentheses around the condition!
while countdown > 0 {
println!("{}...", countdown);
countdown -= 1; // Don't forget to change the condition variable!
}
println!("Liftoff!");
Fuller Example
fn main() {
let mut player_health = 100;
let mut monsters_defeated = 0;
// A classic game loop scenario
while player_health > 0 {
// Simulate taking damage
player_health -= 25;
monsters_defeated += 1;
println!("Fought a monster! Health is now {}", player_health);
// We can still use `break` inside a while loop if an emergency happens
if monsters_defeated == 3 {
println!("You found the exit and escaped early!");
break;
}
}
println!("Adventure over. You defeated {} monsters.", monsters_defeated);
}
4. Common Mistakes & Pitfalls
Mistake 1: Misunderstanding While Scoping and Lifecycle Rules
The mistake: Assuming While instances remain valid beyond their declaring scope block or across asynchronous boundaries without explicit lifetime tracking.
Why it's wrong: Rust strictly enforces lexical scope boundaries and non-lexical lifetimes (NLL) at compile time. Accessing dropped values or failing to handle variable drop order results in compiler errors such as E0597 or E0382.
Incorrect:
fn get_ref() -> &str {
let s = String::from("while_data");
&s // ❌ Error E0106/E0515: returns a reference to data owned by the current function
}
Fix:
fn get_string() -> String {
let s = String::from("while_data");
s // Ownership of the String is transferred directly to the caller
}
Mistake 2: Mutating While State Without Exclusive Ownership or mut Borrowing
The mistake: Attempting to mutate data associated with While through an immutable reference &T or without specifying mut in variable declarations.
Why it's wrong: Rust's aliasing XOR mutability rule (&T for shared immutable access, &mut T for exclusive mutable access) prohibits mutating state through shared references unless interior mutability patterns (e.g. RefCell, Mutex) are explicitly used.
Incorrect:
fn update_val(data: &i32) {
// *data += 1; // ❌ Error E0594: cannot assign to `*data`, which is behind a `&` reference
}
Fix:
fn update_val(data: &mut i32) {
*data += 1; // Correct: exclusive mutable reference permits mutation
}
Mistake 3: Concurrent Access to While Across Threads Without Send / Sync Guards
The mistake: Sharing non-thread-safe While instances across OS threads via std::thread::spawn.
Why it's wrong: Types that do not implement Send or Sync marker traits cannot safely cross thread boundaries. The compiler prevents data races by raising compile errors E0277 (trait Send is not implemented).
Incorrect:
use std::rc::Rc;
use std::thread;
let rc = Rc::new(42);
// thread::spawn(move || { println!("{}", rc); }); // ❌ Error E0277: `Rc` cannot be sent between threads safely
Fix:
use std::sync::Arc;
use std::thread;
let arc = Arc::new(42);
thread::spawn(move || {
println!("{}", arc); // Correct: `Arc` implements `Send` and `Sync`
});
5. Practice Exercises
Exercise 1: Resilience Engine with Exponential Backoff State Loop
Scenario: Problem Statement: In distributed network services, operations (such as HTTP requests, RPCs, or database transactions) frequently fail due to transient errors like network jitter, connection timeouts, or service overload. To ensure system resilience, engineers implement retry loops with exponential backoff.
Requirements:
Design a BackoffRetryEngine struct with an execute method that executes a closure F inside a while loop under the following constraints:
- Maintain state counters for
attempts(starting at 0) andtotal_delay_ms(starting at 0). - The
whileloop must continue whileattempts < policy.max_attempts. - Increment
attemptsby 1 at the beginning of each iteration. - Call the closure
op(attempts). If it returnsOk(payload), return anOk(ExecutionSummary)immediately containing the total attempts, accumulated delay, and payload. - If the closure returns
Err(RetryError::Transient(msg)):- If
attempts < policy.max_attempts, addcurrent_backofftototal_delay_ms, then updatecurrent_backoff = (current_backoff * 2).min(policy.max_backoff_ms). - If
attempts == policy.max_attempts, let thewhileloop terminate naturally and returnErr(RetryError::Transient(...)).
- If
- If the closure returns
Err(RetryError::Fatal(msg)), terminate thewhileloop immediately usingreturn/breakand return the fatal error.
Include comprehensive unit tests covering:
- Immediate success on attempt 1.
- Eventual success after transient retries with accumulated delay verification.
- Exhaustion of retry budget (
max_attempts). - Fatal error triggering early termination without exhausting remaining retries.
Answer
Implementation
#[derive(Debug, PartialEq, Eq)]
pub enum RetryError {
Transient(String),
Fatal(String),
}
#[derive(Debug, PartialEq, Eq)]
pub struct RetryPolicy {
pub max_attempts: usize,
pub initial_backoff_ms: u64,
pub max_backoff_ms: u64,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ExecutionSummary {
pub total_attempts: usize,
pub total_delay_ms: u64,
pub payload: String,
}
pub struct BackoffRetryEngine {
policy: RetryPolicy,
}
impl BackoffRetryEngine {
pub fn new(policy: RetryPolicy) -> Self {
Self { policy }
}
pub fn execute<F>(&self, mut op: F) -> Result<ExecutionSummary, RetryError>
where
F: FnMut(usize) -> Result<String, RetryError>,
{
let mut attempts = 0;
let mut total_delay_ms = 0;
let mut current_backoff = self.policy.initial_backoff_ms;
while attempts < self.policy.max_attempts {
attempts += 1;
match op(attempts) {
Ok(data) => {
return Ok(ExecutionSummary {
total_attempts: attempts,
total_delay_ms,
payload: data,
});
}
Err(RetryError::Transient(_msg)) => {
if attempts < self.policy.max_attempts {
total_delay_ms += current_backoff;
current_backoff = (current_backoff * 2).min(self.policy.max_backoff_ms);
}
}
Err(RetryError::Fatal(msg)) => {
return Err(RetryError::Fatal(msg));
}
}
}
Err(RetryError::Transient(format!(
"Operation failed after {} attempts",
attempts
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_successful_first_attempt() {
let policy = RetryPolicy {
max_attempts: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let engine = BackoffRetryEngine::new(policy);
let result = engine.execute(|_attempt| Ok("Success".to_string()));
assert!(result.is_ok());
let summary = result.unwrap();
assert_eq!(summary.total_attempts, 1);
assert_eq!(summary.total_delay_ms, 0);
assert_eq!(summary.payload, "Success");
}
#[test]
fn test_transient_retry_success() {
let policy = RetryPolicy {
max_attempts: 5,
initial_backoff_ms: 50,
max_backoff_ms: 500,
};
let engine = BackoffRetryEngine::new(policy);
let result = engine.execute(|attempt| {
if attempt < 3 {
Err(RetryError::Transient("Connection reset".to_string()))
} else {
Ok("Connected".to_string())
}
});
assert!(result.is_ok());
let summary = result.unwrap();
assert_eq!(summary.total_attempts, 3);
// Attempt 1: delay 50ms, Attempt 2: delay 100ms -> Total delay = 150ms
assert_eq!(summary.total_delay_ms, 150);
assert_eq!(summary.payload, "Connected");
}
#[test]
fn test_retry_exhaustion() {
let policy = RetryPolicy {
max_attempts: 3,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let engine = BackoffRetryEngine::new(policy);
let result = engine.execute(|_attempt| {
Err(RetryError::Transient("Timeout".to_string()))
});
assert!(result.is_err());
assert_eq!(
result,
Err(RetryError::Transient("Operation failed after 3 attempts".to_string()))
);
}
#[test]
fn test_fatal_error_immediate_termination() {
let policy = RetryPolicy {
max_attempts: 5,
initial_backoff_ms: 100,
max_backoff_ms: 1000,
};
let engine = BackoffRetryEngine::new(policy);
let mut call_count = 0;
let result = engine.execute(|attempt| {
call_count += 1;
if attempt == 1 {
Err(RetryError::Transient("Busy".to_string()))
} else {
Err(RetryError::Fatal("Auth Failed".to_string()))
}
});
assert!(result.is_err());
assert_ne!(call_count, policy.max_attempts);
assert_eq!(call_count, 2);
assert!(matches!(result, Err(RetryError::Fatal(ref msg)) if msg == "Auth Failed"));
}
}
Technical Explanation
whilePredicate Evaluation and State Control: Thewhile attempts < self.policy.max_attemptscondition acts as a strict dynamic guard. Unlikeforloops which consume an iterator,whileallows manual increments and conditional iteration short-circuiting based on state modified inside the body.- Ownership and FnMut Trait Bounds: The closure parameter
opis bound withFnMut(usize) -> Result<String, RetryError>. UsingFnMutpermits the closure to mutate internal state across repeated invocations inside thewhileloop (e.g., trackingcall_countin tests), while passed by mutable referencemut op. - Early Exit vs Loop Exhaustion: Returning
Ok(...)orErr(RetryError::Fatal)directly from within thematchblock exits the function (and loop) immediately without performing unnecessary backoff calculations. When transient errors persist, the loop terminates naturally whenattemptsreachesmax_attempts. - Edge Cases: When
max_attemptsis set to 0, thewhilepredicate0 < 0evaluates tofalseimmediately, safely returning a retry exhaustion error without invokingop. Backoff capping using.min(max_backoff_ms)prevents numerical overflow during repeated doubling.
Exercise 2: Length-Prefixed Binary Stream Parser & Frame Decoder
Scenario: Problem Statement:
High-performance network services (such as Redis, Kafka, or custom TCP protocols) transmit binary frames over stream sockets. Because TCP delivers byte streams rather than discrete packets, incoming data arrives in arbitrary chunks into an accumulating buffer Vec<u8>.
Requirements:
Implement StreamFrameDecoder::decode_stream to parse binary frames from a mutable buffer reference &mut Vec<u8>.
Each binary frame consists of:
- Magic Byte:
0xAA(1 byte). - Payload Length:
u16in big-endian byte order (2 bytes). - Payload: N bytes (where N = Payload Length).
The decoder must use a while loop with cursor navigation:
while cursor + HEADER_SIZE <= buffer.len()(whereHEADER_SIZE = 3bytes):- Check magic byte at
buffer[cursor]. Ifbuffer[cursor] != 0xAA, incrementcorrupted_count += 1, advancecursor += 1, andcontinueto scan for the next valid frame header. - Read 2-byte payload length via
u16::from_be_bytes([buffer[cursor + 1], buffer[cursor + 2]]) as usize. - Calculate total frame size
total_frame_len = 3 + payload_len. - Check if the full frame is present in the buffer:
if cursor + total_frame_len > buffer.len(). If false (fragmented frame),breakout of thewhileloop without advancingcursor, leaving unparsed bytes in the buffer. - Extract payload slice
buffer[cursor + 3 .. cursor + total_frame_len].to_vec(), store inFrame, advancecursor += total_frame_len.
- Check magic byte at
- After the
whileloop finishes, drain processed bytes from the front of the buffer:buffer.drain(0..cursor). - Return
(Vec<Frame>, usize)returning parsed frames and total corrupted bytes skipped.
Write unit tests verifying:
- Decoding multiple sequential valid frames in a single call.
- Handling incomplete frame headers/payloads (fragmentation) by retaining remaining buffer bytes and resuming correctly when more data arrives.
- Skipped corrupted prefix bytes recovery.
- Frames with zero-length payloads (
payload_len == 0).
Answer
Implementation
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Frame {
pub payload: Vec<u8>,
}
pub struct StreamFrameDecoder;
impl StreamFrameDecoder {
pub const MAGIC_BYTE: u8 = 0xAA;
pub const HEADER_SIZE: usize = 3; // 1 byte magic + 2 bytes u16 len
pub fn decode_stream(buffer: &mut Vec<u8>) -> (Vec<Frame>, usize) {
let mut frames = Vec::new();
let mut cursor = 0;
let mut corrupted_count = 0;
while cursor + Self::HEADER_SIZE <= buffer.len() {
if buffer[cursor] != Self::MAGIC_BYTE {
corrupted_count += 1;
cursor += 1;
continue;
}
let payload_len = u16::from_be_bytes([buffer[cursor + 1], buffer[cursor + 2]]) as usize;
let total_frame_len = Self::HEADER_SIZE + payload_len;
if cursor + total_frame_len > buffer.len() {
// Incomplete payload; suspend parsing until next network read chunk
break;
}
let payload = buffer[cursor + Self::HEADER_SIZE..cursor + total_frame_len].to_vec();
frames.push(Frame { payload });
cursor += total_frame_len;
}
// Retain unparsed/fragmented trailing bytes by draining processed bytes
buffer.drain(0..cursor);
(frames, corrupted_count)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_multiple_valid_frames() {
let mut stream_buf = Vec::new();
// Frame 1: [0xAA, 0x00, 0x02, 0x10, 0x20]
stream_buf.extend_from_slice(&[0xAA, 0x00, 0x02, 0x10, 0x20]);
// Frame 2: [0xAA, 0x00, 0x01, 0xFF]
stream_buf.extend_from_slice(&[0xAA, 0x00, 0x01, 0xFF]);
let (frames, corrupted) = StreamFrameDecoder::decode_stream(&mut stream_buf);
assert_eq!(frames.len(), 2);
assert_eq!(corrupted, 0);
assert_eq!(frames[0].payload, vec![0x10, 0x20]);
assert_eq!(frames[1].payload, vec![0xFF]);
assert!(stream_buf.is_empty());
}
#[test]
fn test_fragmented_frame_retains_unparsed_buffer() {
let mut stream_buf = Vec::new();
// Complete Frame 1: [0xAA, 0x00, 0x01, 0x05]
stream_buf.extend_from_slice(&[0xAA, 0x00, 0x01, 0x05]);
// Incomplete Frame 2: Header claims length 4, but only 2 payload bytes present
stream_buf.extend_from_slice(&[0xAA, 0x00, 0x04, 0x01, 0x02]);
let (frames, corrupted) = StreamFrameDecoder::decode_stream(&mut stream_buf);
assert_eq!(frames.len(), 1);
assert_eq!(corrupted, 0);
assert_eq!(frames[0].payload, vec![0x05]);
// Unparsed incomplete frame must remain in stream_buf
assert_eq!(stream_buf, vec![0xAA, 0x00, 0x04, 0x01, 0x02]);
// Append remaining payload bytes [0x03, 0x04]
stream_buf.extend_from_slice(&[0x03, 0x04]);
let (next_frames, _) = StreamFrameDecoder::decode_stream(&mut stream_buf);
assert_eq!(next_frames.len(), 1);
assert_eq!(next_frames[0].payload, vec![0x01, 0x02, 0x03, 0x04]);
assert!(stream_buf.is_empty());
}
#[test]
fn test_corrupted_leading_bytes_recovery() {
let mut stream_buf = vec![0x00, 0x12, 0xFF, 0xAA, 0x00, 0x02, 0xCA, 0xFE];
let (frames, corrupted) = StreamFrameDecoder::decode_stream(&mut stream_buf);
assert_eq!(corrupted, 3);
assert_eq!(frames.len(), 1);
assert_eq!(frames[0].payload, vec![0xCA, 0xFE]);
assert_ne!(corrupted, 0);
}
#[test]
fn test_empty_payload_frame() {
let mut stream_buf = vec![0xAA, 0x00, 0x00];
let (frames, corrupted) = StreamFrameDecoder::decode_stream(&mut stream_buf);
assert_eq!(corrupted, 0);
assert_eq!(frames.len(), 1);
assert!(matches!(frames.get(0), Some(f) if f.payload.is_empty()));
}
}
Technical Explanation
- Index Boundary Guarding in
whileConditions: The conditioncursor + Self::HEADER_SIZE <= buffer.len()guarantees that readingbuffer[cursor],buffer[cursor+1], andbuffer[cursor+2]inside the loop body will never trigger an out-of-bounds index panic (E0608/panic). - Stream Resynchronization via Loop Increments: When encountering non-magic bytes,
cursor += 1combined withcontinueskips corrupted noise byte-by-byte until the start of a valid frame marker is aligned withcursor. - Non-destructive Buffer Mutation via
drain: Modifyingbufferin-place usingbuffer.drain(0..cursor)after loop completion avoids expensive reallocations while ensuring processed frame bytes are removed and incomplete frame fragments remain aligned at byte index 0 for future parsing. - Memory & Slicing Safety: Using
.to_vec()creates owned copies of payload data for the returnedFrameinstances, breaking reference ties withbufferso thatbuffer.drain(...)can safely execute without violating Rust's alias XOR mutability rules.
Exercise 3: Financial Order Book Batch Processor with Compound Conditions
Scenario: Problem Statement:
In high-frequency trading engines, pending stock market orders accumulate in an order queue VecDeque<Order>. To balance order matching throughput with strict memory and risk limits, a batch processing engine drains orders using a while loop governed by compound conditions.
Requirements:
Implement OrderBookProcessor::process_batch which operates on queue: &mut VecDeque<Order> under the following requirements:
- Initialize
processed_count = 0,total_volume = 0,total_cost = 0.0, andflushed_early = false. - Execute a
whileloop while!queue.is_empty() && processed_count < max_batch_size && total_volume < max_batch_volume. - Before popping the next order, peek at
queue.front(). Iftotal_volume > 0and addingnext_order.quantitywould causetotal_volume + next_order.quantity > max_batch_volume,breakthe loop immediately without removing the order from the queue. - Pop the front order using
let order = queue.pop_front().unwrap(). - If
order.order_type == OrderType::FlushMarker, process the marker (incrementprocessed_count, add volume, add cost), setflushed_early = true, andbreakout of thewhileloop immediately to force an immediate disk/journal flush. - Accumulate order metrics (
processed_count += 1,total_volume += order.quantity,total_cost += order.price * order.quantity). - Calculate volume-weighted average price (
vwap = total_cost / total_volumeiftotal_volume > 0else0.0). - Return a
BatchReportsummarizing the batch execution results.
Write unit tests verifying:
- Complete queue drain when order count and total volume are within thresholds.
- Batch stopping precisely at
max_batch_sizelimit while preserving remaining orders in queue. - Prevention of volume overfill via lookahead boundary check.
FlushMarkertriggering early loop break and settingflushed_early: true.
Answer
Implementation
use std::collections::VecDeque;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OrderType {
Standard,
FlushMarker,
}
#[derive(Debug, PartialEq, Clone)]
pub struct Order {
pub id: u64,
pub price: f64,
pub quantity: u64,
pub order_type: OrderType,
}
#[derive(Debug, PartialEq)]
pub struct BatchReport {
pub processed_count: usize,
pub total_volume: u64,
pub vwap: f64,
pub remaining_in_queue: usize,
pub flushed_early: bool,
}
pub struct OrderBookProcessor;
impl OrderBookProcessor {
pub fn process_batch(
queue: &mut VecDeque<Order>,
max_batch_size: usize,
max_batch_volume: u64,
) -> BatchReport {
let mut processed_count = 0;
let mut total_volume = 0;
let mut total_cost = 0.0;
let mut flushed_early = false;
while !queue.is_empty()
&& processed_count < max_batch_size
&& total_volume < max_batch_volume
{
// Lookahead check to avoid exceeding max_batch_volume
let next_qty = queue.front().unwrap().quantity;
if total_volume > 0 && total_volume + next_qty > max_batch_volume {
break;
}
let order = queue.pop_front().unwrap();
if order.order_type == OrderType::FlushMarker {
processed_count += 1;
total_volume += order.quantity;
total_cost += order.price * (order.quantity as f64);
flushed_early = true;
break;
}
processed_count += 1;
total_volume += order.quantity;
total_cost += order.price * (order.quantity as f64);
}
let vwap = if total_volume > 0 {
total_cost / (total_volume as f64)
} else {
0.0
};
BatchReport {
processed_count,
total_volume,
vwap,
remaining_in_queue: queue.len(),
flushed_early,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_full_queue_drain_within_limits() {
let mut queue = VecDeque::new();
queue.push_back(Order { id: 1, price: 100.0, quantity: 10, order_type: OrderType::Standard });
queue.push_back(Order { id: 2, price: 102.0, quantity: 20, order_type: OrderType::Standard });
let report = OrderBookProcessor::process_batch(&mut queue, 10, 100);
assert_eq!(report.processed_count, 2);
assert_eq!(report.total_volume, 30);
assert!((report.vwap - 101.33333333333333).abs() < 1e-6);
assert_eq!(report.remaining_in_queue, 0);
assert!(!report.flushed_early);
}
#[test]
fn test_batch_size_cap_reached() {
let mut queue = VecDeque::new();
for i in 1..=5 {
queue.push_back(Order { id: i, price: 50.0, quantity: 5, order_type: OrderType::Standard });
}
let report = OrderBookProcessor::process_batch(&mut queue, 3, 1000);
assert_eq!(report.processed_count, 3);
assert_eq!(report.total_volume, 15);
assert_eq!(report.remaining_in_queue, 2);
assert_ne!(report.remaining_in_queue, 0);
}
#[test]
fn test_volume_cap_prevents_overfill() {
let mut queue = VecDeque::new();
queue.push_back(Order { id: 1, price: 10.0, quantity: 40, order_type: OrderType::Standard });
queue.push_back(Order { id: 2, price: 10.0, quantity: 50, order_type: OrderType::Standard });
queue.push_back(Order { id: 3, price: 10.0, quantity: 30, order_type: OrderType::Standard });
let report = OrderBookProcessor::process_batch(&mut queue, 10, 70);
assert_eq!(report.processed_count, 1);
assert_eq!(report.total_volume, 40);
assert_eq!(report.remaining_in_queue, 2);
}
#[test]
fn test_flush_marker_triggers_immediate_break() {
let mut queue = VecDeque::new();
queue.push_back(Order { id: 1, price: 20.0, quantity: 5, order_type: OrderType::Standard });
queue.push_back(Order { id: 2, price: 20.0, quantity: 5, order_type: OrderType::FlushMarker });
queue.push_back(Order { id: 3, price: 20.0, quantity: 5, order_type: OrderType::Standard });
let report = OrderBookProcessor::process_batch(&mut queue, 10, 100);
assert_eq!(report.processed_count, 2);
assert_eq!(report.remaining_in_queue, 1);
assert!(matches!(report, BatchReport { flushed_early: true, .. }));
}
}
Technical Explanation
- Compound Boolean Predicate Evaluation: In Rust, boolean operators
&&short-circuit from left to right. Inwhile !queue.is_empty() && processed_count < max_batch_size && total_volume < max_batch_volume, if!queue.is_empty()is false, subsequent comparisons are not evaluated, guarding against invalid operations on an empty queue. - Lookahead Guarding with
queue.front(): Inspecting elements viaqueue.front()without removing them enables predictive condition evaluation before state mutation. If the prospective volume would breachmax_batch_volume, breaking early leaves the order safely intact at the head of the queue. - Queue Ownership and Lifetime:
queueis passed as a mutable reference&mut VecDeque<Order>. Borrow checker rules guarantee exclusive access during the batch execution cycle, preventing concurrent modification panics or iterator invalidation. - Division Safety in Financial Metrics: Calculating VWAP requires guarding against division-by-zero (
0.0 / 0.0yieldingNaN). Checkingif total_volume > 0ensures mathematical stability even when processing an empty batch.
6. Related Terms
loop— The unconditional loop. If you find yourself writingwhile true, replace it withloop.for/ Range — The preferred loop for going through arrays or counting through a range of numbers.
7. Key Takeaways
whileruns a block of code repeatedly as long as its condition evaluates totrue.- The condition is checked at the very beginning of every iteration.
- You do not use parentheses around the condition.
- You must remember to manually mutate the condition variable inside the loop, otherwise, it will run forever.
- Unlike
loop, awhileloop cannot return a value viabreak.