Four ways to run JavaScript

JavaScript always runs inside a runtime environment (see What Is JavaScript?). While you learn, you will use four entry points:

Where How Good for
Browser console Developer tools, Console tab Trying one line; inspecting a live page
Script in a web page <script> element in an HTML file Code that works with the page
Node.js REPL node in a terminal Trying ideas without a browser
Node.js script node file.js Real programs, tools and automation

You need no installation for the first two: every desktop browser has a JavaScript engine and a console. For the last two, install the current Long-Term Support (LTS) version of Node.js (see Node.js versions). A code editor such as Visual Studio Code helps with all of them: it colors the syntax, completes names and marks errors as you type.

Installing Node.js

Check first whether Node.js is already there: open a terminal (Terminal on macOS and Linux, Windows Terminal or PowerShell on Windows) and type node --version. If the command is not found, install the LTS version:

  • Windows and macOS: the installer from nodejs.org/en/download (choose the LTS version), or a package manager: winget install OpenJS.NodeJS.LTS on Windows, brew install node@24 with Homebrew on macOS. Plain brew install node gives the newest Current line, not the LTS.
  • Linux: the distribution's own nodejs package is often an older release line, so check its version. The download page also lists other ways to get a current LTS, such as a version manager (nvm, fnm) that installs Node.js in your home folder and lets you switch between versions.

npm, the package manager for JavaScript, comes with Node.js; see Modules and npm. Afterward, node --version and npm --version both print a version number.

The browser console

The browser console is the part of the browser developer tools where you can type JavaScript and see its result at once, and where the page's own scripts write their messages and errors. Open it with F12 and pick the Console tab, or go there directly with Ctrl+Shift+J in Chrome and Edge, Ctrl+Shift+K in Firefox (on macOS, Cmd+Option instead of Ctrl+Shift). In Safari, first enable the developer features in the settings.

The console is a REPL (read, evaluate, print, loop): it reads a line, runs it and shows the value. You do not need console.log() to see the value of an expression:

> 1250 * 0.21
262.5
> let region = "North"
undefined
> region.toUpperCase()
'NORTH'
> document.title
'Outage map — Kestrel Energy'

The undefined after let region = … is not an error. A declaration is a statement, not an expression, so it has no value to show, and the console prints undefined instead. The console runs in the context of the page that is open, so document is that page: you can read and change it live. Press Shift+Enter for a new line without running the code yet.

The script element in a web page

A web page runs JavaScript with the script element, <script>. It can hold the code itself (an inline script) or load it from a separate file with the src attribute (an external script):

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Outage map</title>
    <!-- External script: downloaded now, run after the page is parsed -->
    <script src="outages.js" defer></script>
  </head>
  <body>
    <h1>Current outages</h1>
    <p id="status">Loading…</p>

    <!-- Inline script: runs as soon as the browser reaches it -->
    <script>
      console.log("Page parsed up to here");
    </script>
  </body>
</html>

Some rules that follow from how browsers read a page:

  • The browser reads HTML from top to bottom. A plain <script> stops that reading until the script has been downloaded and run. A script that runs before an element exists cannot find it.
  • Two common fixes: put scripts at the end of <body>, or put them in <head> with the defer attribute. defer downloads the file in parallel and runs it after the whole page has been parsed, in the order the scripts appear. It works only for external scripts.
  • type="module" loads the file as an ES module, which is deferred automatically and may use import (see Modules and npm).
  • An element with src must be empty: code between the tags of an external script is ignored.
  • Prefer external files. They are cached by the browser, are easy to lint and test, and work with a strict Content Security Policy, which can forbid inline scripts.

How a script then reads and changes the page is the subject of The DOM and Events.

The Node.js REPL

Type node without a file name in a terminal and you get the Node.js REPL, the same idea as the browser console but without a web page. The prompt is >:

node
Welcome to Node.js v24.21.0.
Type ".help" for more information.
> const prices = [0.31, 0.29, 0.34]
undefined
> prices.length
3
> Math.max(...prices)
0.34
> _ * 100
34
> .exit

In the REPL, _ holds the last result, .help lists the commands that start with a dot, and .exit (or Ctrl+D, or Ctrl+C twice) leaves it. Press Tab to complete names: type Math. and press Tab twice to see everything in Math.

Running a script file

For anything longer than a few lines, write a file. Save this as outages.js:

// outages.js — count open outage tickets per region
const tickets = [
  { id: "INC-20417", region: "North", open: true },
  { id: "INC-20418", region: "South", open: false },
  { id: "INC-20421", region: "North", open: true },
];

let openNorth = 0;
for (const ticket of tickets) {
  if (ticket.open && ticket.region === "North") {
    openNorth++;
  }
}
console.log("Open tickets in North:", openNorth);
Open tickets in North: 2

Then run it from a terminal. The terminal works in one folder at a time, its working directory, and node outages.js looks for the file there; so first go to the folder where you saved it with cd (pwd shows where you are). Otherwise Node.js reports Error: Cannot find module with the full path it tried.

