Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

JavaScript Crash Course

JavaScript is the most ubiquitous programming language in the world. It runs in every browser, powers servers with Node.js, builds mobile apps, and even trains machine learning models. If you learn one language, make it JavaScript.
This crash course is designed to take you from “Hello World” to understanding closures, prototypes, async/await, and modern ES6+ features.

Why JavaScript?

JavaScript has evolved from a simple scripting language to a versatile powerhouse.

Runs Everywhere

From browsers to servers (Node.js) to mobile (React Native) to desktop (Electron) — JavaScript is truly universal.

Massive Ecosystem

npm is the world’s largest package registry with over 2 million packages. There’s a library for everything.

Async by Nature

Built-in event loop and async primitives make handling I/O, network calls, and user interactions seamless.

Rapid Evolution

With yearly ECMAScript updates, JavaScript keeps getting better: async/await, modules, optional chaining, and more.

Course Roadmap

We will peel back the layers of abstraction to understand how JavaScript really works.
1

Fundamentals

Understand variables, types, operators, and control flow. Start Learning
2

Functions & Scope

First-class functions, closures, and the execution context. Explore Functions
3

Objects & Prototypes

Objects, prototypal inheritance, and the this keyword. Master Objects
4

Async JavaScript

Callbacks, Promises, async/await, and the Event Loop. Go Async
5

Modern JavaScript (ES6+)

Destructuring, modules, classes, and the latest features. Go Modern
6

DOM & Browser APIs

Manipulating the DOM, handling events, and browser APIs. Build for the Web

Quick Reference — JavaScript’s Most Common Gotchas

If you read nothing else before starting, know these. Each is covered in depth in the relevant chapter.
GotchaWhat happensFix
typeof nullReturns 'object' (25-year-old bug)Use value === null to check for null
0.1 + 0.20.30000000000000004 (IEEE 754 floats)Use integer math for money (cents)
'5' + 3'53' (string concatenation, not addition)Use Number('5') + 3 or +'5' + 3
[] == falsetrue (coercion through "" to 0)Always use === (strict equality)
var in loopsShared across iterations (closure bug)Use let for block-scoped loop variable
this in callbacksLoses object contextUse arrow functions or .bind()
.sort() no comparatorSorts as strings: [10, 9, 80] becomes [10, 80, 9]Always pass (a, b) => a - b for numbers
fetch on 404/500Does not throw (only network errors throw)Check response.ok before reading body
{ ...obj } with nestingShallow copy (nested objects shared)Use structuredClone() for deep copy
async function returnAlways returns a Promise, never a raw valueUse await to unwrap

Prerequisites

  • Basic programming knowledge (any language).
  • A modern browser (Chrome, Firefox, Edge).
  • Node.js installed for running JS outside the browser (node -v).
  • A code editor (VS Code is highly recommended).

The JavaScript Philosophy

“Any application that can be written in JavaScript, will eventually be written in JavaScript.” — Atwood’s Law
JavaScript is a multi-paradigm language. You can write object-oriented, functional, or procedural code. It’s dynamically typed (flexibility at the cost of runtime errors), and single-threaded with an event-driven architecture. Think of JavaScript like a Swiss Army knife: it is not the best dedicated tool for any single job, but it is the only tool that works in every environment you will encounter on the web. A backend engineer might reach for Go or Rust for raw performance. A data scientist might prefer Python. But JavaScript is the one language that runs natively in every browser on the planet, on every server via Node.js, and increasingly on edge runtimes and IoT devices. That universality is its superpower — and the reason it is worth learning deeply, quirks and all.
Heads up before you dive in. JavaScript has a few infamous quirks that trip up every newcomer: automatic type coercion ('5' + 3 gives you '53', not 8), the this keyword changing meaning depending on how you call a function, and variable hoisting with var. This course tackles each of these head-on. Do not let them scare you — once you understand why they exist, they become predictable.
// A taste of JavaScript -- functional-style data processing in three lines
const greet = (name) => `Hello, ${name}!`;

const names = ['Alice', 'Bob', 'Charlie'];

names
  .filter(name => name.startsWith('A'))  // Keep only names starting with 'A'
  .map(greet)                            // Transform each name into a greeting
  .forEach(console.log);                 // Print each greeting

// Output: "Hello, Alice!"

Interview Deep-Dive

Strong Answer:
  • The key insight is that single-threaded does not mean single-tasked. JavaScript uses a single call stack for executing code, but it offloads I/O operations (file reads, network requests, database queries) to the operating system’s kernel or a thread pool managed by libuv (in Node.js) or the browser’s Web APIs.
  • When an async operation like fs.readFile is called, Node hands it off to the OS or a worker thread. The main thread is immediately free to process the next event. When the I/O completes, a callback is placed on the event loop’s task queue. The event loop picks it up only when the call stack is empty.
  • This is why Node.js excels at I/O-bound workloads (API servers, real-time apps) but struggles with CPU-bound tasks (image processing, heavy computation). A CPU-bound task blocks the single thread and starves every other request. The mitigation is worker_threads in Node.js or Web Workers in the browser, which spin up actual OS threads for computation.
  • In production, a Node.js server handling 10,000 concurrent WebSocket connections is not running 10,000 threads. It is running one thread that rapidly cycles through event callbacks. The bottleneck is never the concurrency model — it is what you do on the main thread between events.
