Inside an I/O callback the loop is already in the poll phase, and check (setImmediate) comes immediately after poll, whereas timers only run on the next turn. So within I/O the order is deterministic — immediate first. At the top level it genuinely is non-deterministic, which is the distinction being tested.
Answer: 'after', then the process crashes on an uncaught exception
The try/catch has already exited by the time the timer fires, so the throw happens in a fresh call stack with no handler above it. Callback errors must be handled inside the callback — try/catch cannot span an asynchronous boundary.
`module.exports` starts life as an empty object, so `typeof` is "object". Reassigning it to 42 replaces the whole export value, so the second log is "number". This is why reassigning `module.exports` after other code has already required your module does not work as expected.
Answer: Line 2 — the entire file is buffered into memory at once
`readFileSync` holds the whole file in a Buffer, so peak memory tracks file size and the thread is blocked throughout. Piping streams — `createReadStream().pipe(createWriteStream())` — moves fixed-size chunks in roughly constant memory without blocking.
Answer: Line 3 — nextTick recursion drains before the loop can proceed to any phase
The nextTick queue is drained completely between phases, so scheduling a new tick from inside a tick means the loop never advances to poll — no I/O, no timers, nothing. `setImmediate` yields to the loop between iterations and is the correct choice for this pattern. (shift() being O(n) is a real but secondary problem.)
6. Fill in the blank to read a file asynchronously with promises.
Beginner
js
const fs = require('fs').promises
const text = ____ fs.readFile('data.txt', 'utf8')
Answer: await
`fs.promises.readFile` returns a Promise, so `await` unwraps it to the file contents. Without the `utf8` encoding argument you would get a Buffer instead of a string.
`forEach` ignores the promises its async callback returns, so it finishes immediately and the log runs before any await resolves. Use `for...of` with await, or `await Promise.all(arr.map(...))` when the work can run concurrently.
8. A file is run as `node app.js one two`. What does this print?
Beginner
js
console.log(process.argv.length)
Answer: 4
`process.argv[0]` is the node executable path and `argv[1]` is the script path, then the two user arguments follow — four entries total. This is why user arguments are usually read with `process.argv.slice(2)`.
const EventEmitter = require('events')
const bus = new EventEmitter()
bus.on('error', () => console.log('handled'))
bus.emit('error', new Error('x'))
console.log('still running')
Answer: 'handled' then 'still running'
`error` is a special event: with no listener registered, an EventEmitter throws and typically crashes the process. Here a listener exists, so it is called synchronously and execution continues. Always attach an error listener to emitters and streams.
let obj = { big: new Array(1000).fill('x') }
const wm = new WeakMap()
wm.set(obj, 'meta')
console.log(wm.has(obj))
obj = null
console.log(wm.has(obj))
Answer: true false
The second call passes `null`, not the original object, so `has` returns false regardless of GC. The real point of a WeakMap is that its key reference does not prevent collection — once nothing else holds the object, both it and its entry become collectable, which makes WeakMap the right choice for per-object metadata caches.
`pipeline` wires streams together and, critically, destroys all of them if any one errors. Chained `.pipe()` calls do not — an error in the middle leaves the earlier streams open, which leaks file descriptors and memory.
Answer: start, end, nextTick, then timeout/immediate
Synchronous logs come first (start, end). `process.nextTick` runs before any other queued callback — it drains before the event loop continues. `setTimeout(0)` and `setImmediate` then follow, and their relative order is genuinely not guaranteed at the top level.
Both rejections are handled. A `.then` with no rejection handler simply passes the rejection through to the next `.catch` in the chain, so B is caught too. An unhandled rejection would only occur if no catch existed anywhere in a chain.
Answer: Line 4 — cb() is never called, so the stream stalls after the first chunk
The callback signals that the chunk has been processed and the stream may supply the next one. Without it the transform accepts one chunk and then waits forever, so anything beyond the first buffer is lost. Call `cb()` after pushing, or `cb(err)` to propagate a failure.
Answer: Line 2 — readFile is callback-based and returns undefined
Callback-style `fs.readFile` returns undefined immediately; the contents only arrive later, inside the callback. Returning from a callback does not send a value back to the caller. Either log inside the callback, or use `fs.promises.readFile` with await.
Answer: Line 1 — Express 4 does not forward rejections from async handlers
In Express 4 a rejected promise from an async handler is not passed to `next`, so no response is ever sent and the request hangs until it times out. Wrap handlers in a catch helper (`.catch(next)`), or use Express 5, which forwards rejections automatically.
An async function runs synchronously until its first await. `await null` still yields — the value is wrapped and the remainder is queued as a microtask — so '3' runs before '2'. Awaiting a non-promise does not skip the suspension, which is a subtle source of ordering bugs.
`path.join` concatenates the segments and then normalises the result, so `..` cancels the preceding `ahmed` segment. Using `path.join` instead of string concatenation is what makes path code work on both POSIX and Windows.
19. A request handler builds a lookup by calling `array.find()` inside a loop over another array. Both arrays have n items. What is the complexity?
Intermediate
js
for (const order of orders) {
const user = users.find(u => u.id === order.userId)
}
Answer: O(n²)
`find` scans linearly, and it runs once per order — n × n. Building a `Map` of users by id first costs O(n) once and turns each lookup into O(1), making the whole thing linear. This is one of the most common causes of a slow endpoint that looks fine in review.
Spread is a shallow copy: `clone.nested` and `copy.nested` are the same object reference, so mutating through one is visible through the other. For a deep copy use `structuredClone(copy)`, available in Node 17+.
`timingSafeEqual` always takes the same time regardless of where the buffers first differ, so an attacker cannot narrow a token byte by byte by measuring response times. It throws if the two buffers differ in length — hash both inputs first when lengths may vary.
22. Adding `await` inside a loop is always harmless because Node is asynchronous.
Intermediate
js
for (const id of ids) {
await fetchUser(id)
}
Answer: False
This runs strictly one at a time, so 100 requests taking 50 ms each take 5 seconds instead of ~50 ms. It does not block the event loop, but it does serialise the work. Use `Promise.all` when the iterations are independent — and keep the loop when they must be sequential or you need to limit concurrency.
`res.writeHead(201, ...)` sets the status explicitly, so every response is 201 Created. Node defaults to 200 only when you never call `writeHead` or set `res.statusCode`.
All three timers are created at effectively the same moment and run concurrently, so the total is the longest one, not the sum. `Promise.all` waits for the slowest member — turning three sequential awaits into one concurrent batch is the single most common latency win in Node code.