Step 06 of 21

JavaScript basics

Work confidently with JavaScript values, variables, functions, objects, arrays and scope, including closures and common traps.

20 min 9 sections

JavaScript is the only programming language browsers run natively, and it is what makes a page respond to people. Before you can change the page or talk to servers, you need a solid grip on the language itself: what kinds of values exist, how variables and functions work, how to model data with objects and arrays, and which names your code can see. This lesson covers that core, including the quirks that catch out newcomers.

Running JavaScript

You can run JavaScript in two places without installing anything special:

  • The browser console. Open DevTools (F12) and choose the Console tab. Type an expression and press Enter.
  • Node.js. Save code in a file such as basics.js and run node basics.js in a terminal.

console.log prints values, which is how you will check your work throughout this lesson:

js
const name = "Ada";
console.log("Hello,", name);
console.log(2 + 3 * 4);
text
Hello, Ada
14

Values and types

Every value in JavaScript has a type. There are seven primitive types, and everything else is an object:

TypeExamples
string"hello", 'hi', `Total: ${n}`
number42, 3.14, -0.5, NaN, Infinity
bigint9007199254740993n
booleantrue, false
undefinedthe value of a variable that has not been assigned
nullan intentional "no value"
symbolSymbol("id"), a unique identifier
object{ name: "Ada" }, [1, 2, 3], functions, dates

The typeof operator tells you a value's type, with two famous quirks:

js
typeof "hi";        // "string"
typeof 42;          // "number"
typeof undefined;   // "undefined"
typeof (() => {});  // "function"
typeof [1, 2];      // "object"  – use Array.isArray([1, 2]) instead
typeof null;        // "object"  – a bug from 1995 that can never be fixed

Numbers

JavaScript has one number type for integers and decimals, stored as 64-bit floating point. That makes some decimal arithmetic inexact:

js
0.1 + 0.2;             // 0.30000000000000004
(0.1 + 0.2).toFixed(2); // "0.30"
Number("42");          // 42
Number("42px");        // NaN ("not a number")
parseInt("42px", 10);  // 42
Number.isNaN(NaN);     // true – NaN is not even equal to itself

For money, store whole amounts in the smallest unit (cents) and format only for display.

Strings

Strings can use single quotes, double quotes, or backticks. Backtick template literals can embed expressions with ${} and span several lines:

js
const item = "socks";
const qty = 3;
const msg = `You ordered ${qty} pairs of ${item}.`;

msg.length;                 // 29
msg.toUpperCase();          // "YOU ORDERED 3 PAIRS OF SOCKS."
msg.includes("socks");      // true
"  padded  ".trim();        // "padded"
"JavaScript".slice(0, 4);   // "Java"

Strings are immutable: methods return new strings rather than changing the original.

Variables: const and let

Declare variables with const by default and let when you need to reassign:

js
const taxRate = 0.2;
let total = 100;
total = total * (1 + taxRate); // fine: let can be reassigned
// taxRate = 0.25;             // TypeError: Assignment to constant variable.

const stops you reassigning the variable, not changing the value it holds. An object declared with const can still have its properties changed:

js
const user = { name: "Ada" };
user.name = "Grace"; // allowed

You will see var in older code. It ignores block scope and can be redeclared, which causes subtle bugs. Do not use it in new code.

Comparisons and truthiness

Use strict equality, === and !==. The loose == converts types before comparing, with surprising results:

js
0 == "";              // true
0 === "";             // false
null == undefined;    // true
null === undefined;   // false
"5" + 3;              // "53" – + with a string concatenates
"5" - 3;              // 2    – - converts to numbers

In an if or a && / || expression, every value is treated as truthy or falsy. There are only a few falsy values: false, 0, -0, 0n, "", null, undefined and NaN. Everything else is truthy – including "0", "false", [] and {}.

Two operators handle missing values safely:

js
const settings = { volume: 0, theme: null };

