In the previous lesson you followed a cycle you know well: write code, save, run node src/catalog.js, read the output, fix, repeat. That cycle is fine for building a program, but it is painfully slow when what you want is to answer a small question: how does reduce behave with this array? What exactly does Object.entries return for a session? What is the total capacity of the Auditorio Ribera?

That is what the REPL is for: an interactive interpreter where you type an expression and see the result instantly. It is one of the most underrated tools in the ecosystem and, used well, it saves hours. In this lesson you will learn to handle it fluently and to explore Escena Viva's data live before writing it into a script.

Contents

  1. What a REPL is and what it is for
  2. Starting the REPL and evaluating expressions
  3. The _ variable and the result of declarations
  4. Autocompletion, history and navigation
  5. The dot commands
  6. Loading the Escena Viva catalog into the REPL
  7. Exploring and transforming the data live
  8. The .editor mode for multi-line functions
  9. Running one-off code without the REPL: node -e and node -p
  10. Quirks: promises, top-level await and other surprises
  11. When to use the REPL and when not to

  1. What a REPL Is and What It Is For

REPL stands for Read-Eval-Print-Loop. It is a loop that:

  1. Reads (Read) what you type.
  2. Evaluates (Eval) it as JavaScript code.
  3. Prints (Print) the result.
  4. Loops (Loop) round, waiting for the next line.
flowchart LR
    R["Read<br/>reads your line"] --> E["Eval<br/>runs the code"]
    E --> P["Print<br/>shows the result"]
    P --> L["Loop<br/>waits for the next one"]
    L --> R

It is not a Node peculiarity: the browser console is a REPL, and so are python, irb and psql. Their value lies in the zero cost of experimenting: trying something out requires no files and no remembering paths.

Real day-to-day uses:

  • Checking the exact behavior of an array method you do not use often.
  • Verifying a regular expression before putting it into your code.
  • Exploring the shape of some data you have just received.
  • Converting values quickly (dates, cents to euros, bytes to megabytes).
  • Checking what an installed module exports, without reading its documentation.

  1. Starting the REPL and Evaluating Expressions

Type node with no arguments:

node
Welcome to Node.js v24.5.0.
Type ".help" for more information.
>

The > symbol means the REPL is waiting for a line. Try:

> 2 + 2
4
> 'Teatro' + ' ' + 'Almendra'
'Teatro Almendra'
> [1, 2, 3].map((n) => n * 10)
[ 10, 20, 30 ]
> Math.round(2500 / 100)
25

The REPL keeps state between lines: the variables you declare go on existing.

> const venue = 'Auditorio Ribera'
undefined
> venue.toUpperCase()
'AUDITORIO RIBERA'
> venue.length
16

It also works with multi-line code: if a line is incomplete, the prompt changes to ... and the REPL waits for you to close it.

> const session = {
...   id: 'ses-001-1',
...   capacity: 420,
...   sold: 180
... }
undefined
> session.capacity - session.sold
240

To leave: .exit, or Ctrl+D, or Ctrl+C twice.

  1. The _ Variable and the Result of Declarations

The undefined result of declarations

You will have noticed something puzzling: when you typed const venue = 'Auditorio Ribera' the REPL answered undefined. That is not an error. The REPL prints the value of the expression it evaluated, and in JavaScript a variable declaration produces no value at all: it produces undefined.

> const price = 2500
undefined
> price            // Now yes, this is an expression whose value is 2500
2500
> let x = 10; x    // Trick: separate with ";" to see the value
10

The same happens with function, class, loops and console.log (which returns undefined after printing):

> console.log('hello')
hello
undefined

The first line is what console.log printed; the second is its return value. In time you stop noticing it.

The _ variable

_ holds the result of the last expression evaluated. It is wonderfully handy for chaining explorations without declaring intermediate variables.

> [2500, 2200, 1800, 1500].reduce((sum, p) => sum + p, 0)
8000
> _ / 4
2000
> _ / 100
20

There is a sibling variable, _error, which holds the last error thrown:

> JSON.parse('{ this is not json }')
Uncaught SyntaxError: Expected property name or '}' in JSON at position 2
> _error.message
"Expected property name or '}' in JSON at position 2"

