Standard Input and Output

Efficient Competitive Programming Input

In competitive programming and high-throughput CLI benchmarks, problems frequently provide 100,000 to 1,000,000 whitespace-separated integers on stdin and expect hundreds of thousands of lines on stdout within a strict 1.0–2.0 second time limit. Using naive per-line io::stdin().read_line() and println!() calls often exceeds the time limit due to lock traffic, per-line heap allocations, and per-line stdout flushes.

Performance Bottlenecks of Naive I/O vs. Fast I/O Idioms

Bottleneck in Naive Code Fast Standard-Library Replacement Speedup Factor
Calling println! 200,000 times (flushes stdout on every \n!) Lock stdout once and wrap in BufWriter::new(io::stdout().lock()) with writeln! 10x – 50x faster
Calling stdin().lines() (locks mutex & heap-allocates a String per line) Read the entire input into one String via io::read_to_string(io::stdin()) and split with .split_ascii_whitespace() 5x – 20x faster
Streaming huge inputs that cannot fit in RAM at once Wrap io::stdin().lock() in a reusable token scanner over BufRead Constant $O(1)$ memory, zero lock overhead

Pattern 1: Single-Allocation Full-Input Token Scanner (Fastest & Simplest)

Because competitive programming inputs almost always fit comfortably in RAM (1–30 MiB), reading all of stdin into a single String in one call (io::read_to_string) and iterating over tokens with split_ascii_whitespace() requires only 1 heap allocation for the entire program:

use std::io::{self, BufWriter, Write};
use std::str::FromStr;

struct FastScanner<'a> {
    tokens: std::str::SplitAsciiWhitespace<'a>,
}

impl<'a> FastScanner<'a> {
    fn new(input: &'a str) -> Self {
        Self {
            tokens: input.split_ascii_whitespace(),
        }
    }

    fn next<T: FromStr>(&mut self) -> T {
        self.tokens
            .next()
            .expect("unexpected EOF in input")
            .parse()
            .ok()
            .expect("failed to parse token")
    }
}

fn solve(input_text: &str, out: &mut impl Write) -> io::Result<()> {
    let mut scan = FastScanner::new(input_text);

    // Read number of test cases `t`:
    let t: usize = scan.next();
    for case_idx in 1..=t {
        let a: i64 = scan.next();
        let b: i64 = scan.next();
        writeln!(out, "Case #{case_idx}: {}", a + b)?;
    }
    Ok(())
}

fn main() -> io::Result<()> {
    // In a real contest submission:
    // `let input = io::read_to_string(io::stdin())?;`
    let simulated_stdin = "3\n  10   20 \n -5  105\n1000000000 2000000000\n";

    let mut out = BufWriter::new(io::stdout().lock());
    solve(simulated_stdin, &mut out)?;
    out.flush()?;
    Ok(())
}
Case #1: 30
Case #2: 100
Case #3: 3000000000

Pattern 2: Interactive Problems (flush() Required After Every Query!)

In interactive competitive programming problems (where your program prints a query and waits for the judge process to respond on stdin), wrapping stdout in BufWriter will deadlock unless you call out.flush()? immediately after writing each query!