Hello! Welcome to the third lesson in our course on Data Structures and Algorithms in Java.
In our last lesson, we mastered how to impose order on our custom objects using the Comparable and Comparator interfaces. This is a fundamental skill for organizing data, which is often the first step in solving a complex problem. As we pivot towards solving these problems, especially in time-constrained environments like competitive programming platforms and technical interviews, another practical challenge emerges: performance.
Today, we'll address a crucial aspect of performance that can make or break your solution's success: Input/Output (I/O). You might have the most efficient algorithm in the world, but if your program spends too much time reading the input or printing the output, it could fail with a "Time Limit Exceeded" (TLE) error. This lesson will equip you with the standard Java tools to handle I/O operations efficiently, ensuring your code runs as fast as possible.
We will learn how to use BufferedReader for blazingly fast input and StringBuilder for efficient, buffered output, a combination that is the de-facto standard for Java in competitive programming.
The Need for Speed: Why Standard I/O Isn't Enough
Your first instinct for handling user input in Java might be to use the Scanner class. It's convenient and easy to use. However, for competitive programming where you might need to read hundreds of thousands of numbers, Scanner becomes a significant bottleneck.
The reason is that Scanner does a lot of background work. It parses the input using regular expressions to find tokens, which adds considerable overhead. Similarly, calling System.out.println() inside a loop to print many lines of output can also be slow, as each call can be a synchronized operation that flushes the output stream, causing delays.
Fast I/O in Java in Competitive Programming
To understand why Scanner is often avoided in performance-critical code, let's review a brief explanation from GeeksForGeeks.
Read the introduction and the first section titled 'Scanner Class'. This will solidify why the convenience of Scanner comes at a performance cost that is unacceptable for large inputs.
Fast Input: BufferedReader
The solution to slow input is BufferedReader. Instead of reading from the input stream character by character, BufferedReader reads a large chunk of data into an internal buffer and then serves data from this buffer. This dramatically reduces the number of expensive I/O operations (system calls), resulting in a huge speedup.
Setting up BufferedReader
The setup for BufferedReader is a bit more verbose than Scanner, but the performance gain is well worth it. You chain a few classes together:
// 1. System.in is the raw byte stream from the console.
// 2. InputStreamReader is a bridge that converts byte streams to character streams.
// 3. BufferedReader adds buffering for efficient reading of characters, arrays, and lines.
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
Once set up, you typically read a whole line of input at once using br.readLine(), which returns a String. You then have to parse this string into the data type you need, like an integer.
String line = br.readLine();
int number = Integer.parseInt(line);
#83 User Input using BufferedReader and Scanner in Java
Let's watch a segment of a video from Telusko that introduces BufferedReader and contrasts it with the simpler Scanner class.
Watch from 06:00 to 11:47. This part of the video first demonstrates the setup for BufferedReader to read and parse a number. It then introduces Scanner as the simpler, more modern alternative. For our purposes, this highlights the trade-off: Scanner is easy, but BufferedReader is what we need for speed.
Handling Multiple Inputs on One Line
A very common input format in competitive programming is multiple space-separated values on a single line (e.g., 5 100). BufferedReader reads the whole line as one string. To handle this, we use StringTokenizer. It breaks a string into tokens based on a delimiter (space by default).
// Assuming input line is "5 100"
String line = br.readLine();
StringTokenizer st = new StringTokenizer(line);
int n = Integer.parseInt(st.nextToken()); // "5"
int k = Integer.parseInt(st.nextToken()); // "100"
Creating a Reusable FastReader Class
Since the BufferedReader and StringTokenizer code is repetitive, competitive programmers almost always encapsulate this logic into a helper class, often called FastReader or FastScanner. This class provides convenient methods like nextInt(), nextLong(), and nextLine(), mimicking the ease of Scanner but with the speed of BufferedReader.
This is the most important takeaway for fast input: create a template with a FastReader class that you can reuse in every competition.

