# JavaScript basics

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

Frontend Developer roadmap · JavaScript · 20 min read · https://levelcode.org/roadmaps/frontend/javascript-basics

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**:

| Type | Examples |
| --- | --- |
| `string` | `"hello"`, `'hi'`, `` `Total: ${n}` `` |
| `number` | `42`, `3.14`, `-0.5`, `NaN`, `Infinity` |
| `bigint` | `9007199254740993n` |
| `boolean` | `true`, `false` |
| `undefined` | the value of a variable that has not been assigned |
| `null` | an intentional "no value" |
| `symbol` | `Symbol("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
```

> [!WARNING]
> `sort()` and `reverse()` change the original array, and `sort()` compares as strings by default: `[10, 1, 5, 100].sort()` gives `[1, 10, 100, 5]`. Pass a comparator for numbers, `(x, y) => x - y`, and use `toSorted()` when you want a sorted copy instead.

## 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.

> [!TIP]
> In loops, `let` creates a new binding for each iteration, so a callback created inside the loop sees that iteration's value. With `var`, every callback would share one variable and see its final value.

## 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.

**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.

## Quiz

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

   A. 0
   B. 50
   C. undefined
   D. false

   **Answer: B.** || returns the right side whenever the left is falsy, and 0 is falsy. Use settings.volume ?? 50 to keep 0, since ?? only replaces null and undefined.

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

   A. [1, 2]
   B. A TypeError, because a is const
   C. [3]
   D. [1, 2, 3]

   **Answer: D.** b holds a reference to the same array, so pushing through b changes the array a points to. const only prevents reassigning the variable, not mutating the array.

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

   A. [3, 20, 100]
   B. [20, 3, 100]
   C. [100, 20, 3]
   D. It throws a TypeError

   **Answer: C.** Without a comparator, sort() converts elements to strings and compares them character by character, so "100" < "20" < "3". Use sort((x, y) => x - y) for numeric order.

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

   A. 1
   B. 3
   C. 2
   D. undefined

   **Answer: A.** Each call to makeCounter creates a new scope with its own count starting at 0. g closes over a fresh count, so its first call returns 1 regardless of how often f was called.