settings.volume || 50;   // 50 – wrong! || treats 0 as missing
settings.volume ?? 50;   // 0  – ?? only replaces null and undefined
settings.theme ?? "light"; // "light"

const order = { customer: null };
order.customer?.address?.city; // undefined, instead of a TypeError

?. (optional chaining) stops and returns undefined if the value on its left is null or undefined. ?? (nullish coalescing) provides a default only for null and undefined.

Functions

A function is a reusable block of code that takes inputs (parameters) and can return an output. There are three common ways to write one:

js
// Function declaration
function add(a, b) {
  return a + b;
}

// Function expression stored in a variable
const subtract = function (a, b) {
  return a - b;
};

// Arrow function: concise; an expression body is returned automatically
const multiply = (a, b) => a * b;
const square = (n) => n * n;
const makeUser = (name) => ({ name, active: true }); // wrap object literals in ()

Things to know:

  • A function without a return statement returns undefined.
  • Parameters can have defaults: function greet(name = "friend") { … }.
  • Function declarations are hoisted: you can call them above the line where they are defined. Function expressions and arrows cannot be used before their line.
  • Functions are values. You can store them, pass them to other functions, and return them. A function passed in to be called later is a callback:
js
function applyDiscount(prices, rule) {
  return prices.map(rule);
}

applyDiscount([100, 40], (p) => p * 0.9); // [90, 36]

Objects and arrays

Objects

An object groups related values under named properties:

js
const product = {
  name: "Ridge Runner",
  price: 120,
  tags: ["trail", "waterproof"],
  describe() {
    return `${this.name} costs £${this.price}`;
  },
};

product.price;            // 120
product["name"];          // "Ridge Runner" – brackets allow computed keys
product.colour = "grey";  // add a property
delete product.colour;    // remove it
product.describe();       // "Ridge Runner costs £120"

Destructuring pulls properties into variables, and spread copies properties into a new object:

js
const { name, price } = product;
const discounted = { ...product, price: 99 }; // new object; product is unchanged

To loop over an object's contents, turn it into an array first. Object.keys gives the property names, Object.values the values, and Object.entries gives [key, value] pairs, which pair nicely with destructuring:

js
const stock = { socks: 12, caps: 0, jackets: 3 };

Object.keys(stock);   // ["socks", "caps", "jackets"]
Object.values(stock); // [12, 0, 3]

for (const [item, count] of Object.entries(stock)) {
  console.log(`${item}: ${count}`);
}

An object used this way – arbitrary keys mapping to values – is often called a dictionary or lookup table. It is one of the most common data structures you will build.

Arrays

Arrays are ordered lists, indexed from 0:

js
const sizes = [40, 41, 42];
sizes[0];          // 40
sizes.length;      // 3
sizes.push(43);    // adds to the end, returns the new length (4)
sizes.includes(42); // true

for (const size of sizes) {
  console.log(size);
}

The array methods you will use constantly take a callback, call it once per element, and return something new without changing the original array. map transforms every element, filter keeps the ones that pass a test, find returns the first match (or undefined), some asks whether any element passes, and reduce folds the whole array into a single value, starting from the initial value you give it:

js
const products = [
  { name: "Socks", price: 8, inStock: true },
  { name: "Jacket", price: 150, inStock: false },
  { name: "Cap", price: 20, inStock: true },
];

products.map((p) => p.name);               // ["Socks", "Jacket", "Cap"]
products.filter((p) => p.inStock);         // Socks and Cap objects
products.find((p) => p.price > 100);       // the Jacket object
products.some((p) => !p.inStock);          // true
products.reduce((sum, p) => sum + p.price, 0); // 178

Values versus references

Primitives are copied by value. Objects and arrays are held by reference: assigning one to another variable copies the reference, not the data.

js
const a = { count: 1 };
const b = a;      // the same object
b.count = 2;
a.count;          // 2

const c = { ...a };           // shallow copy: a new top-level object
const d = structuredClone(a); // deep copy, including nested objects