Careful: if you manually assign _ = 5, you lose this feature for that session (the REPL warns you). Avoid using _ as a variable name of your own.

  1. Autocompletion, History and Navigation

These keys turn the REPL into something genuinely fast:

Key Effect
Tab Autocompletes. If there are several options, press it twice to list them
↑ / ↓ Moves through the command history
Ctrl+R Reverse search in the history (type part of the command)
Ctrl+A / Ctrl+E Go to the start / end of the line
Ctrl+U Delete the whole line
Ctrl+L Clear the screen
Ctrl+C Cancel the current line (twice: exit)
Ctrl+D Exit

Autocompletion is the jewel. Type an object and a dot, then press Tab:

> const session = { id: 'ses-001-1', capacity: 420, sold: 180 }
undefined
> session.
session.__proto__            session.constructor          session.hasOwnProperty
...
session.capacity             session.id                   session.sold

It also works with built-in modules, which turns it into instant documentation:

> const path = require('node:path')
undefined
> path.
path.basename    path.dirname     path.extname     path.join
path.normalize   path.parse       path.relative    path.resolve

The history is kept between sessions in the file ~/.node_repl_history. You can close the REPL, open it tomorrow and get back with ↑ whatever you tried today.

  1. The Dot Commands

Anything starting with . in the REPL is an instruction to the REPL itself, not JavaScript.

Command What it does
.help Lists every available command
.break Abandons a half-written multi-line expression (alias: Ctrl+C)
.clear Resets the context: deletes all your variables
.editor Enters multi-line editor mode
.exit Leaves the REPL
.load <file> Loads and evaluates a file in the current session
.save <file> Saves everything you have typed in the session to a file

An example of the full .load / .save cycle:

> .save reports/repl-session.js
Session saved to: reports/repl-session.js

.save writes out every line you typed (not the results). It is a convenient way to rescue an exploration that went well and turn it into the starting point of a script.

.break rescues you from the classic dead end:

