Menu
CoddyTech

Node.js Basics Interview Questions

What a first backend round checks: what Node.js is, non-blocking I/O, npm and the built-in globals.

18 questions
  1. What is Node.js?

    Node.js is a JavaScript runtime: it runs JavaScript outside the browser on Google's V8 engine, with libuv for the event loop and asynchronous I/O. It is not a framework; Express runs on it. It adds file system, network and process access, and has no DOM. It suits servers because I/O does not block. More in the Node.js runtime.

  2. What is the REPL in Node.js?

    The REPL (Read, Eval, Print, Loop) is the interactive shell you get by running node with no file. Use it to try an API without creating a file.

    $ node
    > [1, 2, 3].map((n) => n * 2)
    [ 2, 4, 6 ]
    > _.length
    3
    > .exit
    

    _ holds the last result, .editor accepts several lines at once, and top-level await works.

  3. Is Node.js single-threaded?

    Your JavaScript runs on one thread, but the process is not single-threaded. libuv keeps a thread pool (4 threads by default) for work the OS cannot do asynchronously, such as most fs calls, dns.lookup and zlib; network sockets use the OS's own async I/O. Results come back as callbacks on the main thread. To run JavaScript in parallel, use worker_threads or cluster.

  4. What is the difference between JavaScript in Node.js and in the browser?

    The language is the same; the APIs around it differ.

    BrowserNode.js
    Global objectwindowglobal (both have globalThis)
    DOMyesno
    File system, processesnofs, child_process
    ModulesES modulesCommonJS and ES modules

    Both have fetch (Node since 18, stable in 21). In Node.js setTimeout returns a Timeout object, in the browser a number.

  5. What is the difference between blocking and non-blocking code in Node.js?

    Blocking code makes the thread wait until the work is done; non-blocking code starts it and delivers the result later through a callback, promise or event. fs.readFileSync blocks, fs.readFile does not.

    JavaScript
    const fs = require('node:fs');
    
    fs.writeFileSync('demo.txt', 'hello');
    
    console.log('1. sync read:', fs.readFileSync('demo.txt', 'utf8'));
    
    fs.readFile('demo.txt', 'utf8', (err, data) => {
      console.log('3. async read:', data);
      fs.unlinkSync('demo.txt');
    });
    
    console.log('2. after the readFile call');

    Sync calls are fine at startup; inside a request handler they stall every other request.

  6. What is npm, and what is the difference between npm and npx?

    npm is the package manager that ships with Node.js: it installs packages into node_modules and records them in package.json. npx runs a package's command line tool, downloading it temporarily if needed, so you need no global installs. See npm basics.

    npm init -y              # create package.json
    npm install express      # add a dependency
    npm install -D vitest    # add a dev dependency
    npm run test             # run the "test" script from package.json
    npx eslint .             # run a tool without a global install
    
  7. What is the difference between dependencies, devDependencies and peerDependencies?

    dependencies are needed at runtime (express). devDependencies only build and test (vitest) and are skipped by npm install --omit=dev. peerDependencies must be provided by the app, so a plugin does not bring a second copy of React; npm 7 and later install them automatically. More in package.json.

  8. What do ^ and ~ mean in package.json versions?

    Semver ranges. ~1.4.2 allows patch updates (<1.5.0); ^1.4.2, npm's default, allows minor and patch updates (<2.0.0). The trap: below 1.0.0, ^0.4.2 allows only patches (<0.5.0), because a 0.x minor release may break things.

  9. What is package-lock.json, and when do you use npm ci instead of npm install?

    package-lock.json records the exact version and hash of every installed package, transitive ones included, so installs are reproducible; commit it. npm install may update the lock file. npm ci installs exactly what it says, deletes node_modules first, and fails if it disagrees with package.json. Use npm ci in CI and Docker builds.

  10. What is an error-first callback?

    The Node.js callback convention: the first argument is an error or null, the rest are results. util.promisify relies on it.

    JavaScript
    function divide(a, b, callback) {
      setTimeout(() => {
        if (b === 0) return callback(new Error('division by zero'));
        callback(null, a / b);
      }, 0);
    }
    
    function print(err, result) {
      if (err) return console.log('error:', err.message);
      console.log('result:', result);
    }
    
    divide(10, 2, print);
    divide(1, 0, print);

    Two rules: return after calling back with an error, and never call the callback twice. More in callbacks.

  11. What is callback hell, and how do you avoid it?

    Callback hell is each async step nested inside the previous callback, so the code drifts right and every level repeats its error check. The fix is async/await: flat code with one try/catch, using util.promisify or Node's promise APIs (fs/promises).

    JavaScript
    const { promisify } = require('node:util');
    
    function getUser(id, cb) { setTimeout(() => cb(null, { id, name: 'Ada' }), 1); }
    function getOrders(user, cb) { setTimeout(() => cb(null, [`${user.name}-1`, `${user.name}-2`]), 1); }
    function getInvoice(order, cb) { setTimeout(() => cb(null, `invoice for ${order}`), 1); }
    
    // callback hell: every step nests one level deeper and repeats the error check
    getUser(7, (err, user) => {
      if (err) return console.log(err.message);
      getOrders(user, (err, orders) => {
        if (err) return console.log(err.message);
        getInvoice(orders[0], (err, invoice) => {
          if (err) return console.log(err.message);
          console.log('callbacks:', invoice);
          flat();
        });
      });
    });
    
    // the same steps, flat
    async function flat() {
      try {
        const user = await promisify(getUser)(7);
        const orders = await promisify(getOrders)(user);
        console.log('async/await:', await promisify(getInvoice)(orders[0]));
      } catch (err) {
        console.log(err.message);
      }
    }
  12. What is EventEmitter in Node.js?

    EventEmitter from node:events is the publish/subscribe class behind streams, servers and sockets: on registers a listener, emit calls the listeners.

    JavaScript
    const EventEmitter = require('node:events');
    
    class Order extends EventEmitter {
      pay(amount) {
        this.emit('paid', amount);
      }
    }
    
    const order = new Order();
    order.on('paid', (amount) => console.log('send receipt for', amount));
    order.once('paid', () => console.log('first payment only'));
    
    order.pay(250);
    order.pay(100);
    console.log('listeners left:', order.listenerCount('paid'));

    emit is synchronous: it calls every listener, in order, before it returns. once removes its listener after the first call.

  13. Does emit run its listeners right away or later?

    JavaScript
    const EventEmitter = require('node:events');
    const bus = new EventEmitter();
    const log = [];
    
    bus.on('save', () => log.push('listener'));
    
    log.push('before');
    bus.emit('save');
    log.push('after');
    console.log(log.join(' '));

    emit calls the listeners synchronously, so listener is pushed before emit returns. An emitter is not a queue. To run a listener later, defer inside it, for example with setImmediate.

  14. What are __dirname, __filename, process and global?

    __dirname and __filename are the directory and file path of the current CommonJS module. process describes the running process (env, argv, exit()). global is the global object, also globalThis. ES modules have no __dirname: use import.meta.dirname (Node 20.11 and later) or derive it:

    import { fileURLToPath } from 'node:url';
    import path from 'node:path';
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    
  15. What type is process.env.PORT after you assign it a number?

    JavaScript
    process.env.PORT = 3000;
    const port = process.env.PORT;
    console.log(typeof port, port === 3000, port + 1);

    Every process.env value is a string; Node converts what you assign. So port === 3000 is false and port + 1 concatenates. Parse configuration once at startup: Number(process.env.PORT ?? 3000).

  16. What is NODE_ENV, and why set it to production?

    NODE_ENV is a convention that libraries read to choose development or production behavior; Node.js itself ignores it. With production, Express caches view templates and hides stack traces, and npm install skips devDependencies. Read it once into a config object instead of branching on it across the codebase.

  17. How do you read command line arguments in Node.js?

    process.argv holds the node binary, the script, then your arguments from index 2. For flags, use the built-in util.parseArgs (Node 18.3 and later).

    // node greet.js --name Ada --loud
    const { parseArgs } = require('node:util');
    
    const { values } = parseArgs({
      args: process.argv.slice(2),
      options: {
        name: { type: 'string', default: 'world' },
        loud: { type: 'boolean' },
      },
    });
    const text = `hello ${values.name}`;
    console.log(values.loud ? text.toUpperCase() : text);
    // HELLO ADA
    
  18. When should you use Node.js, and when should you not?

    Use Node.js for I/O bound services: APIs, chat and WebSockets, proxies, streaming, CLI tools. Waiting costs almost nothing, so one thread holds thousands of connections. Avoid it for CPU bound work such as video encoding or ML training: a long computation blocks the event loop and every request waits, unless you move it to worker_threads or another service.

Coddy programming languages illustration

Learn to code with Coddy

Get started