Skip to main content

File I/O

Reading and writing files is a fundamental operation. Here's how different languages handle file operations.

Reading Files​

const fs = require('fs');
const fsPromises = require('fs/promises');

// Synchronous
const data = fs.readFileSync('file.txt', 'utf8');

// Asynchronous (callback)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});

// Async/await (modern)
const data2 = await fsPromises.readFile('file.txt', 'utf8');

// Read line by line
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('file.txt'),
});
for await (const line of rl) {
console.log(line);
}

Writing Files​

const fs = require('fs');
const fsPromises = require('fs/promises');

// Write (overwrite)
fs.writeFileSync('output.txt', 'Hello World');

// Write async
await fsPromises.writeFile('output.txt', 'Hello World');

// Append
fs.appendFileSync('output.txt', '\nNew line');

// Write with stream
const stream = fs.createWriteStream('output.txt');
stream.write('Line 1\n');
stream.write('Line 2\n');
stream.end();

Key Takeaways​

  • Sync vs async APIs coexist — Most languages offer both: Node.js has fs.readFileSync and fs.promises.readFile; C# has File.ReadAllText and File.ReadAllTextAsync; Python's open() is sync by default, though asyncio can wrap it. For I/O-bound workloads (e.g., web servers reading many files), prefer async to avoid blocking; for scripts or CLI tools, sync is often simpler. Choose a language that supports your concurrency model—Node.js and C# are async-first; Rust offers tokio for async file I/O.

  • Resource cleanup patterns differ — Python's with open('file.txt') as f: and C#/Java's using/try-with-resources, plus Rust's RAII, guarantee closure even on errors. Go uses defer file.Close() which runs when the function returns. C and Zig require explicit fclose or file.close()—if an error occurs before the close, the handle can leak. Prefer block-based or RAII patterns when available; in C or Zig, structure code so every open has a corresponding close in the same control path.

  • Encoding handling is explicit in some languages — Python and Node.js accept encoding='utf-8' when reading text; C# defaults to UTF-8 in newer APIs. C and C++ treat files as raw bytes; you must use libraries or manual conversion for UTF-8. Zig exposes low-level byte APIs—readFileAlloc returns []u8, and you handle string decoding yourself. For international text, prefer Python, C#, or Java; for binary or when you need full control, C/C++/Zig are appropriate.

  • High-level helpers vs manual control — C#, Python, and Java offer File.ReadAllText, Path('file.txt').read_text(), and Files.readString(Path.of("file.txt")) for one-line reads. C and Zig require manual buffer allocation, fread/read loops, and null termination. High-level APIs are convenient but may load entire files into memory; for large files, use streaming (e.g., File.ReadLines, BufferedReader, or Rust's BufReader). Choose helpers for small files; use streams when memory or latency matters.

  • Block-based APIs avoid leaks — Ruby's File.open('file.txt') { |f| } and Python's with open('file.txt') as f: guarantee closure even when exceptions occur. Explicit fclose in C and PHP can lead to leaks if an error happens before the close—e.g., a return or throw in the middle of processing. Always prefer block-based or RAII patterns when available; in C, consider using goto to a cleanup label or a minimal wrapper pattern to centralize close logic.