What is JavaScript?
JavaScript is a programming language that runs inside a host environment and lets you control what happens on a page. HTML provides structure, CSS provides presentation, and JavaScript provides behaviour. When a menu opens, a form validates, a page loads new data without refreshing or a game responds to your keyboard, JavaScript is doing the work.
It is a high-level, dynamically typed, multi-paradigm language. That mouthful simply means: you do not manage memory by hand, you do not declare types up front, and you can write in an object-oriented, functional or imperative style as the problem demands.
The language was created in 1995 for a single job — making web pages a little interactive — and it escaped that box entirely. Today the same syntax runs servers, mobile apps, command-line tools and desktop applications. Learning it is one of the highest-leverage investments you can make in web development.
Where JavaScript runs
JavaScript is the language. A runtime is the environment that executes it. The most familiar runtime is the browser, but it is far from the only one.
- Browsers ship a JavaScript engine (V8 in Chrome, SpiderMonkey in Firefox, JavaScriptCore in Safari) plus host APIs like the DOM,
fetch, timers and storage. - Node.js, Deno and Bun run JavaScript on the server with APIs for files, networks and processes.
- Embedded runtimes run JavaScript in mobile apps, desktop apps, databases and even microcontrollers.
The language itself is standardised as ECMAScript. The DOM, fetch and console are not part of the language — they are APIs the runtime provides. Keeping that distinction in mind explains why the same code works in one environment and not another.
Variables: let, const and var
A variable is a named reference to a value. Modern JavaScript has three ways to create one, but you only need two.
// app.js
const name = "Ada"; // cannot be reassigned
let score = 0; // can be reassigned
score = score + 10;
// var is the legacy form — avoid it in new code
var old = true;
Use const by default. It does not make the value immutable — you can still mutate the contents of a const object or array — it only prevents rebinding the name to a different value. Use let when a value genuinely changes, such as a counter or accumulator. Reach for var only when reading old code.
Data types
JavaScript has a small set of primitive types plus objects.
| Type | Example | Notes |
|---|---|---|
| string | "hello" |
Text, single or double quotes, or backticks |
| number | 42, 3.14 |
One numeric type for integers and floats |
| bigint | 9007199254740993n |
Arbitrarily large integers |
| boolean | true, false |
Logical values |
| undefined | undefined |
A declared but unassigned value |
| null | null |
An intentional absence of value |
| symbol | Symbol("id") |
Unique identifiers |
| object | { name: "Ada" } |
Collections, functions, arrays — everything else |
The typeof operator tells you what you are working with. Watch for two famous quirks: typeof null is "object" (a historical bug kept for compatibility) and typeof a function is "function" even though functions are objects.
Functions
Functions are reusable blocks of logic, and in JavaScript they are values. You can store them in variables, pass them as arguments and return them from other functions.
// greet.js
function greet(name) {
return `Hello, ${name}!`;
}
const add = (a, b) => a + b;
const shout = (text) => text.toUpperCase();
The function declaration form is hoisted, so it can be called before it appears. Arrow functions (=>) are shorter, do not have their own this, and are ideal for callbacks. When a function takes a single expression, the body and return can be omitted.
Functions can also have default parameters and accept a variable number of arguments through rest syntax:
// math.js
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
function greet(name = "friend") {
return `Hi, ${name}`;
}
sum(1, 2, 3); // 6
Objects and arrays
Objects hold named values; arrays hold ordered values. Together they model almost every piece of data you will handle.
// data.js
const user = {
name: "Ada",
age: 36,
admin: true,
};
const skills = ["HTML", "CSS", "JavaScript"];
user.name; // "Ada"
skills.length; // 3
skills[0]; // "HTML"
You can destructure both to pull values out cleanly, and spread them to make copies:
// destructure.js
const { name, age } = user;
const [first, ...rest] = skills;
const updated = { ...user, age: 37 };
const moreSkills = [...skills, "TypeScript"];
Objects and arrays are reference types. Assigning one to another variable copies the reference, not the contents, so mutating through either name changes the same underlying value. Spread creates a shallow copy when you need independence.
Control flow
Conditionals and loops direct the flow of your program.
// logic.js
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else {
grade = "C";
}
for (const skill of skills) {
console.log(skill);
}
Prefer for...of for arrays and for...in only for enumerating object keys. The ternary operator condition ? a : b is a compact expression when you need a value rather than a branch with side effects. The nullish coalescing operator ?? and optional chaining ?. make defensive code readable:
// safe.js
const city = user.address?.city ?? "Unknown";
Scope and closures
Scope is where a variable is visible. A block { } creates a scope for let and const. A function creates a scope for everything inside it. Closures are the consequence: an inner function keeps access to the variables of the outer function even after that function has returned.
// counter.js
function createCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const next = createCounter();
next(); // 1
next(); // 2
The returned function “closes over” count, keeping it alive and private. Closures power event handlers, memoisation, currying and module patterns. They are one of the most important ideas in the language, and once they click, a lot of JavaScript starts to make sense.
The event loop, a first look
JavaScript runs your code on a single thread — it can only do one thing at a time. Yet it never freezes while waiting for a network request. The trick is the event loop.
Synchronous code runs immediately on the call stack. When you schedule something asynchronous, such as a timer or a fetch, the runtime hands it off and keeps going. When it finishes, its callback is queued. The event loop picks up queued work only once the stack is empty.
// loop.js
console.log("1");
setTimeout(() => console.log("2"), 0);
console.log("3");
// Output: 1, 3, 2
Even with a zero millisecond delay, the callback waits until the current synchronous code finishes. This ordering is the foundation of promises and async/await, covered in depth in the Async JavaScript guide.
How to add JavaScript to a page
You attach JavaScript to HTML with a <script> element. Place it at the end of the body, or use the defer attribute so the HTML is parsed before your code runs.
<!-- index.html -->
<body>
<h1>Hello</h1>
<script src="app.js" defer></script>
</body>
Inline scripts work too, but external files are cacheable, reusable and easier to debug. Use type="module" to load ES modules with import and export.
Common mistakes
- Using
varand being surprised by hoisting or function scope. - Comparing with
==and triggering unexpected type coercion. Use===. - Mutating an object or array and assuming a copy was made.
- Forgetting that
thisdepends on how a function is called, not where it is defined. - Treating
nullandundefinedas interchangeable without a reason. - Reaching for a framework before understanding plain functions and objects.
Best practices
- Prefer
const, thenlet, and avoidvar. - Always use
===and!==unless you specifically want coercion. - Keep functions small and focused on one job.
- Name things for what they mean, not what they are.
- Use optional chaining and nullish coalescing for safe access.
- Learn the language before the framework. Fundamentals outlast tools.
What to learn next
You now have the mental model: values, variables, functions, objects, scope and the event loop. From here the natural next steps are the DOM for interacting with pages, ES6+ features for modern syntax, and asynchronous JavaScript for promises and async/await. Pick one, build something small and let the practice compound.