Scope and closures

Scope decides which variables a piece of code can see. Variables declared with let and const are block-scoped: they exist only inside the nearest { … }.

js
const greeting = "hi";      // outer scope

function shout() {
  const loud = greeting.toUpperCase(); // inner code can read outer variables
  return loud;
}

if (true) {
  const temp = 1;
}
// console.log(temp); // ReferenceError: temp is not defined

JavaScript uses lexical scope: what a function can see is determined by where it is written, not where it is called. Accessing a let or const before its declaration line throws a ReferenceError; that region is called the temporal dead zone.

A closure is a function that remembers the variables from the scope where it was created, even after that outer function has returned:

js
function makeCounter() {
  let count = 0;
  return () => {
    count += 1;
    return count;
  };
}

const next = makeCounter();
next(); // 1
next(); // 2

const other = makeCounter();
other(); // 1 – each call to makeCounter creates a fresh count

count is private: nothing outside can change it except through next. Closures are how event handlers, timers and callbacks remember the data they need.

Practice

Save this in orders.js and implement the four functions below it. Run it with node orders.js.

js
const orders = [
  { id: 1, customer: "ana", items: [{ price: 12, qty: 2 }, { price: 5, qty: 1 }], status: "paid" },
  { id: 2, customer: "ben", items: [{ price: 40, qty: 1 }], status: "refunded" },
  { id: 3, customer: "ana", items: [{ price: 8, qty: 3 }], status: "paid" },
  { id: 4, customer: "cleo", items: [], status: "pending" },
];
  1. orderTotal(order) returns the sum of price * qty for its items.
  2. paidRevenue(orders) returns the total of all orders with status "paid".
  3. totalsByCustomer(orders) returns an object like { ana: 53, ben: 40, cleo: 0 } (every status counts).
  4. makeIdGenerator(start) returns a function that yields start, then start + 1, and so on.
Show solution
js
function orderTotal(order) {
  return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
}

function paidRevenue(orders) {
  return orders
    .filter((order) => order.status === "paid")
    .reduce((sum, order) => sum + orderTotal(order), 0);
}

function totalsByCustomer(orders) {
  const totals = {};
  for (const order of orders) {
    totals[order.customer] = (totals[order.customer] ?? 0) + orderTotal(order);
  }
  return totals;
}

function makeIdGenerator(start) {
  let next = start;
  return () => next++;
}

console.log(orderTotal(orders[0]));
console.log(paidRevenue(orders));
console.log(totalsByCustomer(orders));
const nextId = makeIdGenerator(100);
console.log(nextId(), nextId(), nextId());
text
29
53
{ ana: 53, ben: 40, cleo: 0 }
100 101 102

The initial value 0 in reduce is essential: it makes an empty items array total 0 instead of throwing. ?? is used rather than || so the default only applies when the customer has no entry yet. next++ returns the current value and then increments it, and the closure keeps next alive between calls.

Key takeaways

  • Seven primitive types plus objects; typeof null is "object", and arrays are checked with Array.isArray.
  • Use const by default, let for reassignment, and never var; const objects can still be mutated.
  • Compare with ===; know the falsy values, and use ?? and ?. to handle missing data.
  • Functions are values: pass them as callbacks and use map, filter, find and reduce to transform arrays.
  • Objects and arrays are shared by reference; copy with spread or structuredClone.
  • Scope is lexical and block-based, and closures let functions keep private state.

Check yourself

Quiz

0/4 correct · 4 left

  1. 1.What does console.log(settings.volume || 50) print when settings.volume is 0?

  2. 2.After const a = [1, 2]; const b = a; b.push(3); what is a?

  3. 3.What is the value of [3, 20, 100].sort()?

  4. 4.Using makeCounter from this lesson, you run const f = makeCounter(); f(); f(); const g = makeCounter(); What does the first call to g() return?

Finished reading?

Mark it complete to fill in your roadmap.