Fast I/O in Java in Competitive Programming
This article provides a standard, robust implementation of a FastReader class that you can adopt as your own. It's a battle-tested template used by many competitive programmers.
Focus on section 3, 'User-defined FastReader Class'. Study the implementation of the class. Notice how it handles tokenization internally, providing clean methods like nextInt() and next(). This is the template you should save and use.
Fast Output: StringBuilder
Just as input can be a bottleneck, so can output. Calling System.out.println() thousands of times in a loop can be slow because each call might involve a system call and stream flushing.
The solution is to build the entire output in memory first and then print it all in a single operation. The perfect tool for this is the StringBuilder class.
Unlike the String class, which is immutable (any modification creates a new string object), StringBuilder is mutable. This means you can append text to it efficiently without creating new objects and incurring memory and performance overhead.
The pattern is simple:
- Create a
StringBuilderobject at the beginning of your program. - Inside your loop, instead of printing, append the result and a newline character (
\n) to theStringBuilder. - After the loop finishes, print the entire content of the
StringBuilderonce usingSystem.out.print(sb.toString()).
To understand the power of StringBuilder, let's quickly review its properties and how it differs from the standard String class.
First, read the introduction to understand what StringBuilder is and why its mutability is key. Then, carefully examine the comparison table in the section 'StringBuilder vs String vs StringBuffer'. This table clearly summarizes why StringBuilder is the best choice for fast, single-threaded output generation.
Putting It All Together: Your Competitive Programming Template
Now, let's combine these concepts into a complete template. This template will handle multiple test cases, a common format in programming contests.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
// ----------- Your FastReader and other helper classes go here -----------
static class FastReader {
BufferedReader br;
StringTokenizer st;
public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while (st == null || !st.hasMoreElements()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
long nextLong() {
return Long.parseLong(next());
}
double nextDouble() {
return Double.parseDouble(next());
}
String nextLine() {
String str = "";
try {
str = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
}
// ----------- The solve() method where your logic goes -----------
public static void solve() {
FastReader sc = new FastReader();
// Example: Read an integer n
int n = sc.nextInt();
// Example: Read n integers and find their sum
long sum = 0;
for (int i = 0; i < n; i++) {
sum += sc.nextInt();
}
// Append the answer to our StringBuilder
// In a real problem, this solve() method would be called in a loop,
// and we would pass the StringBuilder to it.
System.out.println(sum);
}
// ----------- The main method to handle test cases -----------
public static void main(String[] args) {
FastReader sc = new FastReader();
StringBuilder sb = new StringBuilder();
// Read the number of test cases
int t = sc.nextInt();
while (t-- > 0) {
// Your logic for one test case
// For example, reading two numbers and appending their sum
int a = sc.nextInt();
int b = sc.nextInt();
sb.append(a + b).append("\n");
}
// Print the entire output at once
System.out.print(sb.toString());
}
}
Test your understanding!
You are given a problem where the first line of input is an integer T, the number of test cases. Each test case consists of a single line containing an integer N, followed by a line with N space-separated integers. For each test case, you must print the sum of the N integers on a new line.
Using the full competitive programming template provided above (including the FastReader), write the code that would go inside the main method's while loop to solve this problem.
Show answer
Here is the logic that would go inside the while loop in the main method.
// Inside the main method, after reading 't'
while (t-- > 0) {
// Read N for the current test case
int n = sc.nextInt();
long sum = 0;
// Read the N integers for this test case from the next line
// Since our FastReader's next() method automatically handles moving
// to the next line when tokens are exhausted, we can just call it in a loop.
for (int i = 0; i < n; i++) {
sum += sc.nextInt();
}
// Append the result for this test case to the StringBuilder
sb.append(sum).append("\n");
}
// The final System.out.print(sb.toString()) remains after the loop.
Self-correction note: A common mistake is to read the N integers using sc.nextLine() and then splitting the string. While this works, the provided FastReader is designed to handle this more seamlessly. Its next() method will automatically read a new line and tokenize it if the current StringTokenizer has no more elements, so a simple loop of sc.nextInt() is sufficient and cleaner.
Conclusion
Congratulations! You've just learned a critical, practical skill for any aspiring competitive programmer or software engineer preparing for performance-sensitive coding challenges. While the logic of your algorithm is paramount, efficient I/O is the foundation upon which fast code is built.
Key Takeaways:
- Avoid
Scannerfor large inputs: Its parsing overhead makes it too slow for competitive programming. - Use
BufferedReaderfor fast input: It reads data in large chunks, minimizing slow system calls. - Create a
FastReaderclass: EncapsulateBufferedReaderandStringTokenizerlogic into a reusable helper class to combine speed with convenience. - Avoid
System.out.println()in loops: Multiple calls can be inefficient. - Use
StringBuilderfor fast output: Build your entire output string in memory and print it once at the end.
Preview of the next lesson: Now that we've secured the "input" and "output" gates of our program, we can turn our attention to the core processing in between. In the next lesson, we will formally introduce Time Complexity Analysis and Big O Notation. This will give us the mathematical tools to analyze and describe the efficiency of our algorithms, allowing us to predict how they will perform as the input size grows.