JavaScript Fundamentals
The language that powers the entire web
JavaScript was built in 10 days. In May 1995, a programmer named Brendan Eich was told by Netscape to create a scripting language for their browser -- and to do it fast. The result was messy, quirky, and full of odd design choices. And somehow, it conquered the world. Today, JavaScript is the most widely used programming language on Earth. It runs in every browser, on every server, in mobile apps, even on satellites. If you're building for the web, you don't choose JavaScript -- JavaScript chooses you. The five core concepts in this chapter are the foundation everything else rests on. Master them, and every chapter after this clicks into place.
Variables: let, const, and Why var Is History
Before your program can do anything useful, it needs to remember things. A user's name. A count of items in a cart. Whether someone is logged in or not. Without memory, every piece of data disappears the instant you create it.
That's what variables are: labeled containers your program uses to hold onto values. You're giving instructions to a very literal robot, and the first instruction is always "remember this for later."
In modern JavaScript, you create variables with two keywords: `const` and `let`. The difference is simple but important. `const` means "this label is permanently attached to this value -- don't ever reassign it." `let` means "this might change later." That's it.
Why does this matter? Because code is read far more often than it's written. When another developer (or future you, three months from now at 2 AM) sees `const`, they immediately know: this value won't change, I don't need to track it. When they see `let`, they know to watch for reassignment somewhere below. It's a signal. A communication tool between humans.
You might wonder about `var`, the old way. It had bizarre scoping rules -- variables declared with `var` leaked out of blocks like if-statements and for-loops, causing hard-to-find bugs. Modern JavaScript left it behind. You should too.
One more thing: JavaScript is dynamically typed. A variable can hold a number right now and a string a moment later. This flexibility is a double-edged sword -- it's quick and loose, but it's also why TypeScript (JavaScript with a type safety net) has taken over professional development. We'll see TypeScript throughout this course.
// const for values that won't be reassigned
const appName = "Fullstack 101"
const maxRetries = 3
const isProduction = process.env.NODE_ENV === "production"
// let for values that will change
let currentUser = null
let attemptCount = 0
// const with objects/arrays -- the binding is fixed,
// but you CAN mutate the contents
const settings = { theme: "dark", language: "en" }
settings.theme = "light" // This is fine!
// settings = {} // This would throw an error
const tags = ["react", "nextjs"]
tags.push("typescript") // This is fine!
// tags = [] // This would throw an errorFunctions: Write It Once, Use It Forever
Imagine you're giving instructions to that very literal robot, and it needs to calculate sales tax. You write out the steps: take the price, multiply by the tax rate, add the result to the price. Great. Now imagine the robot needs to do that 50 more times, for 50 different products.
You could write those same instructions 50 times. Or you could write them once, give that set of instructions a name, and just say "do the tax thing" whenever you need it.
That's a function. It's a named, reusable set of instructions. You feed in raw materials (called arguments), the function does its work, and it hands back a result (the return value). Functions are the fundamental building block of every JavaScript program.
In modern JavaScript, you'll see two styles. Function declarations use the `function` keyword and have a special property called hoisting -- you can call them before they appear in your code, as if they float to the top. Arrow functions (`=>`) are a shorter, sleeker syntax introduced in ES6 that's become the dominant style in React and Node.js codebases.
Here's where it gets powerful: in JavaScript, functions are values. You can store them in variables, pass them into other functions, and return them from other functions. A function that takes another function as an argument is called a higher-order function, and they're everywhere. Array methods like `.map()`, `.filter()`, and `.reduce()` all work this way -- you hand them a little instruction card (a function), and they apply it to every item in the list.
Before reading on, think about this: when you build React components in Chapter 3, those components are literally functions. Every single one. That's how central this concept is.
// Function declaration
function greetUser(name) {
return `Hello, ${name}! Welcome back.`
}
// Arrow function (modern, concise)
const calculateTotal = (price, taxRate) => {
const tax = price * taxRate
return price + tax
}
// Arrow function with implicit return (single expression)
const double = (n) => n * 2
// Higher-order functions: functions that take functions
const numbers = [1, 2, 3, 4, 5]
const doubled = numbers.map((n) => n * 2)
// [2, 4, 6, 8, 10]
const evens = numbers.filter((n) => n % 2 === 0)
// [2, 4]
const sum = numbers.reduce((total, n) => total + n, 0)
// 15
// Functions as first-class citizens
const operations = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
}
console.log(operations.add(5, 3)) // 8Objects and Arrays: Giving Structure to Data
Here's a scenario you'll face constantly: you just called an API and got back a response. It contains a user's name, their email, a list of their recent posts, and each post has a title, a body, and a timestamp. That's a lot of data. How do you organize it?
JavaScript gives you two fundamental structures. Objects hold data as labeled pairs -- a key and a value. Think of an object as a form with fields: "name" is "Alice," "email" is "alice@example.com," "role" is "admin." Arrays hold data as ordered lists -- first item, second item, third item. Think of an array as a numbered list where position matters.
These two structures are the backbone of web development. When you fetch data from an API, it comes back as JSON (JavaScript Object Notation), which maps directly to JavaScript objects and arrays. When you define a React component's props, you're working with an object. When you render a list of items on screen, you're looping through an array. They're inescapable.
Now, here's where modern JavaScript gets elegant. Destructuring lets you "unpack" values from objects and arrays into individual variables in a single line. Instead of writing `const name = user.name` three times for three properties, you write `const { name, email, role } = user` and you're done. You'll see this pattern on nearly every line of React code -- extracting props, unpacking state from hooks, pulling values from API responses.
The spread operator (`...`) is the other essential tool. It lets you copy an object or array and tweak it without changing the original. Why does that matter? Because in React, you never mutate state directly -- you always create new copies. The spread operator makes that painless. We'll see exactly why when we cover React's state management in Chapter 3.
// Objects: key-value pairs
const user = {
id: 42,
name: "Alice Chen",
email: "alice@example.com",
role: "admin",
preferences: {
theme: "dark",
notifications: true,
},
}
// Accessing properties
console.log(user.name) // "Alice Chen"
console.log(user.preferences.theme) // "dark"
console.log(user["role"]) // "admin"
// Destructuring objects
const { name, email, role } = user
console.log(name) // "Alice Chen"
// Nested destructuring
const { preferences: { theme } } = user
console.log(theme) // "dark"
// Spread operator: copy and extend
const updatedUser = { ...user, role: "superadmin" }
// Arrays
const frameworks = ["React", "Vue", "Svelte", "Angular"]
// Destructuring arrays
const [first, second, ...rest] = frameworks
console.log(first) // "React"
console.log(rest) // ["Svelte", "Angular"]
// Spread with arrays
const allFrameworks = [...frameworks, "Solid", "Qwik"]Async/Await: Handling Things That Take Time
This is the concept that trips up more beginners than any other. So let's take it slow, and let's start with a story.
You walk into a coffee shop. You order a latte. Now -- do you stand frozen at the counter, eyes locked on the barista, unable to move or think until the coffee is in your hand? Of course not. You step aside, check your phone, maybe chat with a friend. When the barista calls your name, you walk back and grab your drink.
JavaScript works the same way. It's single-threaded, meaning it can only do one thing at a time -- like you, a single person in the coffee shop. But it doesn't freeze and wait when something takes time. Fetching data from an API? That might take 500 milliseconds. Reading a file? Could take longer. JavaScript kicks off the slow operation, steps aside to handle other work, and comes back when the result is ready. This is called asynchronous programming, and it's powered by something called the event loop.
Here's the question: if JavaScript doesn't wait, how do you write code that says "do this, THEN do that with the result"? Early JavaScript used callbacks -- functions nested inside functions nested inside functions. It worked, but it was ugly. Developers called it "callback hell." Then came Promises, a cleaner pattern. But the real breakthrough was `async/await`.
With `async/await`, you write code that looks straight-line and sequential, but under the hood it's non-blocking. When you write `await fetch(url)`, JavaScript pauses that one function, goes off to handle other things, and picks right back up when the data arrives. It reads like a recipe: do step 1, wait for the result, do step 2 with that result. Simple.
The two rules are: any function that uses `await` must be marked with the `async` keyword. And you should always wrap `await` calls in `try/catch` blocks, because network requests can fail, APIs can be down, and your robot needs to know what to do when things go wrong.
This pattern is everywhere in full-stack development. Fetching data on the frontend. Handling API requests on the backend. Querying a database. Every chapter from here on out uses async/await.
// A Promise represents a value that will exist in the future
const fetchUser = (id) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "Alice" })
} else {
reject(new Error("Invalid user ID"))
}
}, 1000)
})
}
// Using async/await (modern, readable)
async function getUserProfile(userId) {
try {
const user = await fetchUser(userId)
console.log(user.name) // "Alice" (after 1 second)
return user
} catch (error) {
console.error("Failed to fetch user:", error.message)
return null
}
}
// Real-world example: fetching from an API
async function loadPosts() {
try {
const response = await fetch("https://api.example.com/posts")
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`)
}
const posts = await response.json()
return posts
} catch (error) {
console.error("Failed to load posts:", error)
return []
}
}
// Running multiple async operations in parallel
async function loadDashboard(userId) {
const [user, posts, notifications] = await Promise.all([
fetch(`/api/users/${userId}`).then((r) => r.json()),
fetch(`/api/users/${userId}/posts`).then((r) => r.json()),
fetch(`/api/users/${userId}/notifications`).then((r) => r.json()),
])
return { user, posts, notifications }
}One Language, Every Layer
Here's the "aha" moment this chapter has been building toward.
In 2009, a developer named Ryan Dahl took JavaScript -- a language that had been trapped inside browsers for 14 years -- and set it free. He created Node.js, which let JavaScript run on servers. Overnight, the walls between "frontend developer" and "backend developer" started crumbling. For the first time, you could use a single language to build everything.
Think about what that means. Before Node.js, you might write JavaScript for the browser, PHP or Ruby for the server, and SQL for the database layer. Three languages, three mental models, three ecosystems. Now? One language flows through the entire stack. Functions you write for the frontend can be shared with the backend. The npm package ecosystem serves both. The same person can build the button AND the API it calls.
Today, JavaScript and TypeScript (its typed superset that adds the safety net of a type system) run virtually everywhere. In the browser, they power interactive UIs with React, Vue, and Svelte. On the server, Node.js and Deno handle backend logic and API routes. In mobile apps, React Native builds cross-platform iOS and Android apps from JavaScript. On the desktop, Electron powers tools like VS Code and Slack. Even edge computing platforms like Cloudflare Workers run JavaScript milliseconds from users worldwide.
This is exactly why this course teaches JavaScript from top to bottom. The React components you'll build in Chapter 3, the APIs you'll design in Chapter 4, the Node.js servers you'll run in Chapter 5, the Next.js applications you'll ship in Chapter 6 -- they're all JavaScript. One language. Every layer. That's why we're here.
- Use `const` by default and `let` only when you need to reassign -- this signals intent to anyone reading your code. Never use `var`; its scoping rules are a bug factory.
- Functions are the building blocks of everything in JavaScript. Arrow functions, higher-order functions like map/filter/reduce, and functions-as-values are patterns you'll use in every React component you build.
- Destructuring and the spread operator are how modern JavaScript unpacks and copies data cleanly -- you'll see them on nearly every line of React code, from extracting props to updating state.
- Async/await makes asynchronous code read like a straight-line recipe. Pair it with try/catch for error handling whenever you fetch data, query a database, or call an API.
- JavaScript is the only language that runs natively in browsers AND on servers. One language across the entire stack is the superpower that makes full-stack development accessible.
What is the key difference between `let` and `const`?