🔥 JavaScript Intermediate
Level Up Your Skills - Advanced Concepts & Techniques
Ready to go beyond the basics? This intermediate course builds on your JavaScript fundamentals and introduces powerful concepts used by professional developers.
🎯 Course Prerequisites
This course assumes you know:
- Variables, data types, and operators
- Basic control flow (if/else, loops)
- Functions and scope
- Arrays and objects
If you need to review: Start with the Beginner Course
🔀 Advanced If/Else Statements
Think of if/else like a choose-your-own-adventure book.
Based on the reader's choices, the story goes different directions. Similarly, if/else statements let your program make decisions and execute different code paths.
// Basic if/else
let age = 25;
if (age >= 18) {
console.log("You can vote!");
} else {
console.log("Too young to vote");
}
// Nested if/else
let temperature = 75;
if (temperature > 90) {
console.log("It's hot! Stay inside.");
} else if (temperature > 70) {
console.log("Perfect weather!");
} else if (temperature > 50) {
console.log("A bit chilly");
} else {
console.log("Brrr! Bundle up!");
}
// Ternary operator (shortcut for simple if/else)
let canDrive = age >= 16 ? "Yes, you can drive" : "No, too young";
console.log(canDrive);
// Switch statement (great for multiple options)
let day = "Monday";
switch (day) {
case "Monday":
console.log("Start of work week");
break;
case "Friday":
console.log("TGIF!");
break;
case "Saturday":
case "Sunday":
console.log("Weekend!");
break;
default:
console.log("Regular workday");
}
🔄 Advanced Loops
// For loop (when you know how many times)
for (let i = 0; i < 5; i++) {
console.log("Count:", i);
}
// While loop (when you don't know how many times)
let count = 0;
while (count < 3) {
console.log("While count:", count);
count++;
}
// Do-while (runs at least once)
let number;
do {
number = Math.floor(Math.random() * 10);
console.log("Random number:", number);
} while (number !== 5);
// For-of loop (modern way to iterate arrays)
let fruits = ["apple", "banana", "orange"];
for (let fruit of fruits) {
console.log("I like", fruit);
}
// For-in loop (for objects)
let person = { name: "Alice", age: 25, city: "NYC" };
for (let key in person) {
console.log(key + ":", person[key]);
}
// Break and continue
for (let i = 0; i < 10; i++) {
if (i === 3) continue; // Skip 3
if (i === 7) break; // Stop at 7
console.log(i);
}
🎉 Control Flow Challenge!
Create a program that:
- Checks if a number is positive, negative, or zero
- Uses a switch statement for days of the week
- Creates a countdown from 10 to 1 using a loop
- Finds the largest number in an array
🔧 Function Parameters & Arguments
// Default parameters
function greet(name = "Guest", time = "day") {
return `Good ${time}, ${name}!`;
}
console.log(greet()); // "Good day, Guest!"
console.log(greet("Alice")); // "Good day, Alice!"
console.log(greet("Bob", "evening")); // "Good evening, Bob!"
// Rest parameters (collects multiple arguments)
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100
// Spread operator with functions
let numbers = [1, 2, 3, 4, 5];
console.log(Math.max(...numbers)); // 5 (spreads array as arguments)
📚 Function Scope & Closures
Think of closures like a backpack you carry around.
Even when you're far from home, you still have access to everything in your backpack. Closures let functions "remember" variables from their creation context.
// Global scope
let globalVar = "I'm global";
function outerFunction() {
let outerVar = "I'm from outer function";
function innerFunction() {
let innerVar = "I'm from inner function";
console.log(innerVar); // Can access innerVar
console.log(outerVar); // Can access outerVar
console.log(globalVar); // Can access globalVar
}
innerFunction();
// console.log(innerVar); // Error! Can't access innerVar here
}
outerFunction();
// Closure example
function createCounter() {
let count = 0; // This variable is "closed over"
return function() {
count++;
return count;
};
}
let counter1 = createCounter();
let counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (separate counter!)
🚀 Higher-Order Functions
// Functions that take functions as parameters
function processArray(array, callback) {
let result = [];
for (let item of array) {
result.push(callback(item));
}
return result;
}
let numbers = [1, 2, 3, 4, 5];
let doubled = processArray(numbers, x => x * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
let squared = processArray(numbers, x => x ** 2);
console.log(squared); // [1, 4, 9, 16, 25]
// Functions that return functions
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
🔧 Essential Array Methods
let fruits = ["apple", "banana", "orange"];
// Adding/removing elements
fruits.push("grape"); // Add to end: ["apple", "banana", "orange", "grape"]
fruits.unshift("mango"); // Add to start: ["mango", "apple", "banana", "orange", "grape"]
fruits.pop(); // Remove from end: ["mango", "apple", "banana", "orange"]
fruits.shift(); // Remove from start: ["apple", "banana", "orange"]
// Finding elements
console.log(fruits.indexOf("banana")); // 1
console.log(fruits.includes("apple")); // true
console.log(fruits.find(fruit => fruit.length > 5)); // "banana"
// Slicing and splicing
let numbers = [1, 2, 3, 4, 5];
console.log(numbers.slice(1, 4)); // [2, 3, 4] (doesn't modify original)
numbers.splice(2, 2, 10, 20); // Replace 2 elements with new ones
console.log(numbers); // [1, 2, 10, 20, 5]
🔄 Iteration Methods
let numbers = [1, 2, 3, 4, 5];
// forEach - Execute function for each element
numbers.forEach(num => console.log(num * 2));
// map - Transform each element
let doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]
// filter - Keep only elements that pass test
let evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4]
// reduce - Combine all elements into single value
let sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // 15
let max = numbers.reduce((max, num) => num > max ? num : max);
console.log(max); // 5
// some/every - Test conditions
console.log(numbers.some(num => num > 3)); // true (at least one > 3)
console.log(numbers.every(num => num > 3)); // false (not all > 3)
🏗️ Constructor Functions & Classes
// Constructor function (old way)
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function() {
return `Hello, I'm ${this.name}`;
};
}
let alice = new Person("Alice", 25);
console.log(alice.greet()); // "Hello, I'm Alice"
// Class syntax (modern way)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
celebrateBirthday() {
this.age++;
return `Happy birthday! Now ${this.age} years old.`;
}
}
let bob = new Person("Bob", 30);
console.log(bob.greet());
console.log(bob.celebrateBirthday());
🔗 Prototypes & Inheritance
// Inheritance with classes
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} makes a sound`;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // Call parent constructor
this.breed = breed;
}
speak() {
return `${this.name} barks!`; // Override parent method
}
fetch() {
return `${this.name} fetches the ball!`;
}
}
let dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.speak()); // "Buddy barks!"
console.log(dog.fetch()); // "Buddy fetches the ball!"
⏰ Understanding Asynchronous Code
Think of asynchronous code like ordering food at a restaurant.
You place your order (start async operation), then continue chatting with friends while waiting. When food arrives (operation completes), you get notified and can eat.
// Synchronous (blocking) - one thing at a time
console.log("Start");
console.log("Middle");
console.log("End");
// Output: Start, Middle, End
// Asynchronous (non-blocking) - can do other things while waiting
console.log("Start");
setTimeout(() => {
console.log("This happens later");
}, 1000);
console.log("End");
// Output: Start, End, "This happens later"
📋 Promises
// Creating a promise
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// Using promises
delay(1000)
.then(() => {
console.log("1 second passed");
return delay(500);
})
.then(() => {
console.log("Another 0.5 seconds passed");
})
.catch(error => {
console.error("Something went wrong:", error);
});
// Promise with resolve/reject
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
// Simulate API call
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, name: "John Doe" });
} else {
reject(new Error("Invalid user ID"));
}
}, 1000);
});
}
fetchUserData(123)
.then(user => console.log("User:", user))
.catch(error => console.error("Error:", error));
⚡ Async/Await
// Async function
async function getUserData() {
try {
console.log("Fetching user...");
let user = await fetchUserData(456);
console.log("User data:", user);
console.log("Fetching posts...");
let posts = await fetchPosts(user.id);
console.log("Posts:", posts);
} catch (error) {
console.error("Error:", error);
}
}
// Async function that returns a promise
async function fetchPosts(userId) {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, title: "My first post" },
{ id: 2, title: "Another post" }
]);
}, 500);
});
}
getUserData();
🎉 Async Challenge!
Create a program that:
- Simulates fetching user data (use setTimeout)
- Chains multiple async operations
- Handles errors properly
- Uses both promises and async/await
🚨 Understanding Errors
Think of error handling like having a spare tire in your car.
You hope you never need it, but when you get a flat tire, you're glad you have it. Error handling ensures your program can recover gracefully from unexpected situations.
// Different types of errors
try {
// ReferenceError: variable doesn't exist
console.log(undefinedVariable);
// TypeError: wrong type of operation
let num = 5;
num.toUpperCase();
// SyntaxError: invalid syntax (caught at parse time)
// console.log("Unclosed string);
} catch (error) {
console.log("Error caught:", error.message);
console.log("Error type:", error.name);
}
🛟 Try/Catch/Finally
// Basic try/catch
function divideNumbers(a, b) {
try {
if (b === 0) {
throw new Error("Division by zero!");
}
return a / b;
} catch (error) {
console.error("Error:", error.message);
return null;
}
}
console.log(divideNumbers(10, 2)); // 5
console.log(divideNumbers(10, 0)); // Error: Division by zero!
// Try/catch/finally
function processData(data) {
try {
console.log("Processing:", data);
// Simulate processing
if (!data) {
throw new Error("No data provided");
}
return data.toUpperCase();
} catch (error) {
console.error("Processing failed:", error.message);
return "ERROR";
} finally {
console.log("Cleanup completed");
// This always runs, even if there's an error
}
}
processData("hello"); // Processing: hello, Cleanup completed
processData(null); // Processing: null, Processing failed: ..., Cleanup completed
🎯 Custom Error Types
// Custom error classes
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field;
}
}
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
// Using custom errors
function validateUser(user) {
if (!user.name) {
throw new ValidationError("Name is required", "name");
}
if (!user.email) {
throw new ValidationError("Email is required", "email");
}
if (user.age < 18) {
throw new ValidationError("Must be 18 or older", "age");
}
}
function saveUser(user) {
try {
validateUser(user);
// Simulate saving to database
console.log("User saved successfully");
} catch (error) {
if (error instanceof ValidationError) {
console.log(`Validation error in ${error.field}: ${error.message}`);
} else {
console.log("Unexpected error:", error.message);
}
}
}
saveUser({ name: "John", email: "", age: 25 }); // Validation error
saveUser({ name: "", email: "john@example.com", age: 16 }); // Validation error
⚡ Async Error Handling
// Error handling with promises
function fetchUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId <= 0) {
reject(new Error("Invalid user ID"));
return;
}
if (userId > 1000) {
reject(new NetworkError("User not found", 404));
return;
}
resolve({ id: userId, name: "User " + userId });
}, 1000);
});
}
// Handling promise errors
fetchUserData(123)
.then(user => console.log("User:", user))
.catch(error => {
if (error instanceof NetworkError) {
console.log(`Network error ${error.statusCode}: ${error.message}`);
} else {
console.log("Error:", error.message);
}
});
// Async/await error handling
async function getUserProfile(userId) {
try {
const user = await fetchUserData(userId);
console.log("Profile for:", user.name);
return user;
} catch (error) {
console.error("Failed to get user profile:", error.message);
throw error; // Re-throw to let caller handle it
}
}
// Using with try/catch
async function displayUser(userId) {
try {
const user = await getUserProfile(userId);
console.log("Displaying user:", user);
} catch (error) {
console.log("Could not display user:", error.message);
}
}
displayUser(123); // Success
displayUser(-1); // Error
🎉 Error Handling Challenge!
Create robust error handling for:
- A calculator function with input validation
- An async API call with retry logic
- Custom error types for different scenarios
- Graceful degradation when errors occur