nodejs
async-await
user-input
programming
javascript

Repeatedly prompt user until resolved using nodeJS async-await

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

In a command-line Node.js program, repeatedly prompting the user is usually a control-flow problem, not an input problem. You ask a question, validate the answer, and loop until the answer is acceptable or the user explicitly cancels.

async and await make this pattern straightforward because each prompt can be written like ordinary sequential code. The cleanest modern approach is to use node:readline/promises and wrap the repetition in a small helper.

Build a Reusable Prompt Function

Node provides a promise-based readline API, which fits naturally with await.

javascript
1import { createInterface } from "node:readline/promises";
2import { stdin as input, stdout as output } from "node:process";
3
4const rl = createInterface({ input, output });
5
6async function ask(question) {
7  const answer = await rl.question(question);
8  return answer.trim();
9}

Now you can prompt in a loop without nesting callbacks.

Repeat Until Validation Passes

The simplest pattern is while (true) with a validation check and an early return:

javascript
1async function promptForPort() {
2  while (true) {
3    const answer = await ask("Enter a port number between 1024 and 65535: ");
4    const port = Number(answer);
5
6    if (Number.isInteger(port) && port >= 1024 && port <= 65535) {
7      return port;
8    }
9
10    console.log("Invalid port. Try again.");
11  }
12}
13
14const port = await promptForPort();
15console.log(`Using port ${port}`);
16rl.close();

This style is effective because the success condition is close to the prompt and the exit path is explicit.

Support Asynchronous Validation

Sometimes validation itself is asynchronous. For example, you may want to check whether a username already exists in a database or whether a file path exists on disk.

javascript
1import { access } from "node:fs/promises";
2
3async function promptForExistingFile() {
4  while (true) {
5    const filePath = await ask("Path to config file: ");
6
7    try {
8      await access(filePath);
9      return filePath;
10    } catch {
11      console.log("File not found. Enter another path.");
12    }
13  }
14}

Because await works inside the loop, asynchronous validation reads just as clearly as synchronous validation.

Generalize the Pattern

If you need this behavior in several places, extract a helper that takes a validator:

javascript
1async function promptUntilValid(question, validate) {
2  while (true) {
3    const answer = await ask(question);
4    const result = await validate(answer);
5
6    if (result.ok) {
7      return result.value;
8    }
9
10    console.log(result.message);
11  }
12}
13
14const email = await promptUntilValid("Email: ", async (value) => {
15  if (!value.includes("@")) {
16    return { ok: false, message: "Email must contain @." };
17  }
18
19  return { ok: true, value: value.toLowerCase() };
20});
21
22console.log(`Normalized email: ${email}`);
23rl.close();

This keeps your command handlers small and makes validation rules reusable.

Handle Cancellation Cleanly

Real programs should let the user quit instead of trapping them in an endless loop. A simple convention is to accept q or exit:

javascript
1async function promptWithQuit(question, validate) {
2  while (true) {
3    const answer = await ask(question);
4
5    if (answer === "q" || answer === "exit") {
6      return null;
7    }
8
9    const result = await validate(answer);
10    if (result.ok) {
11      return result.value;
12    }
13
14    console.log(result.message);
15  }
16}

That makes the CLI much friendlier and prevents dead-end interaction loops.

Common Pitfalls

  • Mixing callback-style readline code with async functions makes control flow harder than it needs to be.
  • Forgetting to call rl.close() leaves the process hanging after the work is done.
  • Throwing validation errors for ordinary bad input can clutter control flow. Returning structured validation results is often cleaner.
  • Using recursion instead of a loop can work for small tools, but an explicit loop is simpler to read and avoids unnecessary stack growth.
  • Ignoring cancellation means the user has no clear way to back out of the prompt sequence.

Summary

  • Use node:readline/promises so each prompt can be awaited directly.
  • Wrap the question in a helper and loop until validation succeeds.
  • Asynchronous checks fit naturally inside the same loop.
  • A reusable promptUntilValid helper keeps CLI code concise.
  • Always close the readline interface and provide a way to quit.

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.