
What this course is for, who it is aimed at, and why a course that has been growing for ten years is the right shape for a language that never throws anything away. Every modern feature is a layer over an older one, and you learn both.
Every example on this course is a real file in one public repo on GitHub. Clone it once and you can run anything you see me run. The clone command, a map of which chapter's code sits in which folder, and where the setup instructions live.
The four places code gets run in this course — Chrome DevTools snippets, VS Code with F8, node from the terminal, and the animated explainers — so none of them catches you out later. The full setup is written up below the video, so you can follow along in whichever one you prefer.
A quick map of the Basics section: what we cover, and why these are the pieces everything later leans on.
There is no JavaScript Inc. The language is an agreement, run in the open by TC39 on GitHub. How a proposal moves from stage 0 to stage 4, what ES6 and ES2015 have to do with each other, and why nothing is ever taken away.
Two different answers to the same problem — writing modern JavaScript that runs on older engines. A compiler rewrites syntax you could have written by hand; a polyfill supplies a runtime feature that was simply missing. Knowing which is which tells you why some things can never be compiled into existence.
What strict mode actually changes about the way your code runs, and why the silent failures it catches used to be so easy to ship. Modern JavaScript switches it on for you in modules and classes, so this is really about knowing what it is doing on your behalf — and it is still a common interview question.
The interview classic, and the follow-up that actually decides it. Primitives go by value and objects by reference, but reassigning the parameter inside the function does not touch the caller's variable — and that is the part people get wrong.
Why the old arguments object was never good enough: it hides the contract, it is awkward to mix with fixed parameters, and it is not a real array. Rest parameters fix all three, with one rule — it has to come last.
Rest and spread are the same three dots doing opposite jobs. Merging arrays, copying one properly instead of quietly aliasing it, and spreading a runtime array into a function's parameters.
Backticks, and the depth behind them: real multi-line strings, and interpolation that takes any expression rather than just a variable. This is also the lecture that sets up template tags.
The follow-up question to template strings. A tag is just a function handed the literal split into pieces, with the interpolated values arriving separately — the machinery behind styled-components and every i18n tag you have used.
A short map of the section: the types JavaScript actually has, and the equality rules that grew out of how they evolved.
The types the specification defines, typeof and its famous lie about null, dynamic versus static typing, and the real difference between undefined and null — one is set by the engine, the other only ever by you.
Why == and === are not interchangeable. Coercion worked through on the cases that surprise people, and the equality table that explains why the answer has been === for years.
NaN is a number, and it is the only value in JavaScript that is not equal to itself. Why the global isNaN cannot be trusted, and the self-inequality check that is what an interviewer is fishing for.
Map and Set, and when to reach for them instead of an object and an array: keys of any type, a real size, and values that are unique by construction. Including the object-as-a-key bug a plain object will never warn you about.
The two primitive types everybody forgets. A Symbol is unique forever no matter what you label it, and a BigInt holds whole numbers past the point where an ordinary Number quietly starts rounding.
A short map of the section, and a warning: JavaScript's scoping rules changed, but the old ones never went away, so you need both sets at once.
Global, function and block scope, and why a var still leaks out of a for loop while let and const do not. Including the historical reason nothing about var was ever fixed.
let and const are the fixes to two rough edges on var. What each one actually guarantees, and the strange one interviewers follow up with — the temporal dead zone, and what happens when you touch a variable before its declaration.
Declarations are moved to the top of their scope, assignments are not, which is why a variable can be undefined rather than an error. And the trap: a function declaration is hoisted whole, a function stored in a var is not.
Two scripts, two globals with the same name, one silently overwriting the other. Function scope as the fix, and the immediately invoked function expression as the way it was written before modules existed.
How a variable lookup walks outward through nested scopes, and the fact that settles every tricky case: the chain is lexical, decided by where the code is written and not by who called it.
Closures shown in the debugger — an inner function keeping hold of the variables it needs long after the outer function has returned. Then the interview classic: the loop that prints the same number ten times, and how it gets fixed.
A short map of the section: destructuring, and the four different ways JavaScript will let you loop over the same array.
Pulling values straight out of objects and arrays, renaming as you go, and destructuring in a function's parameter list with defaults for what is missing. Plus the advice that goes with it — one level deep, no more.
Four ways to loop and what each one costs you. break and continue work in a classic for and in for-of but not inside forEach, and for-in over an array hands you string indexes, which is a trap that is far too late to fix.
The array methods that landed after map, filter and reduce: at() for reaching in from either end, findLast, and the copying versions toSorted and toReversed — which quietly fix a mutation bug you have very probably shipped.
map, filter, take and drop, living on the iterator instead of the array — so a Set, a Map or a generator goes straight through without being spread into an array first. And they are lazy, which is the part that makes them worth a lecture.
A short map of the section, and a claim: if there were only one interview question left, it would be about this.
this is decided by how a function is called, not by where it is written — worked out live through a run of experiments, including the nested-function trap that silently writes to the global object.
Three ways to pin down this: call and apply invoke a function with the context you choose, bind fixes it permanently. Along the way, the fact that makes all of it possible — functions are objects.
Arrow functions are not mainly about being shorter. They take this lexically from where they are written, which fixes the callback case and breaks the method case, and knowing which is which is the whole point.
A short map of the section, and a warning: object orientation in JavaScript is not what it is in any other language you know. Take these lectures in order.
Property lookup walks the object, then its prototype, then its prototype's prototype. Shown live: linking objects by hand, watching a lookup resolve, the shadowing gotcha when you write to a property the prototype owns, and Object.create as the way to do it properly.
A class is a blueprint; a prototype is an existing object you build the next one from. What people mean when they say classical inheritance in JavaScript, and why there is no such thing underneath.
Function constructors, and what new actually does — approximated by hand until there is no magic left in it. Then the two places a method can live, on this or on the prototype, and the trade-off between shared memory and genuinely private variables.
Inheritance in the constructor pattern: calling the base constructor with call, and then the one line that actually links the two prototypes together. Shown failing first, so it is obvious what Object.create is fixing.
The other way to do object orientation — no constructors, no new, just objects created from other objects. Three ways to initialise them, inheritance by extending the chain, and an honest comparison with the constructor pattern.
class, extends and super, with getters and setters doing real validation. Then the payoff: DevTools showing it is still the same prototype chain underneath, proved by having an ES6 class extend an old ES5 constructor function.
The two habits ES2022 retired: state assembled line by line in the constructor, and an underscore prefix meaning please do not touch. Public class fields declare state at the top, and hash-private is privacy the language actually enforces.
A short map of the section: what asynchronous really means, taken all the way down to the hardware, with the advantages and the costs of both ways of working.
Synchronous code is easier to read and easier to predict. Asynchronous code is what makes Node and the browser fast. The difference between them, and how to tell which one a piece of work wants.
Blocking is something developers invented; the hardware underneath was never blocking. What really happens on a file read, the latency numbers scaled to human time, and what a process context switch costs you.
Processes, threads and shared memory — the classic race condition, locks as the fix, and the deadlocks that arrive with them. This is the approach JavaScript deliberately did not take.
Node abstracts I/O into events handled on an implicit event loop. That one sentence, unpacked: where your single thread actually is, what the other threads are doing, and why none of your callbacks ever needs a lock.
V8 is an engine, and on its own it is synchronous. Node, Chrome and Electron are containers that embed it and add their own capabilities — which is exactly why the same asynchronous code can behave differently in each.
A short map of the section: callbacks first, then promises, then async/await, then generators — plus the handful of functions every demo from here on leans on.
A function you hand to another function, to be called back when it is ready. This is the definition the rest of the section is built on: what a callback actually is, why JavaScript leans on them so heavily, and the shape every async API in the language inherited from it.
A trick question to open with: code that fails because the callback is invoked immediately rather than later. Fixing it with setImmediate or process.nextTick, and the lesson underneath — a callback is not asynchronous just because it is a callback.
Three things you can do with an error in a callback, and the convention the whole Node ecosystem settled on without ever voting: error first. Why that position matters the moment you need to pass a failure up the stack.
An exercise in the failure everyone has shipped — an error handed to a callback and then never looked at again. Passing it up the stack properly, shown both ways round.
Independent tasks can fire side by side; dependent ones have to nest, and a few of those stacked up is how the shape got its name. This is the problem promises were invented to solve.
An exercise that looks like it works and does not. try/catch around an asynchronous callback catches nothing — what you actually saw was Node's uncaught handler, and there is a marker log to prove the catch block never ran.
Where promises came from, how to build one with resolve and reject, and how to consume it with then. This is the pattern the rest of the section is built on.
An exercise: wrap Node's callback-based readFile so it hands back a promise instead. Then the shortcut, util.promisify, and an honest note on when it does not fit.
A then handler still fires on a promise that has already resolved, which is why promises are always asynchronous and raw callbacks are not. Then chaining, where each handler's return value feeds the next, and forking, where two handlers on the same promise both get the original value.
An exercise: read a file from disk, then gzip it, using promises. It works — and the nested then inside a then looks suspiciously like the thing promises were supposed to fix.
The single most important composition rule in the section: return a promise from a then handler and the chain waits for it, then passes its resolved value on. This is what flattens the nesting.
An exercise: take the nested read-then-gzip solution and flatten it into one chain by returning the inner promise. It ends up reading like the sentence you would say out loud.
Errors fall down the chain until something catches them. Throwing inside a handler, returning a rejected promise, why one catch at the end is usually all you need, and what .catch really is underneath.
An exercise: swap per-stage error handlers for a single catch at the end of the chain, and weigh up what you gain and what you give up by doing it.
finally runs after a promise settles, whichever way it went — the cleanup half of try/catch/finally, for closing the connection or hiding the spinner regardless of the outcome.
Promise.all takes an array of promises and waits for every one of them. Its whole personality is fail-fast: the first rejection settles it, and you never hear about the rest.
Promise.race settles the moment the first promise settles, whether that one resolved or rejected. Three demos where the winner keeps changing, and an honest note on how rarely it is the right tool.
An exercise: build a read-with-timeout out of Promise.race and a promise that rejects on a timer. Doing it by hand once teaches you more about promises than any explanation will.
The section's capstone: publish a file, spot a 403, authenticate, resume, and time the whole thing out. Including the flaw in the obvious answer — the race only covers the first step — and how composing the chain fixes it.
Three modern statics for batch work. allSettled tells you how every promise went, failures included; any gives you the first that actually works; and withResolvers changes who is allowed to settle a promise in the first place.
AbortController is the one standard way to stop asynchronous work that is already running: a signal you pass in, and an abort you call. Including the surprise — cancelled work comes back as a rejection, not a quiet resolve.
There is a developer on every team who always knows why. When the bug makes no sense, when the async code fires in the wrong order, when nobody can explain what the this keyword is actually bound to... that is who everyone goes to. This course is about becoming that developer.
In around twelve hours you will go from writing JavaScript to genuinely understanding it: the difference between a good developer and the senior one in the room. You will dramatically improve your chances of getting past a technical interview, landing the job you actually want, and being paid what that developer gets paid.
If you are like me, you learnt JavaScript by muddling along: seeing what worked, picking up a thing or two every day. It gets you a long way (until it doesn't). Without the deeper fundamentals you hit head-scratching bugs, you find framework and library source hard to read, and in an interview you can hear yourself describing what happens without being able to say why.
Every lecture is an interview question, because the moment you properly learn something is when there is an interview in front of you. There is always an obvious right answer, and a good interviewer goes one step past it. That step is where this course lives.
What are you going to learn?
Types & Equality — the types JavaScript really has, and what it takes for two values to be equal.
Scopes & Variables — hoisting, the scope chain, closures, and the temporal dead zone.
Destructuring & Modern Arrays — destructuring, spread and rest, the array methods that landed after map and filter, and iterator helpers.
This — a whole section on the this keyword. A deep understanding of it is core to becoming a senior JavaScript developer.
Object Orientation — the history of OO in JavaScript, from the prototype chain through the constructor pattern to the class syntax. You need the earlier patterns to truly understand the current one.
Async, properly — five sections on it: what asynchronous really means, callbacks and the mess they make, promises and every combinator, async/await, and generators with async iterators.
The Event Loop — taken apart frame by frame, in Node and in the browser, until the ordering questions stop being guesswork.
Networking & Events — CORS and the same-origin policy, fetch, event capturing and bubbling, and event delegation.
Modern Platform — ES modules versus CommonJS, how memory is managed, where TypeScript fits, and what landed in ES2026.
Exercises run the whole way through. You predict what the code prints, then we run it and find out together.
Why an interview format?
I find it is only when I am facing an upcoming interview that I get into gear and really make sure I have a deep understanding of what I claim to know. I might know the best practice for solving a problem... but do I know why?
JavaScript interviews are designed to dig deeper, to see whether you are mimicking what you have read or whether you actually understand it. Also, it is FUN. What is more satisfying than learning something and then passing the test?
Ten years of updates
I first built this course in 2016 and I have been updating it ever since.. it has just been rebuilt for 2026. That history is the point. JavaScript is backwards compatible, so it never throws anything away, it only adds layers. Learn it in those layers and the strange behaviour stops being trivia and starts being obvious: a class stops being magic the moment you have seen the prototypes underneath it.