cd ~/projects/js            # the folder that contains outages.js
node outages.js
node --version          # which Node.js is installed
node --watch outages.js # run again every time the file is saved

Node.js runs the file from top to bottom and exits when there is nothing left to do. If a line throws an error that nothing catches, Node.js stops there and prints the error with a stack trace: the file, line number and function calls that led to it. The page on error handling explains how to read one.

Executable scripts on Linux and macOS

A script can start with a shebang line, so you can run it like any other command. JavaScript allows #! on the very first line (standardized in ES2023):

#!/usr/bin/env node
console.log("Meter check complete");
chmod +x check-meters.js
./check-meters.js

Printing with console.log()

console.log() writes its arguments as one line of text to the console: the browser console in a web page, standard output in Node.js. Give it several arguments and it separates them with spaces. Call it without arguments for an empty line.

const customer = "Lucía Romero";
const kwh = 312.5;
const pricePerKwh = 0.31;

console.log("Invoice for", customer);
console.log("Usage:", kwh, "kWh");
console.log();
console.log("Amount due:", kwh * pricePerKwh);
console.log("Rounded:", (kwh * pricePerKwh).toFixed(2));
Invoice for Lucía Romero
Usage: 312.5 kWh

Amount due: 96.875
Rounded: 96.88

Logging values and objects

Text on its own is printed without quotes. Inside an array or an object, Node.js shows how each value would be written in code, so strings get single quotes. That lets you see the difference between the number 5 and the string "5":

console.log(5, "5");
console.log([5, "5"]);
console.log({ meter: "KE-4471", readings: [118.2, 119.0], online: true });
console.log("Nested:", { site: { address: { geo: { lat: 52.01 } } } });
5 5
[ 5, '5' ]
{ meter: 'KE-4471', readings: [ 118.2, 119 ], online: true }
Nested: { site: { address: { geo: [Object] } } }

Notice 119: there is no separate integer type, so 119.0 and 119 are the same number. Node.js shows objects nested more than two levels deep as [Object]. A browser console shows objects as a tree you can expand instead.

The first argument can contain placeholders: %s for a string, %d for a whole number, %o for an object. Template literals (see Strings and Template Literals) are usually easier to read:

const region = "North";
const customers = 1250;
console.log("%s: %d customers without power", region, customers);
console.log(`${region}: ${customers} customers without power`);
North: 1250 customers without power
North: 1250 customers without power

Other console methods

Method Use
console.info() Same as console.log(); some consoles add an icon
console.warn() A warning: yellow in the browser, standard error in Node.js
console.error() An error message: red in the browser, standard error in Node.js
console.table() An array of objects as a table (see Debugging and Developer Tools)

In a terminal, console.log() and console.error() look the same, but they go to different streams: standard output and standard error. That matters when output is redirected to a file or piped into another program; error messages then stay visible (see Pipes and Redirection). In the browser console you can filter the messages by level.

Statements, comments and semicolons

A JavaScript program is a series of statements. Curly braces { } group statements into a block; indentation is only for readers. Names are case-sensitive: console works, Console does not:

console.log("Checking meters");
Console.log("Done");
Checking meters
ReferenceError: Console is not defined

Comments come in two forms: // to the end of the line, and /* … */ for a block that can span lines. A comment that starts with /** is a documentation comment (JSDoc), read by editors and tools.

Automatic semicolon insertion

Statements end with a semicolon. If you leave one out, the engine usually inserts it for you where a line break makes the statement complete. This automatic semicolon insertion is why code without semicolons mostly works — and why it sometimes does something you did not mean:

function statusText() {
  return
    "All meters online";
}
console.log(statusText());
undefined

A line break directly after return ends the statement, so the function returns nothing and the string below is never reached. Keep the value on the same line as return. Whether a team writes semicolons or not is a style choice; a formatter such as Prettier applies it consistently (see Code Quality).

Strict mode

Early JavaScript was forgiving: many mistakes failed silently instead of raising an error. ES5 (2009) added strict mode, an opt-in variant of the language that turns several of those silent mistakes into errors and forbids a few confusing features. The most useful difference for beginners: assigning to a variable that was never declared. Without strict mode, a typo quietly creates a new global variable:

let totalKwh = 0;
totlKwh = 312.5; // typo: creates a new global variable
console.log(totalKwh);
0

The program runs and prints the wrong answer. With the directive "use strict"; as the first statement of the file (or of a function), the same typo stops the program at the line that is wrong:

"use strict";
let totalKwh = 0;
totlKwh = 312.5; // typo
console.log(totalKwh);
ReferenceError: totlKwh is not defined

Strict mode also, among other things:

  • throws a TypeError when you assign to a read-only property, instead of ignoring it;
  • forbids two parameters with the same name and the old with statement;
  • leaves this as undefined in a plain function call instead of pointing to the global object (see Classes, Prototypes and this).

You rarely need to write the directive yourself in modern code: ES modules and class bodies are always strict. Plain scripts and Node.js CommonJS files (the default .js files) are not, so add "use strict"; at the top of those.