Web Development Advanced Course
Master the deepest parts of frontend engineering — advanced CSS, JavaScript internals, real-time APIs, performance, and full app architecture. This is where you go from writing code to engineering software.
Start LearningAdvanced CSS Grid: Named Areas & minmax
20 minWhat you'll learn
- Master grid-template-areas
- Use minmax() and auto-fit
- Build complex page layouts
You already know basic Grid — now it's time for production-grade layouts. grid-template-areas lets you name regions and place them visually, so your CSS reads like a wireframe. minmax() and auto-fit together create grids that automatically flow as many columns as fit — the magic behind responsive galleries and dashboards that adapt without a single media query.
.layout {
display: grid;
grid-template-areas:
'header header header'
'sidebar main main'
'footer footer footer';
grid-template-columns: 200px 1fr 1fr;
gap: 12px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
Try it yourself
Build a layout with header, two equal columns, and footer using grid-template-areas.
.l { display:grid; grid-template-areas:'h h' 'a b' 'f f'; grid-template-columns:1fr 1fr; }CSS Container Queries
20 minWhat you'll learn
- Understand container queries
- Style based on parent size
- Build reusable components
Media queries respond to the viewport; container queries respond to a parent's size. This is a game-changer for components: a card can change its layout based on how much room IT has, not how wide the screen is. Write a component once, drop it anywhere, and it adapts automatically. This is the modern way to build truly reusable UI.
.card-container {
container-type: inline-size;
}
.card {
display: block;
}
@container (min-width: 400px) {
.card {
display: flex;
gap: 16px;
}
}
Try it yourself
Make a component switch from column to row at 350px container width.
.wrap { container-type: inline-size; }
@container (min-width:350px) { .item { flex-direction: row; } }Fluid Typography with clamp()
16 minWhat you'll learn
- Use clamp() for fluid sizes
- Scale typography smoothly
- Build readable responsive text
clamp() picks a value between a minimum and maximum, with a preferred size in between. For typography, clamp(1rem, 2vw + 1rem, 2rem) makes text grow smoothly with the screen — no breakpoints, no sudden jumps. It's the modern replacement for dozens of media-query font-size overrides and keeps reading comfortable on every device.
h1 {
font-size: clamp(1.5rem, 4vw + 1rem, 3rem);
}
p {
font-size: clamp(1rem, 1vw + 0.75rem, 1.25rem);
line-height: 1.6;
}
Fluid Heading
This text scales smoothly with the viewport using clamp().
Try it yourself
Make a heading that scales from 1.2rem to 2.4rem.
h1 { font-size: clamp(1.2rem, 3vw + 0.5rem, 2.4rem); }Advanced Animations: Staggering & Easing
22 minWhat you'll learn
- Create staggered entrances
- Use custom cubic-bezier easing
- Build polished motion
Great animation is about timing. Staggering — delaying each item slightly — turns a flat list into an elegant cascade. cubic-bezier() gives you custom easing curves that feel natural instead of robotic. The difference between a site that feels cheap and one that feels premium is often just these two details applied with restraint.
.item {
opacity: 0;
animation: fadeUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }
.item:nth-child(4) { animation-delay: 0.3s; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
Staggered entrance
Try it yourself
Stagger 5 list items by 0.05s each.
.li:nth-child(1){animation-delay:0s}.li:nth-child(2){animation-delay:.05s}/* etc */CSS Scroll Snap
16 minWhat you'll learn
- Create snap-scrolling carousels
- Use scroll-snap-type
- Build touch-friendly galleries
Scroll snap makes content settle neatly into positions as users scroll — the backbone of carousels, galleries, and onboarding screens. The container declares scroll-snap-type, each item declares scroll-snap-align. The browser does the rest with smooth native scrolling, no JavaScript required. It's one of the most underrated modern CSS features.
.carousel {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
.carousel-item {
flex: 0 0 100%;
scroll-snap-align: center;
}
Try it yourself
Create a vertical snap container with three sections.
.c { overflow-y:auto; scroll-snap-type:y mandatory; height:100vh; }
.s { scroll-snap-align:start; height:100vh; }CSS Masking & Clipping
18 minWhat you'll learn
- Clip with clip-path
- Mask with images
- Create creative shapes
clip-path and mask let you cut elements into shapes — circles, polygons, even custom SVG paths. This powers everything from circular avatars to decorative section dividers. Unlike border-radius (which only rounds corners), clip-path can create any polygon shape, and masks can fade content out with gradients. It's how designers make pages feel bespoke.
.circle {
width: 120px;
height: 120px;
background: linear-gradient(135deg, #e11d48, #f59e0b);
clip-path: circle(50%);
}
.hex {
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}
Try it yourself
Clip an element into a triangle.
.tri { clip-path: polygon(50% 0%, 0% 100%, 100% 100%); }CSS Logical Properties
14 minWhat you'll learn
- Write RTL-friendly CSS
- Use margin-inline and padding-block
- Build international sites
Logical properties describe spacing and sizing relative to text direction, not physical left/right. margin-inline-start works on the correct side whether text flows left-to-right (English) or right-to-left (Arabic). If you build for international audiences, this is the difference between a site that works everywhere and one that breaks the moment it's translated.
.box {
padding-inline: 20px;
padding-block: 12px;
margin-inline-start: auto;
border-inline-start: 4px solid #e11d48;
}
Try it yourself
Replace margin-left with a logical property.
.x { margin-inline-start: 16px; }Pseudo-elements ::before and ::after
16 minWhat you'll learn
- Add decorative content
- Create tooltips and badges
- Avoid extra HTML
::before and ::after generate virtual elements without touching your HTML. They're perfect for decorative dots, icons, badges, tooltips, and animated underlines. Because they're pure CSS, they keep your markup clean and semantic. Master them and you'll find yourself writing less HTML and achieving more polished details.
.badge::after {
content: 'NEW';
position: absolute;
top: -8px;
right: -8px;
background: #e11d48;
color: white;
font-size: 0.6rem;
padding: 2px 6px;
border-radius: 50px;
}
Try it yourself
Add a small circle bullet before every list item using ::before.
li::before { content:''; display:inline-block; width:6px; height:6px; background:#e11d48; border-radius:50%; margin-right:6px; }CSS Theming with Custom Properties
18 minWhat you'll learn
- Build dark/light themes
- Create theme tokens
- Apply system preference
A theming system lets users switch between dark and light mode — or follow their OS preference automatically. You define semantic tokens (--bg, --text, --accent) at the :root level, then swap them inside a [data-theme='dark'] attribute selector. prefers-color-scheme detects the OS choice. This is how every professional site handles theming.
:root {
--bg: #ffffff;
--text: #1e1b4b;
--accent: #e11d48;
}
[data-theme='dark'] {
--bg: #0f172a;
--text: #f1f5f9;
--accent: #fb7185;
}
body {
background: var(--bg);
color: var(--text);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) { --bg: #0f172a; --text: #f1f5f9; }
}
Try it yourself
Add a --spacing token and use it for card padding.
:root { --spacing: 16px; }
.card { padding: var(--spacing); }CSS Performance: contain & will-change
16 minWhat you'll learn
- Understand rendering cost
- Use contain to isolate layout
- Apply will-change wisely
CSS performance matters at scale. contain tells the browser an element's styles don't affect anything outside it, so the renderer can skip expensive recalcs. will-change hints which properties will animate, so the browser pre-optimizes. Both improve scroll and animation smoothness on content-heavy pages — and both are easy to overuse, so apply them only where you measure problems.
.card {
contain: layout paint;
transition: transform 0.3s ease;
will-change: transform;
}
.card:hover {
transform: translateY(-4px);
}
Try it yourself
Add will-change: transform to a card that will animate.
.card { will-change: transform; transition: transform .2s; }Advanced Flexbox: flex-basis & order
16 minWhat you'll learn
- Control item sizing precisely
- Reorder items visually
- Build complex responsive rows
flex-basis sets an item's starting size before growing/shrinking; flex-grow distributes extra space; order changes visual position without touching HTML. Together they let you build layouts where a sidebar is 250px, the main content flexes to fill, and on mobile the whole order reflows — all with three properties and no media-query hacks.
.container {
display: flex;
gap: 12px;
}
.sidebar {
flex: 0 0 250px;
}
.main {
flex: 1 1 auto;
}
@media (max-width: 700px) {
.sidebar { order: 2; }
.main { order: 1; }
.container { flex-direction: column; }
}
Try it yourself
Make one item twice as wide as its siblings using flex-grow.
.a { flex: 2; } .b, .c { flex: 1; }CSS 3D Transforms
20 minWhat you'll learn
- Rotate in 3D space
- Use perspective
- Build flip cards
3D transforms add depth — rotateY, rotateX, and perspective make elements feel like physical cards. The classic flip card is built by rotating a front and back face 180 degrees apart. Perspective on the parent creates the depth illusion. It's flashy, so use it sparingly — but for profile cards and product images, it's delightful.
.flip-card {
perspective: 1000px;
}
.flip-inner {
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flip-card:hover .flip-inner {
transform: rotateY(180deg);
}
.flip-front, .flip-back {
backface-visibility: hidden;
}
.flip-back {
transform: rotateY(180deg);
}
Try it yourself
Add a rotateX tilt effect on hover.
.tilt:hover { transform: perspective(500px) rotateX(10deg); }JavaScript Closures Deep Dive
22 minWhat you'll learn
- Understand lexical scope
- Create private state
- Build factory functions
A closure is a function that remembers the variables from where it was created, even after that outer function has finished. This is how JavaScript achieves private state without classes — the inner function 'closes over' the outer variables. Closures power callbacks, event handlers, factories, and the module pattern. It's arguably the single most important JavaScript concept to truly internalize.
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
Try it yourself
Create a makeAdder(n) that returns a function adding n to its argument.
function makeAdder(n) { return x => x + n; }
const add5 = makeAdder(5);
add5(3); // 8Prototypes & Prototypal Inheritance
20 minWhat you'll learn
- Understand the prototype chain
- Use Object.create
- Grasp inheritance without classes
JavaScript uses prototypal inheritance, not classical. Every object has a hidden link to a prototype object, and property lookups walk that chain. This is how 'methods' work — they're just properties found on the prototype. Classes are syntax sugar over this system, so understanding prototypes makes the class behavior finally click.
const animal = {
speak() { return 'Sound'; }
};
const dog = Object.create(animal);
dog.speak = function() { return 'Woof'; };
console.log(dog.speak());
console.log(Object.getPrototypeOf(dog) === animal);
Try it yourself
Create two objects where one inherits from the other, then override a method.
const parent = { greet(){return 'hi'} };
const child = Object.create(parent);
child.greet = () => 'hello';
child.greet(); // 'hello'The Event Loop & Microtasks
22 minWhat you'll learn
- Understand call stack and task queue
- Distinguish microtasks from macrotasks
- Predict async execution order
JavaScript is single-threaded, yet it handles async — thanks to the event loop. Synchronous code runs first, Promises (microtasks) run before setTimeout (macrotasks). Understanding this ordering explains the most confusing interview questions and real bugs. Once you know microtasks jump the queue, async behavior stops being mysterious.
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
Try it yourself
Predict: console.log('a'); setTimeout(()=>console.log('b')); Promise.resolve().then(()=>console.log('c'));
a, c, b
Advanced Promises: allSettled, any, race
20 minWhat you'll learn
- Combine multiple promises
- Handle partial failures
- Pick the fastest result
Beyond the basics, Promise combinators handle real-world concurrency. Promise.all rejects if ANY promise fails — brittle for independent tasks. Promise.allSettled waits for all and reports each success/failure. Promise.any resolves with the first success, Promise.race with the first settlement. Choosing the right combinator is what separates robust async code from fragile code.
const tasks = [
fetch('/api/a'),
fetch('/api/b'),
fetch('/api/c')
];
const results = await Promise.allSettled(tasks);
results.forEach((r, i) => {
console.log(i, r.status, r.value || r.reason);
});
Try it yourself
Use Promise.any to fetch from two mirrors and use whichever succeeds first.
const res = await Promise.any([fetch(mirror1), fetch(mirror2)]);
Generators & Iterators
20 minWhat you'll learn
- Create iterable objects
- Use function* and yield
- Build lazy sequences
Generators (function*) produce values lazily with yield, pausing between each one. Unlike an array that computes everything eagerly, a generator produces the next value only when asked. This enables infinite sequences, memory-efficient data processing, and custom iteration. They're niche but powerful — essential for anyone writing libraries or processing large datasets.
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
Try it yourself
Write a generator that yields even numbers forever.
function* evens(){ let n=0; while(true){ yield n; n+=2; } }Proxy & Reflect
22 minWhat you'll learn
- Intercept object operations
- Build reactive data
- Understand meta-programming
A Proxy wraps an object and intercepts operations — getting, setting, deleting properties. This is how reactivity systems (like Vue's) detect changes. Reflect provides the default behavior cleanly. Proxies let you build validation, logging, and 'auto-saving' objects. It's advanced meta-programming, but understanding it reveals how modern frameworks work under the hood.
const target = { name: 'Sufyan' };
const handler = {
set(obj, prop, value) {
console.log(`Setting ${prop} to ${value}`);
return Reflect.set(obj, prop, value);
}
};
const proxy = new Proxy(target, handler);
proxy.name = 'Ali'; // logs 'Setting name to Ali'
Try it yourself
Create a proxy that logs every property read.
const h = { get(o,p){ console.log('read',p); return o[p]; } };
const p = new Proxy({a:1}, h);
p.a; // logs 'read a'Symbols & Well-known Symbols
16 minWhat you'll learn
- Create unique keys
- Avoid property collisions
- Customize built-in behavior
Symbols are guaranteed-unique values, perfect for object keys that must never collide. Well-known symbols like Symbol.iterator let you customize how built-in operations treat your objects — defining Symbol.iterator on an object makes it work with for...of. They're how JavaScript extends itself, and they power many advanced patterns.
const id = Symbol('id');
const user = { [id]: 123, name: 'Sufyan' };
const iterable = {
[Symbol.iterator]: function* () {
yield 1; yield 2; yield 3;
}
};
console.log([...iterable]); // [1, 2, 3]
Try it yourself
Create a unique Symbol and use it as an object key.
const k = Symbol('key');
const o = {}; o[k] = 'value';
o[k]; // 'value'Map, Set, WeakMap, WeakSet
20 minWhat you'll learn
- Use Map for key-value data
- Use Set for unique values
- Understand weak collections
Map stores key-value pairs with any key type (not just strings like objects). Set stores unique values and answers 'contains?' instantly. WeakMap/WeakSet hold weak references — entries vanish when nothing else uses them, which prevents memory leaks for DOM-tracking and caches. These four are the modern replacements for object-as-dictionary and array.includes hacks.
const map = new Map();
map.set('user', { name: 'Sufyan' });
map.set(1, 'one');
const set = new Set([1, 2, 2, 3]);
console.log(map.get('user'), set.size, set.has(2));
Try it yourself
Use a Set to remove duplicates from [1,1,2,3,3].
const arr = [1,1,2,3,3]; const unique = [...new Set(arr)]; // [1,2,3]
ES Modules
18 minWhat you'll learn
- Use import/export
- Organize code into modules
- Understand tree-shaking
ES modules split code into files that explicitly import and export what they need. Named exports share multiple values; default exports share one. Modules enable tree-shaking — bundlers can drop unused code, shrinking your final bundle. This is the foundation of modern frontend architecture, and the standard everywhere from Node to browsers to bundlers.
// utils.js
export function add(a, b) { return a + b; }
export const PI = 3.14159;
// main.js
import { add, PI } from './utils.js';
console.log(add(2, 3), PI);
Try it yourself
Create a module exporting two functions and import one of them.
// a.js
export const f1 = () => 1;
export const f2 = () => 2;
// b.js
import { f1 } from './a.js';Debounce & Throttle
20 minWhat you'll learn
- Limit expensive function calls
- Debounce input handlers
- Throttle scroll events
Debounce and throttle control how often a function runs. Debounce waits until activity stops — perfect for search inputs (fire after the user pauses typing). Throttle enforces a maximum rate — perfect for scroll and resize handlers. Without them, you'd fire hundreds of expensive operations per second and tank performance.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
const search = debounce((q) => console.log('Searching:', q), 500);
search('h'); search('he'); search('hel'); // only 'hel' logs after 500ms
Try it yourself
Write a throttle that runs at most once per 300ms.
function throttle(fn, ms){ let last=0; return (...a)=>{ const now=Date.now(); if(now-last>=ms){ last=now; fn(...a); } }; }Currying & Function Composition
20 minWhat you'll learn
- Transform multi-arg functions
- Compose small functions
- Write functional code
Currying converts a function taking multiple arguments into a chain of single-argument functions. Composition builds complex behavior by piping functions together: compose(f, g)(x) = f(g(x)). These patterns create tiny, reusable, testable pieces that snap together — the heart of functional programming, and increasingly common in modern frontend (Redux, React hooks, etc.).
const curry = (fn) => (a) => (b) => fn(a, b); const add = (a, b) => a + b; const curriedAdd = curry(add); const compose = (f, g) => (x) => f(g(x)); const double = x => x * 2; const inc = x => x + 1; const doubleThenInc = compose(inc, double); console.log(curriedAdd(2)(3)); // 5 console.log(doubleThenInc(5)); // 11
Try it yourself
Curry a multiply(a,b) function and use it to create a double function.
const mult = a => b => a*b; const double = mult(2); double(5); // 10
Memoization
16 minWhat you'll learn
- Cache function results
- Avoid recomputation
- Speed up expensive functions
Memoization caches a function's result for each set of arguments, so calling it again with the same inputs returns instantly. It's perfect for expensive pure functions — recursive Fibonacci, sorting, API calls with repeated queries. The trade-off is memory, so memoize only functions where the same inputs occur frequently.
function memoize(fn) {
const cache = new Map();
return (arg) => {
if (cache.has(arg)) return cache.get(arg);
const result = fn(arg);
cache.set(arg, result);
return result;
};
}
const slowSquare = memoize(n => { console.log('computing'); return n * n; });
slowSquare(4); slowSquare(4); // 'computing' logs once
Try it yourself
Memoize a factorial function.
const cache = {};
function fact(n){ if(n in cache) return cache[n]; cache[n] = n<=1?1:n*fact(n-1); return cache[n]; }Recursion & Tree Traversal
22 minWhat you'll learn
- Solve problems recursively
- Traverse nested structures
- Handle deeply nested data
Recursion is a function calling itself until a base case. It's the natural tool for nested data — JSON structures, file trees, comment threads. Tree traversal (visiting every node) is recursion's killer app. Once you can walk a tree, you can render comments, build menus, and process any hierarchical data with confidence.
function walk(node) {
console.log(node.name);
(node.children || []).forEach(walk);
}
const tree = {
name: 'root',
children: [
{ name: 'a', children: [{ name: 'a1' }] },
{ name: 'b' }
]
};
walk(tree);
Try it yourself
Write a recursive sum for nested number arrays.
function sum(arr){ return arr.reduce((t,x)=>t+(Array.isArray(x)?sum(x):x),0); }Regular Expressions
22 minWhat you'll learn
- Match patterns with regex
- Validate inputs
- Extract data from strings
Regular expressions describe text patterns — emails, phone numbers, dates, URLs. They're a language unto themselves, and while they look like line noise at first, they're unmatched for validation and text extraction. Combined with String methods like match, replace, and test, regex is one of the highest-leverage tools for real-world data handling.
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
console.log(emailPattern.test('[email protected]')); // true
console.log(emailPattern.test('not-an-email')); // false
const text = 'Order #1234 confirmed';
console.log(text.match(/\d+/)[0]); // '1234'
Try it yourself
Write a regex to check if a string is all digits.
const isDigits = /^\d+$/.test('12345'); // trueThe Module Pattern & Encapsulation
16 minWhat you'll learn
- Create private state
- Expose public APIs
- Organize code without classes
The module pattern uses closures to create private variables and return a public API. It was THE way to organize JavaScript before ES modules, and the pattern still appears everywhere — in plugins, libraries, and legacy code. Understanding it means reading and maintaining a decade of JavaScript codebase with confidence.
const Counter = (function() {
let count = 0;
return {
increment() { count++; return count; },
get value() { return count; }
};
})();
Counter.increment();
Counter.increment();
console.log(Counter.value); // 2
console.log(Counter.count); // undefined — private!
Try it yourself
Create a module with a private password and a public check() method.
const Auth = (()=>{ const pw='secret'; return { check(p){ return p===pw; } }; })();Advanced Error Handling & Custom Errors
18 minWhat you'll learn
- Create custom error classes
- Classify error types
- Build robust error boundaries
Real apps fail in specific, knowable ways — validation errors, network errors, auth errors. Custom error classes let you throw and catch typed errors, so your handlers can respond appropriately instead of a blanket catch. Combined with try/catch at boundaries, this turns cryptic failures into clear, recoverable states.
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
function validateAge(age) {
if (age < 18) throw new ValidationError('Must be 18+');
return true;
}
try {
validateAge(15);
} catch (e) {
if (e instanceof ValidationError) console.log('Validation:', e.message);
else console.log('Unknown:', e);
}
Try it yourself
Create a NetworkError class and catch it separately.
class NetworkError extends Error {}
try { throw new NetworkError('offline'); } catch(e){ if(e instanceof NetworkError) console.log('net'); }Intersection Observer
20 minWhat you'll learn
- Detect element visibility
- Build infinite scroll
- Implement lazy loading
IntersectionObserver watches when elements enter or leave the viewport. It replaces scroll-position math with an efficient callback API — perfect for infinite scroll, lazy-loading images, scroll-triggered animations, and 'has the user seen this?' analytics. It's dramatically more performant than listening to scroll events.
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Visible:', entry.target.id);
}
});
}, { threshold: 0.5 });
observer.observe(document.querySelector('#section2'));
Try it yourself
Observe an element and log when it's fully visible (threshold 1).
new IntersectionObserver(cb, { threshold: 1 }).observe(el);Mutation Observer
16 minWhat you'll learn
- Watch DOM changes
- React to dynamic content
- Build live-updating UI
MutationObserver watches the DOM and fires when nodes are added, removed, or changed. It's the tool for reacting to content you don't control — third-party widgets, dynamically injected markup, or observing your own renders. When you need to run code 'after something appears on the page', this is the clean way.
const observer = new MutationObserver((mutations) => {
mutations.forEach(m => {
console.log('Added nodes:', m.addedNodes.length);
});
});
observer.observe(document.body, { childList: true, subtree: true });
Try it yourself
Observe a container and log whenever a child is added.
new MutationObserver(ms => ms.forEach(m => console.log(m.addedNodes.length))).observe(container, { childList: true });Service Workers & Offline Support
25 minWhat you'll learn
- Register a service worker
- Cache assets for offline
- Understand PWA basics
Service workers run in the background, independent of the page, enabling offline support, background sync, and push notifications — the heart of Progressive Web Apps. They intercept network requests and serve cached responses. This is how apps like Twitter Lite work on flaky connections, and it's a career-defining skill for frontend developers.
// Register (main.js)
navigator.serviceWorker.register('/sw.js');
// sw.js
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('v1').then(cache =>
cache.addAll(['/', '/styles.css', '/app.js'])
)
);
});
self.addEventListener('fetch', (e) => {
e.respondWith(
caches.match(e.request).then(res => res || fetch(e.request))
);
});
Try it yourself
Register a service worker at /sw.js.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}Fetch Advanced: AbortController & Streaming
22 minWhat you'll learn
- Cancel in-flight requests
- Handle large responses
- Build robust data loading
AbortController lets you cancel fetch requests — essential when a user navigates away or types a new search. Streaming responses read data incrementally with response.body.getReader(), enabling progress bars and processing huge files without loading them all into memory. These two features turn simple fetch into production-grade data handling.
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch('/api/slow', { signal: controller.signal });
const data = await res.json();
console.log(data);
} catch (e) {
if (e.name === 'AbortError') console.log('Request cancelled');
} finally {
clearTimeout(timeout);
}
Try it yourself
Create an AbortController and abort after 2 seconds.
const c = new AbortController();
setTimeout(() => c.abort(), 2000);
fetch(url, { signal: c.signal });WebSockets for Real-time Apps
25 minWhat you'll learn
- Establish a WebSocket connection
- Send and receive messages
- Build real-time features
WebSockets open a persistent two-way connection — the server can push messages without the client asking. This powers chat, live dashboards, multiplayer games, and stock tickers. Unlike polling (wasteful) or HTTP requests (one-way), WebSockets deliver true real-time with minimal overhead.
const socket = new WebSocket('wss://example.com/chat');
socket.addEventListener('open', () => {
socket.send('Hello server!');
});
socket.addEventListener('message', (event) => {
console.log('Received:', event.data);
});
socket.addEventListener('close', () => {
console.log('Disconnected');
});
Try it yourself
Connect to a WebSocket and log the open event.
const ws = new WebSocket('wss://x.com');
ws.addEventListener('open', () => console.log('connected'));Canvas API Basics
25 minWhat you'll learn
- Draw shapes and lines
- Render text on canvas
- Build data visualizations
Canvas gives you a pixel grid to draw on with JavaScript — shapes, lines, text, gradients, and images. It powers charts, games, image editors, and animations where the DOM is too slow. Unlike SVG (vector), Canvas is raster — perfect for thousands of moving objects, particle effects, and custom visualizations.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#e11d48';
ctx.fillRect(10, 10, 100, 60);
ctx.beginPath();
ctx.arc(150, 40, 30, 0, Math.PI * 2);
ctx.fillStyle = '#6366f1';
ctx.fill();
Try it yourself
Draw a green circle at (100, 100) with radius 40.
ctx.fillStyle='green'; ctx.beginPath(); ctx.arc(100,100,40,0,Math.PI*2); ctx.fill();
Drag & Drop API
20 minWhat you'll learn
- Make elements draggable
- Handle drop targets
- Build sortable interfaces
Native drag-and-drop lets users grab elements and move them — useful for file uploads, kanban boards, and sortable lists. It's event-driven: dragstart on the source, dragover and drop on the target. While finicky, native DnD avoids pulling in a heavy library for simple use cases.
const item = document.querySelector('#item');
const zone = document.querySelector('#zone');
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', 'item');
});
zone.addEventListener('dragover', (e) => e.preventDefault());
zone.addEventListener('drop', (e) => {
e.preventDefault();
console.log('Dropped!');
});
Try it yourself
Add a dragstart that sets some data.
el.addEventListener('dragstart', e => e.dataTransfer.setData('text', 'hello'));Custom Events
16 minWhat you'll learn
- Create and dispatch custom events
- Decouple components
- Build event-driven UIs
Custom events let different parts of your app communicate without direct coupling. Component A dispatches a 'userLoggedIn' event; components B and C listen and react independently. This loose coupling makes code more modular and testable — a pattern used heavily in framework design and large applications.
const loginEvent = new CustomEvent('userLoggedIn', {
detail: { name: 'Sufyan' }
});
document.addEventListener('userLoggedIn', (e) => {
console.log('Welcome', e.detail.name);
});
document.dispatchEvent(loginEvent);
Try it yourself
Dispatch a 'cartUpdated' event with { count: 3 }.
document.dispatchEvent(new CustomEvent('cartUpdated', { detail: { count: 3 } }));Web Components (Custom Elements)
25 minWhat you'll learn
- Create custom HTML tags
- Encapsulate with Shadow DOM
- Build framework-free components
Web Components let you define your own HTML elements — <user-card>, <todo-item> — that work in any framework or no framework at all. Custom Elements plus Shadow DOM give you true encapsulation (styles don't leak in or out). They're the platform-native answer to React/Vue components, and browsers now support them natively.
class UserCard extends HTMLElement {
connectedCallback() {
this.innerHTML = '<div class="card">' + this.getAttribute('name') + '</div>';
}
}
customElements.define('user-card', UserCard);
Try it yourself
Define a custom <greeting-tag> element.
class G extends HTMLElement { connectedCallback(){ this.textContent='Hello'; } }
customElements.define('greeting-tag', G);State Management Patterns
22 minWhat you'll learn
- Understand UI state
- Implement a simple store
- Use pub/sub patterns
As apps grow, shared state gets messy — components need the same data but live far apart. A central store with subscribe/notify solves this: components read from the store and re-render when it changes. This is the core idea behind Redux, Vuex, and every state library — and you can build a basic version in 20 lines to truly understand it.
function createStore(initial) {
let state = initial;
const listeners = new Set();
return {
getState: () => state,
setState: (next) => {
state = next;
listeners.forEach(fn => fn(state));
},
subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }
};
}
const store = createStore({ count: 0 });
store.subscribe(s => console.log('State:', s));
store.setState({ count: 1 });
Try it yourself
Add an unsubscribe function to the store.
subscribe(fn){ ls.add(fn); return () => ls.delete(fn); }Code Splitting & Lazy Loading
22 minWhat you'll learn
- Split code into chunks
- Load modules on demand
- Reduce initial bundle size
Code splitting breaks your app into chunks that load only when needed. Dynamic import() returns a Promise that loads a module on demand — perfect for routes, heavy libraries, and features users might never open. The result: faster initial load, less JavaScript parsed, better performance scores. Every serious app ships with some form of this.
const button = document.querySelector('#loadChart');
button.addEventListener('click', async () => {
const { drawChart } = await import('./chart.js');
drawChart();
});
Try it yourself
Dynamically import a module and call one of its exports.
const m = await import('./util.js');
m.doSomething();Capstone: Build a Complete SPA
50 minWhat you'll learn
- Combine all advanced skills
- Build a single-page app
- Ship a portfolio project
Your final project: a single-page app with client-side routing, a central store, lazy-loaded views, and real API data — no framework. This pulls together every advanced skill: modules, state management, async/await, error handling, observers, and performance. It's the kind of project that proves you're ready for real frontend work and belongs in your portfolio.
// app structure
const routes = {
'#/home': () => renderHome(),
'#/users': async () => {
const users = await fetch('/api/users').then(r => r.json());
renderUsers(users);
}
};
function router() {
const hash = location.hash || '#/home';
(routes[hash] || routes['#/home'])();
}
window.addEventListener('hashchange', router);
router();
5
1.2k
Try it yourself
Add a #/about route to the router.
routes['#/about'] = () => renderAbout();
You've completed all 40 advanced lessons. You're now a frontend engineer.
Practice your skills or return to the Web Development hub.