Follow-up: If a single CPU-bound task can block everything, how would you architect a Node.js service that needs to resize uploaded images while also serving API requests?You would never do image resizing on the main event loop thread. The standard approaches are:
  • Use worker_threads to offload the resize to a separate thread. The main thread posts the image buffer to the worker and gets a message back when it is done. This keeps the event loop free.
  • Use a separate microservice or job queue (like Bull/BullMQ backed by Redis) where the API server enqueues a resize job and a dedicated worker process picks it up. This is the production-standard pattern at scale because it lets you scale API servers and image workers independently.
  • Use a native addon like sharp (which calls into libvips via C++ bindings and releases the GIL equivalent automatically), so the heavy lifting happens off the main thread even without explicit worker usage.
  • The anti-pattern is calling a synchronous image processing library directly in an Express route handler. I have seen this take down a production API serving 500 requests per second because a single 5MB image resize blocked the event loop for 800ms, causing every other request to queue up behind it.
Strong Answer:
  • JavaScript’s == operator triggers the Abstract Equality Comparison algorithm (defined in the ECMAScript spec, section 7.2.14), which performs type coercion before comparison. The === operator skips coercion entirely and compares type + value.
  • For [] == ![]: first, ![] is evaluated. Since [] is truthy (all objects are truthy), ![] becomes false. Now we have [] == false. The spec says: if one side is boolean, convert it to a number. false becomes 0. Now we have [] == 0. The spec says: if one side is an object and the other is a number, call ToPrimitive on the object. [].valueOf() returns the array itself (not a primitive), so it falls through to [].toString(), which returns "". Now we have "" == 0. The spec says: if one side is a string and the other is a number, convert the string to a number. Number("") is 0. Now we have 0 == 0, which is true.
  • The practical lesson is not to memorize every coercion path, but to internalize one rule: always use === unless you have a specific, documented reason to use ==. The one defensible exception is value == null, which catches both null and undefined in a single comparison.
  • In production, linting rules like ESLint’s eqeqeq enforce this automatically. At a previous team I worked on, we had a bug where a form field value of "0" was being treated as falsy in an if (value) check, causing a valid zero input to be silently discarded. The fix was switching to if (value !== null && value !== undefined) — or using the nullish coalescing operator.
Follow-up: What is the difference between Object.is() and ===, and when does it matter?Object.is() differs from === in exactly two cases: Object.is(NaN, NaN) returns true (whereas NaN === NaN is false), and Object.is(-0, 0) returns false (whereas -0 === 0 is true). This matters in specific scenarios: if you are implementing a memoization cache and a function is called with NaN as an argument, === would fail to detect a cache hit. React uses Object.is internally for its dependency comparison in useEffect and useMemo hooks — this is why passing NaN as a dependency does not cause infinite re-renders.
Strong Answer:
  • Multi-paradigm means JavaScript supports object-oriented programming (prototypal inheritance, classes), functional programming (first-class functions, closures, immutability patterns), and procedural/imperative programming (sequential statements, loops). You are not locked into one style the way you are with, say, Haskell (functional) or Java pre-8 (OOP).
  • Concretely, this affects architecture because different parts of a codebase may benefit from different paradigms. Data transformation pipelines (parsing API responses, transforming state) are often cleanest as functional chains: data.filter(...).map(...).reduce(...). State management and encapsulation (a database connection pool, a cache with TTL) often benefit from OOP with classes. Glue code and scripts are often simplest as imperative procedures.
  • The risk is inconsistency. On a team of 8 engineers, if half write functional-style code and half write class-based OOP, the codebase becomes incoherent. The practical solution is to establish conventions: “We use functional style for data transforms, classes for services, and we never mix paradigms within a single module.” Linting rules and code review enforce this.
  • A real-world example: in a React + Node.js codebase, the frontend is typically functional (function components, hooks, pure render functions) while the backend services might be class-based (NestJS controllers, injectable services). Trying to force one paradigm everywhere leads to awkward code — a React component written as a class in 2026 feels archaic, and a complex stateful service written as nested closures feels unreadable.
Follow-up: When would you explicitly choose a functional approach over OOP in JavaScript, and what are the trade-offs?I would choose functional when the domain is about transformations: mapping API responses, building UI from state, data validation pipelines. The benefits are testability (pure functions with no side effects are trivial to unit test), composability (pipe/compose small functions into larger ones), and predictability (no hidden state mutations). The trade-offs are: deeply nested closures can be harder to debug (stack traces are less readable), performance can suffer if you create many intermediate arrays in hot paths, and some domains (stateful connections, resource lifecycle management) are genuinely more natural to express as objects with methods. The pragmatic answer is: use both, but within clear boundaries.