Codesmith
Course Description
Go deep into the foundational mechanics of JavaScript! From higher-order functions and closure to asynchronous code execution and object-oriented programming, build a comprehensive mental model around concepts that even the most experienced developers often lack. Grab a “backpack” as you follow the thread of execution through the call stack, callback queue, and microtask queue. Leverage prototypical inheritance and modern OOP features like classes and private/static fields. Understand the difference between stack and heap memory, and explore metaprogramming and type coercion with symbols.
This course and others like it are available as part of our Frontend Masters video subscription.
Preview
Course Details
Published: January 29, 2026
Learn Straight from the Experts Who Shape the Modern Web
Your Path to Senior Developer and Beyond
- 250+ In-depth courses
- 24 Learning Paths
- Industry Leading Experts
- Live Interactive Workshops
Table of Contents
Introduction
Section Duration: 8 minutes
Will Sentance begins the course by explaining the importance of building a deep foundation of the core principles of JavaScript. This course covers five critical areas: higher-order functions, closure, async programming, OOP, and type coercion—along with recent JavaScript features like immutable array methods and promise aborts.
Principles of JavaScript
Section Duration: 20 minutes
Will demonstrates the thread of execution. The JavaScript interpreter executes code line-by-line. Variables or "data" like strings and arrays are saved in memory. Functions can also be saved and executed at a later time.
Will explains that functions are code that's defined and saved. They are called or executed later by using the function's name followed by parentheses. Calling a function creates a new execution context containing its own memory and thread of execution. The mechanism JavaScript uses to manage the order in which functions should be run is the call stack.
Callbacks & Higher Order functions
Section Duration: 1 hour, 17 minutes
Will believes callbacks and higher-order functions are two of the most misunderstood concepts in JavaScript. They are critical components in functional programming, enabling more complex function composition and useful function utilities, such as map and reduce.
Will walks through the execution of the copyArrayAndMultiplyBy2 function. An array is passed as an argument to the function. An output array is declared inside the function and filled with the mutated values from the array parameter. The output array is returned once the function has finished executing.
Will refactors the function to accept a second parameter representing a function that should be called with each value in the array. This allows the copyArrayAndManipulate function to be generalized and mutate the array in different ways.
Will highlights the advantages of higher-order functions. They create more declarative and readable code. They are also a tool for keeping code DRY (Don't Repeat Yourself) because functions can be generalized or reused.
Will introduces arrow functions, a feature added in ES6. Arrow functions use a slightly different syntax when defining a function and bind the "this" keyword to the scope of the function rather than the window object.
Will explains the dangers with mutating arrays or other variables passed by reference to a function. In recent updates to the JavaScript specification, new non-mutating array methods have been added, including toReversed, toSpliced, and toSorted. These new methods return a copy of the original array rather than mutating it.
Will introduces the pair programming exercises that accompany this course. The first exercises on callbacks are linked below. Additional exercises on closure, asynchronous JavaScript, and object-oriented programming are also referenced later in the course.
Closure
Section Duration: 1 hour, 19 minutes
Will introduces closure, one of the most complex features of JavaScript. When a function is executed, it saves a link to any context, state, or memory available from the execution context where it was created. Will refers to this data as a "backpack" that accompanies the function when it's executed.
Will walks through a thread of execution example for a function that returns another function. The returned function is saved in global memory and can be called when parentheses are added to the label given to the returned function.
Will shares a code example where a function is called from within another function. This demonstrates the inner function is able to reference any memory from within the outer function, including a counter variable.
Will introduces the "backpack" or closure that accompanies the inner function returned from the outer function. This "backpack" contains references to any data or memory from the execution context when the outer function was called. Later, when the inner function is executed, it can look inside the closure for any references.
Will answers questions related to function scope and what other data can be reference by the inner function through the "backpack".
Will explains the origins of the term "closure" and reviews the behavior of the execution context and closure reference.
Will walks through instances where multiple closures are created. JavaScript functions create local execution contexts that reset each time they run, but when an inner function is returned, it carries a persistent private memory ("backpack") with it. Each call to the outer function creates a new execution context with a fresh local variable, but the returned inner functions retain their own copies of that variable, independent of the outer function's subsequent calls.
Will spends a few minutes answering questions about closure and how the "backpack" is persisted in memory during subsequent function calls.
Will explains several use cases for closures in JavaScript, highlighting its role in maintaining persistent memory within functions. These use cases include helper functions, memoization, iterators, generators, the module pattern, and asynchronous programming.
Type Coercion & Metaprogramming
Section Duration: 1 hour, 50 minutes
Will introduces JavaScript's type coercion, a design feature that automatically converts data types to make the language more flexible, especially when interacting with web browsers. Operators in JavaScript behave like functions but operate on values adjacent to them, and type coercion helps handle data from user input as strings, converting them to the appropriate types for operations. Understanding this helps write more bug-resistant code and effectively handle JavaScript's quirks.
Will explores type coercion with math operators, showing how strings are automatically converted to numbers for multiplication and related operators. A side effect is that addition with a string operand triggers string concatenation instead of numeric addition. To avoid unpredictable results, manual type coercion using the unary plus operator or the Number() function is recommended to ensure consistent numeric operations.
Will looks at toBoolean() coercion for values like zero and empty strings. He also highlights the difference between loose equality (==), which coerces values before comparison, and strict equality (===), which compares without coercion, allowing a distinction between zero and empty string inputs. This is important for handling user input validation, such as preventing submission of empty or zero quantities while allowing zero donations.
Will summarizes three main type coercion flows in programming: coercion to number, string, and boolean, each triggered by different operators. These coercions are common across many languages and are based on conventions, with some rules being predictable and others more variable.
Will introduces stack and heap memory. JavaScript stores primitive values directly in memory (stack) but stores objects by reference in a flexible memory area called the heap. When comparing objects, JavaScript compares their references (memory addresses), not their contents, which means two objects with identical properties are not equal unless they reference the same memory location.
Will demonstrates how the built-in Date function returns a date value, representing milliseconds since January 1, 1970. Although these date objects cannot be directly subtracted or compared, the hidden millisecond values can be accessed indirectly to measure time differences. He also demonstrates that these date objects are regular objects, allowing properties to be added using square bracket notation, which is important for metaprogramming and dynamic property assignment.
Will explains how Date objects can be automatically coerced into primitive values, such as numbers, through a hidden method called @@toPrimitive. This enables operations like subtracting two Date objects to get the difference in milliseconds. The coercion happens because the language calls the @@toPrimitive method, which returns a numeric value representing the date, allowing math to be performed on these objects.
Will introduces a special hidden property called Symbol.toPrimitive. This property contains a function that defines the coercion behavior, such as returning a specific number. You cannot add this property directly with dot notation, but you can assign it using the Symbol.toPrimitive key with bracket notation. This allows objects to be coerced to numbers or strings as needed, overriding the default behavior that usually results in NaN for objects.
Will summarizes Symbols and metaprogramming, features introduced in ES6. allowing developers to override default language behaviors safely and compatibly with legacy code. Understanding this coercion mechanism is key for writing predictable JavaScript and handling implicit conversions effectively.
Asynchronous JavaScript & the event loop
Section Duration: 2 hours, 8 minutes
Will reviews that JavaScript runs code line by line on a single thread of execution, meaning it can only do one thing at a time. JavaScript creates execution contexts and uses a call stack to keep track of which function is currently running. When a function is called, a new execution context is created and added to the call stack; once the function finishes, its context is removed
Will explains the single-threaded nature of JavaScript would cause delays when waiting for tasks like fetching data from a server. To address this, JavaScript relies on external features provided by the web browser or Node environment, such as timers, network requests, and the DOM, which run outside the main JavaScript thread. These external features allow asynchronous operations without blocking the single JavaScript thread, making the language powerful despite its single-threaded nature.
Will discusses how JavaScript interacts with the web browser's features, called APIs, which run outside the JavaScript engine. He uses the example of setTimeout, showing that when called in JavaScript, it sets a timer in the browser's API, allowing JavaScript to continue running without blocking. After the timer completes, the callback function is added to the JavaScript call stack and executed.
Will highlights that even a zero-millisecond timer does not immediately execute the callback function; instead, it is placed in a queue and waits until all global code has finished running. This queue is called the Callback Queue.
Will introduces the Event Loop, which continuously checks if the call stack is empty and if all global code has finished running before moving tasks from the callback queue to the call stack for execution. This ensures predictability by queuing background work until the main thread is free to handle it, rather than running it immediately.
Will shares some background on promises and asynchronous code. Before ES6, JavaScript used callback functions for background tasks like network requests, which could lead to confusing "callback hell" because the data was only available inside the callback function. With ES6, promises were introduced as a better approach, acting as two-pronged facade functions that start background work and immediately return a promise object to track that work within JavaScript. This makes asynchronous code more readable and manageable, though under the hood, it still follows a similar pattern to callbacks.
Will diagrams the behavior of the fetch function. When fetch is called, it immediately returns a promise object that acts as a placeholder for the future data from the internet. This promise has a hidden result property that will eventually hold the data once the network request completes. When the data arrives, the attached function runs automatically with the data as its input.
Will dives deeper into promises and also discusses how they persist in memory due to references maintained by the browser, allowing asynchronous data handling to integrate with JavaScript's synchronous environment.
Will walks through a code example that includes setTimeout and fetch. When fetch is called, it triggers a network request and creates a promise object that holds the future result. The promise stores functions to run once the data arrives, using the .then method to add these functions to a queue. This setup allows JavaScript to continue running other code while waiting for the network response, then automatically execute the stored functions with the retrieved data when ready.
Will introduces the microtask queue and demonstrates that while synchronous code blocks the call stack, asynchronous callbacks from promises are placed into the microtask queue, which has priority over the callback queue. This causes promise-based functions to run before other callbacks, even if those callbacks were queued earlier.
Will answer questions related to the microtask queue and introduces the ability to abort fetch requests using an abort signal with a timeout, which cancels the request if it takes too long, triggering error handling instead of success. This control over asynchronous tasks enables building fast, non-blocking web applications with a single JavaScript thread managing background work efficiently.
Classes & Prototypes (OOP)
Section Duration: 2 hours, 34 minutes
Will introduces object-oriented JavaScript and explains the core operations of software development: saving data and running code. The goal is to write code that's easy to reason about, add features to, and be efficient and performant. The object-oriented paradigm aims to achieve these goals.
Will explains the concept of encapsulation by bundling data and functions together in objects, allowing methods (functions on objects) to be called directly on the data. He demonstrates creating objects with properties using different techniques: object literals, dot notation, and Object.create.
Will explains how to create user objects using a function to avoid repetitive code, following the "Don't Repeat Yourself" (DRY) principle. This approach allows generating multiple user objects efficiently without manually writing each one. He also highlights how the increment method works as a method on each user object and how the function execution context handles parameters and object creation.
Will introduces the prototype chain, where functions like increment are stored once in a separate object, and user objects "link" to this function store. When a function is called, JavaScript looks up the prototype chain to find and run the shared function, improving memory use and maintainability.
Will explains how to create objects using the Object.create() method to link new objects to a shared function store via the prototype chain. This approach allows multiple user objects to share one copy of functions like increment without duplicating them on each object.
Will dives deeper into the prototype chain and covers built-in methods like Object.hasOwnProperty, which can identify if a property is on the Object or up the prototype chain.
Will explains how the "this" keyword behaves differently in regular functions versus arrow functions inside object methods. He shows that a regular nested function's "this" defaults to the global object, which is usually not desired. Using arrow functions for nested functions keeps "this" lexically bound to the outer method's context, meaning it correctly refers to the object the method was called on.
Will introduces the "new" keyword, which automates object creation and prototype linking. The this keyword will be required to reference the newly created object from within Object methods. Will also demonstrates how functions are objects and can be treated as either type.
Will refactors the userCreator function to use the new keyword instead of the Object.create() method. With the new keyword, JavaScript automatically creates a new empty object, assigns it to this, sets its hidden prototype link to the function’s prototype object, and returns it. This setup allows instances created with new to access shared methods on the prototype, reducing code duplication and simplifying object creation.
Will reviews how the "new" keyword automates object creation and prototype linking. The new keyword also enables features like ES6 classes, which serve as syntactic sugar, making code easier to read and write without changing the underlying behavior. Classes wrap constructor functions and prototype methods into a single, clearer structure while maintaining the same functionality.
Will highlights recent additions, such as public instance fields, that extend class functionality beyond the original function-object pattern. Public static fields in JavaScript classes are properties added directly to the class (function object) itself, not to instances or the prototype. They behave like static properties in other languages, allowing you to store values or functions related to the class as a whole rather than individual objects.
Will explains how the class constructor works with the new keyword to create objects. The new keyword creates an empty object, sets its prototype to the class prototype, assigns default properties from a hidden fields property, and then runs the constructor code to add custom properties. Finally, the new object is returned and can access methods via the prototype chain. This process is explicit in JavaScript and important for understanding object creation and inheritance.
Will mentions that JavaScript now supports private instance fields using a hashtag (#) prefix, which allows properties like "score" to be hidden and only accessible within the object's own methods. This prevents external code from directly modifying these private fields, enforcing controlled access through defined functions like "increment."
Will finishes the OOP discussion by demonstrating how private static fields can be used to store shared data accessible only inside the class constructor, preventing external modification. He emphasizes the importance of understanding these differences for writing resilient, well-structured code and for technical interviews.
Wrapping Up
Section Duration: 4 minutes
Will wraps up the course by summarizing higher-order functions, closure, type coercion, asynchronous code execution, and OOP. He encourages understanding these fundamentals to become autonomous engineers and better use frameworks and other languages.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops