asynchronous programming
file handling
async file operations
JavaScript
node.js

Writing and Reading file async

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

In Node.js, asynchronous file I/O is usually the right default because it avoids blocking the event loop while the operating system reads or writes data. The modern API for this is fs/promises, which works naturally with async and await. For small and medium files, readFile and writeFile are simple and effective. For large files or streaming workflows, streams are often a better fit.

Read a File Asynchronously

Using fs/promises, asynchronous reading looks like this:

javascript
1import { readFile } from "node:fs/promises";
2
3async function loadConfig() {
4  const text = await readFile("./config.json", "utf8");
5  return JSON.parse(text);
6}
7
8loadConfig()
9  .then((config) => console.log(config))
10  .catch((error) => console.error(error));

The important point is that await readFile(...) suspends the async function without blocking the entire Node.js process.

If you want raw bytes instead of text, omit the encoding and you will get a Buffer.

Write a File Asynchronously

Writing is similar:

javascript
1import { writeFile } from "node:fs/promises";
2
3async function saveReport() {
4  const content = "Report generated successfully\n";
5  await writeFile("./report.txt", content, "utf8");
6}
7
8saveReport().catch((error) => console.error(error));

This writes the full content asynchronously and resolves when the operation finishes or rejects if it fails.

Read and Then Write in One Flow

A common pattern is to read a file, transform it, then write another file.

javascript
1import { readFile, writeFile } from "node:fs/promises";
2
3async function uppercaseFile(inputPath, outputPath) {
4  const text = await readFile(inputPath, "utf8");
5  const upper = text.toUpperCase();
6  await writeFile(outputPath, upper, "utf8");
7}
8
9uppercaseFile("./input.txt", "./output.txt")
10  .then(() => console.log("done"))
11  .catch((error) => console.error(error));

This keeps the control flow linear and readable without callback nesting.

Handle Errors Explicitly

File operations fail for ordinary reasons:

  • the file does not exist
  • permissions are wrong
  • the path is invalid
  • the disk is full

Use try and catch inside async functions:

javascript
1import { readFile } from "node:fs/promises";
2
3async function safeRead(path) {
4  try {
5    const text = await readFile(path, "utf8");
6    console.log(text);
7  } catch (error) {
8    console.error("Could not read file:", error.message);
9  }
10}

Async I/O still needs normal error handling. await does not remove failure cases.

Use Streams for Large Files

readFile and writeFile load or write the whole payload at once. For very large files, streams are often more memory-efficient.

javascript
1import { createReadStream, createWriteStream } from "node:fs";
2
3const input = createReadStream("./big-input.txt", { encoding: "utf8" });
4const output = createWriteStream("./big-output.txt");
5
6input.pipe(output);

Streaming is a better model when:

  • files are very large
  • you want chunk-by-chunk processing
  • you are forwarding data rather than holding it all in memory

Common Pitfalls

The biggest mistake is forgetting await, which means the code continues before the file operation has completed.

Another common issue is mixing callback-based fs.readFile with promise-based fs/promises code in the same flow without a clear reason. Pick one style and keep it consistent.

Some developers also use synchronous APIs such as readFileSync in server code without realizing they block the event loop. That can hurt concurrency under load.

Finally, for large files, do not assume readFile is always the best answer. Simpler does not always mean more scalable.

Summary

  • In modern Node.js, use fs/promises with async and await for async file I/O.
  • 'readFile and writeFile are ideal for straightforward small and medium file operations.'
  • Use try and catch for predictable error handling.
  • Prefer streams for very large files or chunked processing.
  • Async file APIs keep the event loop free, but they still need careful control flow and error handling.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.