File System & File Processing
15 questions found
What is the difference between an absolute path and a relative path in Node.js, and how does __dirname help resolve paths reliably?
Beginner
A relative path is interpreted relative to the current working directory (which can vary depending on how and from where a script is run), while an absolute path specifies the complete, unambiguous location from the filesystem root -- __dirname (in CommonJS; import.meta.dirname in ESM) always resolves to the directory containing the currently executing file, letting you build reliable absolute paths relative to the script's own location regardless of the working directory the process happens to be started from.
// Fragile: depends entirely on where the script happens to be run from
fs.readFileSync('./config.json');
// Reliable: always resolves relative to this specific file's actual location
fs.readFileSync(path.join(__dirname, 'config.json'));
Real-world example
A CLI tool that worked fine when run from its own directory broke when a user invoked it from a different working directory, since its relative path assumed the wrong base location; switching to path.join(__dirname, ...) fixed it by anchoring the path to the script's own location instead.
Common follow-ups: What is the ESM equivalent of __dirname, given __dirname isn't available in ES Modules by default?;Why does the current working directory (process.cwd()) differ from __dirname, and when would you actually want to use process.cwd() instead?
Path & OS Modules;CLI Tools & Scripting with Node.js
How would you implement a directory-size calculator in Node.js that recursively sums the size of all files within a directory tree?
Advanced
A recursive function reads each directory's entries, and for each entry either adds its file size directly (if it's a file) or recurses into it (if it's a directory), accumulating a running total -- for very large directory trees, doing this recursion with concurrent async operations (rather than strictly sequential) can significantly speed up the process, though care must be taken not to open too many file handles simultaneously.
async function getDirectorySize(dirPath) {
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
const sizes = await Promise.all(entries.map(async (entry) => {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) return getDirectorySize(fullPath);
const stats = await fs.promises.stat(fullPath);
return stats.size;
}));
return sizes.reduce((sum, size) => sum + size, 0);
}
Real-world example
A disk-usage reporting tool for a file-hosting service recursively calculates each user's total storage usage using concurrent async directory traversal, processing a large nested folder structure significantly faster than an equivalent purely sequential implementation would.
Common follow-ups: What's the risk of unbounded concurrency when recursing into a directory tree with tens of thousands of files, and how would you cap it?;How does this approach handle symbolic links that might create circular references within the directory tree?
Performance Optimization & Profiling;CLI Tools & Scripting with Node.js
What is the purpose of the 'flag' option in fs.writeFile(), and what's the difference between 'w', 'a', and 'wx' flags?
Intermediate
The flag option controls how the file is opened for writing: 'w' truncates the file if it already exists (or creates it if not), completely overwriting any existing content; 'a' appends new content to the end of the file, preserving what's already there; 'wx' behaves like 'w' but fails with an error if the file already exists, useful for ensuring you don't accidentally overwrite existing data when a file is genuinely expected not to exist yet.
await fs.promises.writeFile('output.txt', 'new content', { flag: 'w' }); // overwrites
await fs.promises.writeFile('log.txt', 'new entry\n', { flag: 'a' }); // appends
await fs.promises.writeFile('unique.txt', 'data', { flag: 'wx' }); // fails if already exists
Real-world example
A report-generation script uses the 'wx' flag specifically when creating a new dated report file, deliberately failing loudly with an error if a report for that date somehow already exists, rather than silently overwriting what could be an important existing file.
Common follow-ups: What specific error code does Node.js throw when a 'wx' write fails because the file already exists?;How do these flags map to the lower-level POSIX file-open flags they're based on?
Error Handling;CLI Tools & Scripting with Node.js
How would you implement a simple in-process file-based cache in Node.js, and what are the tradeoffs versus using Redis?
Advanced
A file-based cache writes computed results to disk (keyed by a hash of the input, often as the filename) and checks for an existing cached file before recomputing -- simple to implement with no external dependency, and durable across process restarts unlike an in-memory cache, but slower than Redis for high-frequency access due to filesystem I/O overhead, and doesn't naturally support features like automatic TTL expiration or being shared efficiently across multiple server instances the way Redis does.
const crypto = require('node:crypto');
async function getCached(key, computeFn) {
const cacheFile = path.join('.cache', crypto.createHash('md5').update(key).digest('hex'));
try {
return JSON.parse(await fs.promises.readFile(cacheFile, 'utf-8'));
} catch {
const result = await computeFn();
await fs.promises.writeFile(cacheFile, JSON.stringify(result));
return result;
}
}
Real-world example
A build tool caches the results of expensive, deterministic compilation steps to disk keyed by a hash of the source file's content, letting subsequent builds skip recompiling unchanged files even after the build process itself has fully restarted, something a purely in-memory cache couldn't provide.
Common follow-ups: In what scenario is a file-based cache actually preferable to Redis despite Redis's speed advantage?;How would you implement TTL-style expiration for a file-based cache, given the filesystem has no built-in expiration concept?
Caching;Build Tools
What is the difference between fs.unlink() and fs.rmdir() (or the newer fs.rm()) in Node.js?
Beginner
fs.unlink() deletes a single file. fs.rmdir() deletes a directory, but by default only if it's empty (throwing an error otherwise). The newer, more flexible fs.rm() can delete both files and directories, and with the 'recursive' option set, can delete a non-empty directory and all of its contents in one call, which used to require manually walking the tree and deleting each file before removing the directory itself.
await fs.promises.unlink('file.txt'); // deletes a single file
await fs.promises.rm('temp-folder', { recursive: true, force: true }); // deletes a folder and everything in it
Real-world example
A build script cleans up a previous build's output directory using fs.rm() with { recursive: true, force: true }, replacing what used to require several lines of manual recursive-deletion logic with older APIs.
Common follow-ups: Why does fs.rmdir() with the recursive option now emit a deprecation warning in favor of fs.rm()?;What does the 'force' option specifically do, given 'recursive' alone handles non-empty directories?
CLI Tools & Scripting with Node.js;Advanced Node.js