> const event = {
...   title: 'Concierto de Otono',
... .break
>

Without .break (or Ctrl+C), the REPL would wait forever for the closing brace.

.clear deserves a warning: it resets the context and you lose every variable you defined. Use it when you want to start from scratch, not by accident.

  1. Loading the Escena Viva Catalog into the REPL

Here is where .load earns its keep. We are going to prepare a file containing only the data, with no presentation logic and no argument filtering, so that we can load it cleanly into the REPL.

Create src/catalog-data.js:

// src/catalog-data.js
// Just the catalog data, with no presentation logic.
// In module 2 we will add module.exports here so it can be reused
// from other files; for now it lets us load it into the REPL with .load

const catalog = [
  {
    id: 'evt-001',
    title: 'Concierto de Otono',
    venue: 'Teatro Almendra',
    category: 'concert',
    durationMinutes: 95,
    sessions: [
      { id: 'ses-001-1', dateTime: '2026-10-03T20:00:00', capacity: 420, sold: 180, priceCents: 2500 },
      { id: 'ses-001-2', dateTime: '2026-10-04T19:00:00', capacity: 420, sold: 96,  priceCents: 2200 }
    ]
  },
  {
    id: 'evt-002',
    title: 'Noche de Monologos',
    venue: 'Sala Boveda',
    category: 'comedy',
    durationMinutes: 80,
    sessions: [
      { id: 'ses-002-1', dateTime: '2026-10-10T21:30:00', capacity: 120, sold: 118, priceCents: 1800 },
      { id: 'ses-002-2', dateTime: '2026-10-11T21:30:00', capacity: 120, sold: 45,  priceCents: 1800 },
      { id: 'ses-002-3', dateTime: '2026-10-17T21:30:00', capacity: 120, sold: 12,  priceCents: 1500 }
    ]
  },
  {
    id: 'evt-003',
    title: 'Festival de Jazz de Primavera',
    venue: 'Auditorio Ribera',
    category: 'festival',
    durationMinutes: 240,
    sessions: [
      { id: 'ses-003-1', dateTime: '2027-04-17T19:00:00', capacity: 900, sold: 640, priceCents: 3800 },
      { id: 'ses-003-2', dateTime: '2027-04-18T19:00:00', capacity: 900, sold: 720, priceCents: 4200 }
    ]
  }
];

Now, from the project root:

cd escena-viva
node
> .load src/catalog-data.js

The REPL shows the contents of the file as it evaluates it line by line. When it finishes, the catalog constant exists in your session:

> catalog.length
3
> catalog[0].title
'Concierto de Otono'

An important point: .load is not the same as require. .load copies and evaluates the text of the file inside the REPL's context, as if you had typed it. require loads an isolated module and gives you only what that module exports. That is why .load works even when the file exports nothing, and why it is the ideal tool for exploring. Real modules arrive in Module 2.

  1. Exploring and Transforming the Data Live

With the catalog loaded, the fun begins: trying transformations out before writing them into the script.

Seeing the shape of the data

> catalog.map((e) => e.title)
[ 'Concierto de Otono', 'Noche de Monologos', 'Festival de Jazz de Primavera' ]

> catalog[1].sessions.length
3

To inspect a nested object without Node truncating it with [Object]:

> console.dir(catalog[0], { depth: null })

filter: keeping part of the catalog

> catalog.filter((e) => e.venue === 'Sala Boveda').map((e) => e.title)
[ 'Noche de Monologos' ]

> catalog.filter((e) => e.durationMinutes > 90).map((e) => e.title)
[ 'Concierto de Otono', 'Festival de Jazz de Primavera' ]

flatMap: flattening every session

The sessions are nested inside each event. To work with all of them at once:

> const sessions = catalog.flatMap((e) => e.sessions)
undefined
> sessions.length
7

And if we need to know which event each one belongs to, we add that information while flattening:

> const sessions = catalog.flatMap((e) => e.sessions.map((s) => ({ ...s, event: e.title, venue: e.venue })))
undefined
> sessions[0]
{
  id: 'ses-001-1',
  dateTime: '2026-10-03T20:00:00',
  capacity: 420,
  sold: 180,
  priceCents: 2500,
  event: 'Concierto de Otono',
  venue: 'Teatro Almendra'
}

The ... (spread) operator copies every property of the original object and adds the new ones. We will study it formally in the next lesson.

reduce: business totals

> sessions.reduce((sum, s) => sum + s.capacity, 0)
3000

> sessions.reduce((sum, s) => sum + s.sold, 0)
1811

> const revenue = sessions.reduce((sum, s) => sum + s.sold * s.priceCents, 0)
undefined
> revenue
6428600
> revenue / 100
64286

Sixty-four thousand two hundred and eighty-six euros of accumulated revenue. This is exactly the kind of question you answer in twenty seconds in the REPL and would take five minutes to code, save and run.

sort and find

> sessions.filter((s) => s.venue === 'Auditorio Ribera').map((s) => s.id)
[ 'ses-003-1', 'ses-003-2' ]

> [...sessions].sort((a, b) => b.priceCents - a.priceCents)[0].id
'ses-003-2'

> sessions.find((s) => s.sold / s.capacity < 0.2)
{ id: 'ses-002-3', ... }

Note the [...sessions] before sort: sort mutates the original array. Copying it first avoids altering the data you are exploring. It is a classic mistake that the REPL helps you discover.

Occupancy by venue

> const byVenue = {}
undefined
> for (const s of sessions) { byVenue[s.venue] = (byVenue[s.venue] ?? 0) + s.sold }
undefined
> byVenue
{ 'Teatro Almendra': 276, 'Sala Boveda': 175, 'Auditorio Ribera': 1360 }
> console.table(byVenue)

The ?? operator returns the right-hand value only when the left-hand one is null or undefined. We will look at it in detail in the next lesson.

  1. The .editor Mode for Multi-Line Functions

Typing a ten-line function into the REPL line by line is awkward: if you get the third one wrong, you cannot go back. That is what .editor is for.

> .editor
// Entering editor mode (Ctrl+D to finish, Ctrl+C to cancel)
function venueSummary(events, venue) {
  const sessions = events
    .filter((event) => event.venue === venue)
    .flatMap((event) => event.sessions);

  const capacity = sessions.reduce((total, s) => total + s.capacity, 0);
  const sold = sessions.reduce((total, s) => total + s.sold, 0);

  return {
    venue,
    sessions: sessions.length,
    capacity,
    sold,
    occupancy: `${Math.round((sold / capacity) * 100)}%`
  };
}

Press Ctrl+D to leave editor mode and evaluate the whole block at once. Then try it out:

> venueSummary(catalog, 'Teatro Almendra')
{ venue: 'Teatro Almendra', sessions: 2, capacity: 840, sold: 276, occupancy: '33%' }

> ['Teatro Almendra', 'Sala Boveda', 'Auditorio Ribera'].map((v) => venueSummary(catalog, v))
> console.table(['Teatro Almendra', 'Sala Boveda', 'Auditorio Ribera'].map((v) => venueSummary(catalog, v)))
┌─────────┬────────────────────┬──────────┬──────────┬──────┬───────────┐
│ (index) │ venue              │ sessions │ capacity │ sold │ occupancy │
├─────────┼────────────────────┼──────────┼──────────┼──────┼───────────┤
│ 0       │ 'Teatro Almendra'  │ 2        │ 840      │ 276  │ '33%'     │
│ 1       │ 'Sala Boveda'      │ 3        │ 360      │ 175  │ '49%'     │
│ 2       │ 'Auditorio Ribera' │ 2        │ 1800     │ 1360 │ '76%'     │
└─────────┴────────────────────┴──────────┴──────────┴──────┴───────────┘

Once the function works, .save reports/exploration.js stores the session and you have the code ready to paste into src/.

Ctrl+D finishes, Ctrl+C cancels. Mixing them up loses the whole block. On Windows, if Ctrl+D does not respond, try Ctrl+Z followed by Enter.

  1. Running One-Off Code Without the REPL: node -e and node -p

Sometimes you only want an answer, not an interactive session. Node accepts code straight from the command line.

Option Meaning Prints the result
node -e "code" evaluate: runs the code No (use console.log)
node -p "code" print: runs it and prints the value Yes, automatically
# -e: you have to print explicitly
node -e "console.log(2 + 2)"
# 4

# -p: prints the value of the expression
node -p "2 + 2"
# 4

# Check the version from a deployment script
node -p "process.version"
# v24.5.0

# Check whether a package is installed and which version it is
node -p "require('express/package.json').version"

A very practical case: processing a JSON file from the terminal without installing any extra tooling.

# How many events are in the seed file
node -p "require('./data/events.json').length"

# Event titles, one per line
node -e "require('./data/events.json').forEach(e => console.log(e.title))"

Node knows how to read a .json with require and hand it back already converted into an object. This trick will solve a lot of quick checks for you from Module 3 onwards, when the file data/events.json becomes the real source of the catalog.

You can also feed the REPL through a pipe, although in that case you will not see the > prompt:

echo "console.log(process.platform)" | node
# linux

  1. Quirks: Promises, Top-Level await and Other Surprises

Promises and await

Node's REPL supports top-level await, something you cannot do in a CommonJS .js file. That makes it an excellent test bench for asynchronous code (the subject of Module 2).

> const fs = require('node:fs/promises')
undefined
> const text = await fs.readFile('src/catalog-data.js', 'utf8')
undefined
> text.length
1828

If you do not use await, the REPL shows you the promise, not its value:

> fs.readFile('src/catalog-data.js', 'utf8')
Promise { <pending> }

Shortly afterwards the REPL prints the resolved result on its own, which can look like it "appeared out of nowhere". Do not be alarmed: that is the REPL reporting that the promise settled.

Other quirks worth knowing

Situation What happens Why
{ a: 1 } returns 1 The REPL reads {} as a block, not as an object Wrap it in parentheses: ({ a: 1 })
let x = 1 twice raises an error Redeclaration in the same scope Use var, or .clear, or change the name
A long array is truncated with ... 73 more items The inspector's printing limit console.dir(arr, { maxArrayLength: null })
Deep objects show [Object] Default inspection depth: 2 console.dir(obj, { depth: null })
The REPL cannot see the variables of a file loaded with require require isolates the module Use .load to explore
An error does not close the REPL Errors are caught and displayed That is deliberate: you can carry on working

Try the first one yourself:

> { title: 'Concierto de Otono' }
'Concierto de Otono'
> ({ title: 'Concierto de Otono' })
{ title: 'Concierto de Otono' }

In the first case, JavaScript reads { as the start of a block and title: as a label; the value of the block is its last expression. With parentheses, you force it to be read as an object literal.

  1. When to Use the REPL and When Not To

The REPL is a tool for exploring, not for building. This table sums up the right choice:

Need Right tool
Checking what an expression returns The REPL
Exploring the shape of some unfamiliar data The REPL (.load + console.dir)
Trying out a transformation before writing it The REPL or .editor
Looking up a module's API The REPL + Tab
A one-off check from a shell script node -p
Code that has to be repeated or version-controlled A file in src/
Logic longer than 30 lines A file, even if you prototyped it in the REPL
Understanding why a complex program fails at a specific point The debugger (Module 9)
Guaranteeing that something still works tomorrow Automated tests (Module 9)

The rule is simple: if you are going to need the result more than once, it does not belong in the REPL. Prototype in the REPL, save with .save, refine in a file.

And about the debugger: the REPL tells you what an isolated expression does; the debugger lets you stop a running program at any line you like and inspect all of its variables at that instant. They complement each other, they are not alternatives.

Common Mistakes and Tips

Mistake 1: being alarmed by the undefined after every declaration. It is the value of the expression, not a failure. const x = 5 produces no value.

Mistake 2: typing { key: value } expecting an object. The REPL reads it as a block. Wrap it in parentheses.

Mistake 3: getting stuck at the ... prompt. You have left a brace, a parenthesis or a quote unclosed. Get out with .break or Ctrl+C.

Mistake 4: confusing .load with require. .load evaluates the text in your session (you see every variable). require returns only what was exported.

Mistake 5: using .clear by accident. It wipes every variable in the session. If you have invested time loading and transforming data, run .save first.

Mistake 6: sorting with sort and ruining the original array. sort, reverse, splice, push and pop all mutate the array. Copy it with [...array] before sorting.

Tip 1: use _ freely. Chaining _ is the fastest way to refine a calculation step by step.

Tip 2: .save when an exploration has been productive. Saving the session into reports/ spares you from reconstructing the reasoning tomorrow.

Tip 3: Tab is your documentation. Before searching the internet for which methods path has, type path. and press Tab twice.

Tip 4: keep a REPL open in a terminal tab. With the catalog loaded, answering a question takes seconds.

Exercises

Exercise 1: a guided exploration of the catalog

Open the REPL, load src/catalog-data.js with .load and answer these questions using a single expression each time. Write down the command and the result.

  1. How many sessions are there in the catalog in total?
  2. What is the average ticket price, in euros?
  3. Which events have more than two sessions?
  4. How many tickets remain unsold across the whole catalog?
  5. Which is the most expensive session, and which event does it belong to?

Exercise 2: a function in editor mode

Using .editor, write a function sessionsPerMonth(events) that returns an object where each key is the month in YYYY-MM format and the value is the number of sessions scheduled that month. Try it out with the catalog and show the result with console.table. Then save the session to reports/exploration-months.js with .save.

Hint: session.dateTime is an ISO string such as '2026-10-03T20:00:00', so the month can be extracted with slice(0, 7).

Exercise 3: checks from the terminal

Without opening the REPL, write the node -e or node -p commands that solve the following:

  1. Print the major version of the installed Node (just the number, for example 24).
  2. Print how many milliseconds are left from now until the Concierto de Otono (2026-10-03T20:00:00).
  3. Convert 4200 cents into the string '42.00'.
  4. Check whether the reports folder exists in the project (print true or false).

Solutions

Solution 1

> .load src/catalog-data.js

# 1. Total sessions
> catalog.reduce((total, e) => total + e.sessions.length, 0)
7

# 2. Average price in euros
> catalog.flatMap(e => e.sessions).reduce((s, x, i, a) => s + x.priceCents / a.length, 0) / 100
25.428571428571427

A more readable alternative, in two steps with _:

> catalog.flatMap(e => e.sessions).map(s => s.priceCents)
[ 2500, 2200, 1800, 1800, 1500, 3800, 4200 ]
> _.reduce((a, b) => a + b, 0) / _.length / 100
25.428571428571427

(17,800 cents across 7 sessions = 2,542.86 cents, that is 25.43 EUR.)

# 3. Events with more than two sessions
> catalog.filter(e => e.sessions.length > 2).map(e => e.title)
[ 'Noche de Monologos' ]

# 4. Unsold tickets
> catalog.flatMap(e => e.sessions).reduce((t, s) => t + (s.capacity - s.sold), 0)
1189

# 5. Most expensive session and its event
> catalog.flatMap(e => e.sessions.map(s => ({ ...s, event: e.title })))
       .sort((a, b) => b.priceCents - a.priceCents)[0]
{ id: 'ses-003-2', ..., priceCents: 4200, event: 'Festival de Jazz de Primavera' }

Here sort on the result of flatMap is safe: that array is new, it is not the original.

Solution 2

> .editor
function sessionsPerMonth(events) {
  const counts = {};

  for (const event of events) {
    for (const session of event.sessions) {
      // '2026-10-03T20:00:00'.slice(0, 7) === '2026-10'
      const month = session.dateTime.slice(0, 7);
      counts[month] = (counts[month] ?? 0) + 1;
    }
  }

  return counts;
}

Ctrl+D and try it out:

> sessionsPerMonth(catalog)
{ '2026-10': 5, '2027-04': 2 }

> console.table(sessionsPerMonth(catalog))
┌─────────┬────────┐
│ (index) │ Values │
├─────────┼────────┤
│ 2026-10 │ 5      │
│ 2027-04 │ 2      │
└─────────┴────────┘

> .save reports/exploration-months.js
Session saved to: reports/exploration-months.js

Bear in mind that .save stores the whole session, including the lines that failed. Review the file before reusing it.

Solution 3

# 1. Node major version
node -p "process.versions.node.split('.')[0]"
# 24

# 2. Milliseconds until the Concierto de Otono
node -p "new Date('2026-10-03T20:00:00') - new Date()"
# 4483200000  (varies depending on when you run it)

# 3. Cents to a string with two decimals
node -p "(4200 / 100).toFixed(2)"
# 42.00

# 4. Check whether the reports folder exists
node -p "require('node:fs').existsSync('reports')"
# true

In point 2, subtracting two Date objects automatically converts them to milliseconds since the epoch and returns the difference. If you prefer days:

node -p "Math.ceil((new Date('2026-10-03T20:00:00') - Date.now()) / 86400000) + ' days'"

Conclusion

The REPL is Node.js's laboratory: an environment where trying an idea costs nothing. You have learned to start it with node, to interpret the undefined after declarations, to chain calculations with _, to move around with Tab and the history, and to handle the dot commands — .help, .editor, .save, .load, .break, .clear, .exit.

You have applied it to Escena Viva by creating src/catalog-data.js and loading it with .load to explore live: flattening sessions with flatMap, calculating accumulated revenue with reduce, finding the most expensive session with sort, or discovering that there are 1,189 tickets still to sell. You have also seen node -e and node -p for checks from the terminal, and the quirks that trip people up at first: object literals that need parentheses, pending promises and top-level await.

And above all you are clear about the boundary: the REPL is for exploring; whatever has to be repeated goes into a file, and whatever has to be guaranteed goes into a test.

Along the way, some modern JavaScript tools slipped in almost by stealth: flatMap, the spread operator ..., nullish coalescing ??, arrow functions and destructuring. In the next lesson, Modern JavaScript for Node.js, we will study them one by one and without rushing, always while solving a real problem in the Escena Viva catalog. It is the refresher that will underpin every line of code you write from here to the end of the course.

Node.js Course: From Beginner to Advanced

Module 1: Introduction to Node.js

Module 2: Core Concepts

Module 3: File System and I/O

Module 4: HTTP and Web Servers

Module 5: NPM and Package Management

Module 6: The Express.js Framework

Module 7: Databases and ORMs

Module 8: Authentication and Authorization

Module 9: Testing and Debugging

Module 10: Advanced Topics

Module 11: Deployment and DevOps

Module 12: Real-World Projects

© Copyright 2026. All rights reserved