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 Fundamentals
JavaScript is a dynamically typed, interpreted language. Understanding its type system and how values behave is critical to writing bug-free code. Think of JavaScript’s type system like a helpful but overeager assistant: it will try to make things work even when you hand it mismatched types, silently converting a string to a number or a number to a boolean behind your back. Sometimes that is convenient. Sometimes it produces bugs that are maddening to track down. This chapter gives you the foundation to always know what JavaScript is doing with your values and why.1. How JavaScript Works
Unlike compiled languages like Java or C++, JavaScript is interpreted at runtime. Modern engines like V8 (Chrome, Node.js) use Just-In-Time (JIT) compilation for performance.The Process
- Parsing: Your code is parsed into an Abstract Syntax Tree (AST).
- Interpreter: The AST is converted to bytecode and executed immediately.
- JIT Compiler: Hot code paths are compiled to optimized machine code.
2. Variables & Declarations
JavaScript has three ways to declare variables. Useconst by default, let when you need to reassign, and avoid var.
const (Block-scoped, No Reassignment)
let (Block-scoped, Reassignable)
var (Function-scoped, Hoisted) — Avoid!
var vs let vs const — Complete Comparison
| Feature | var | let | const |
|---|---|---|---|
| Scope | Function | Block | Block |
| Hoisting | Hoisted (initialized as undefined) | Hoisted (but in Temporal Dead Zone) | Hoisted (but in Temporal Dead Zone) |
| Reassignment | Yes | Yes | No |
| Redeclaration in same scope | Yes (silently overwrites) | No (SyntaxError) | No (SyntaxError) |
| Mutable value | Yes | Yes | Object/Array contents: yes. Primitive: no |
Use in for loop | Shared across iterations | New binding per iteration | Not reassignable (use in for...of) |
- Start with
constfor every variable. This is your default. It signals intent: “this binding will not change.” - Switch to
letonly when you need to reassign: loop counters, accumulators, values that change over time. - Never use
varin new code. If you see it in a codebase, it is legacy or a bug. The only exception is if you are targeting an environment that does not support ES6 (extremely rare today).
3. Data Types
JavaScript has 8 data types: 7 primitives and 1 object type.Primitive Types
| Type | Example | Notes |
|---|---|---|
number | 42, 3.14, Infinity, NaN | All numbers are 64-bit floats |
bigint | 9007199254740993n | For integers beyond safe range |
string | 'hello', "world", `template` | Immutable sequence of characters |
boolean | true, false | Logical values |
undefined | undefined | Variable declared but not assigned |
null | null | Intentional absence of value |
symbol | Symbol('id') | Unique identifiers (ES6) |
Reference Type: Object
Everything that’s not a primitive is an Object. This includes arrays, functions, dates, and regular objects.typeof — Results and Gotchas
Thetypeof operator returns a string indicating the type. It has a few famous surprises.
| Expression | typeof result | Surprise? |
|---|---|---|
typeof 42 | 'number' | |
typeof 'hello' | 'string' | |
typeof true | 'boolean' | |
typeof undefined | 'undefined' | |
typeof Symbol() | 'symbol' | |
typeof 42n | 'bigint' | |
typeof null | 'object' | Yes — this is a 25-year-old bug that can never be fixed |
typeof [] | 'object' | Yes — arrays are objects. Use Array.isArray() instead |
typeof {} | 'object' | |
typeof function(){} | 'function' | Functions get their own type, but arrays do not |
typeof NaN | 'number' | Yes — “Not a Number” is a number |
typeof undeclaredVar | 'undefined' | Does not throw, unlike accessing undeclaredVar directly |
4. Type Coercion
JavaScript tries to be helpful by automatically converting types. This can lead to unexpected results. Think of it like an overly accommodating waiter: you order “five plus three” and instead of asking whether you meant the number five or the string “5”, the waiter just guesses. Sometimes the guess is right. Sometimes you get a concatenated string when you wanted arithmetic. The key rule to remember: the+ operator prefers strings (if either side is a string, it concatenates), while -, *, and / always convert to numbers.
Number Edge Cases — Floating Point and Safe Integers
Implicit Coercion (Automatic)
Explicit Coercion (Intentional)
Truthy and Falsy Values
In boolean context, values are coerced totrue or false. Memorize the falsy list — everything else is truthy.
Falsy values (evaluate to false):
false,0,-0,0n,'',null,undefined,NaN
[], {}, '0', new Boolean(false)).
5. Operators
Comparison: == vs ===
Always use === (strict equality). It checks value AND type.
| Expression | == (loose) | === (strict) | Why |
|---|---|---|---|
5 == '5' | true | false | Loose coerces string to number |
0 == '' | true | false | Both coerce to 0 |
0 == false | true | false | false coerces to 0 |
'' == false | true | false | Both coerce to 0 |
null == undefined | true | false | Special case: == treats these as equal |
null == 0 | false | false | null only == equals undefined and itself |
NaN == NaN | false | false | NaN is not equal to anything, including itself |
[] == false | true | false | [] coerces to '', then to 0, which equals false |
[] == ![] | true | false | The most infamous JS quirk: ![] is false, [] coerces to 0, false coerces to 0 |
==: Almost never. The only defensible use case is value == null, which checks for both null and undefined in a single comparison. Some style guides (including jQuery’s internal guide) allow this shorthand. Everything else should use ===.
Edge Cases That Bite: NaN and -0
Logical Operators
Optional Chaining (ES2020)
Safely access nested properties without checking each level.6. Control Flow
Conditionals
Switch
Loops
Loop Comparison — When to Use Which
| Loop | Iterates Over | Works On | Use When |
|---|---|---|---|
for (let i = 0; ...) | Index (you control it) | Anything with length | You need the index, or need to skip/reverse/step by 2 |
for...of | Values | Arrays, strings, Maps, Sets, iterables | Default choice for arrays and iterables |
for...in | Enumerable property keys (strings) | Objects | Iterating object properties (never use on arrays) |
.forEach() | Values (with index) | Arrays | Simple iteration, but cannot break or return early |
.map() | Values (transforms) | Arrays | Transforming every element into a new array |
while | Condition-based | Any condition | Unknown number of iterations, polling, game loops |
|| vs ?? vs &&= vs ??= — Default Value Operators
| Operator | Falls through on | Use case | Example |
|---|---|---|---|
|| | Any falsy value (0, '', false, null, undefined, NaN) | Broad default: “give me something truthy” | name || 'Anonymous' |
?? | Only null or undefined | Precise default: “give me a real value if one was set” | port ?? 3000 (keeps 0) |
||= | Any falsy value | Assign default if current value is falsy | config.debug ||= false |
??= | Only null or undefined | Assign default if missing | config.timeout ??= 5000 |
Summary
- Variables: Use
constby default,letwhen needed. Avoidvar. - Types: 7 primitives (
number,string,boolean,null,undefined,symbol,bigint) + Object. - Coercion: JavaScript auto-converts types. Use
===to avoid surprises. - Operators: Use
??for null/undefined,?.for safe property access. - typeof: Watch out for
typeof null === 'object'andtypeof [] === 'object'. - Numbers:
0.1 + 0.2 !== 0.3— use integer arithmetic for money.
Interview Deep-Dive
Walk me through what happens when V8 executes a JavaScript file. What is JIT compilation and why does it matter?
Walk me through what happens when V8 executes a JavaScript file. What is JIT compilation and why does it matter?
Strong Answer:
- V8 (Chrome, Node.js) does not simply interpret JavaScript line by line. The process has multiple stages. First, the source code is parsed into an Abstract Syntax Tree (AST). The AST is then fed to Ignition, V8’s interpreter, which generates bytecode and begins executing it immediately. This gives fast startup — you do not wait for full compilation before seeing output.
- As the code runs, V8’s profiler (the “feedback vector”) monitors which functions are called frequently (“hot” code paths) and what types of arguments they receive. When a function becomes hot enough, TurboFan (V8’s optimizing compiler) kicks in and compiles that specific function to highly optimized machine code, using the type information it has observed.
- The critical gotcha is “deoptimization.” If TurboFan compiled a function assuming the first argument is always a number, and then you call it with a string, V8 must discard the optimized code and fall back to the interpreter. This is called a “bailout” or “deopt.” In a high-throughput service processing millions of events, deoptimizations can cause visible latency spikes. This is why writing “monomorphic” code (where functions always receive the same types) matters for performance-critical paths.
- In practice, you rarely need to think about this directly. But when profiling a Node.js service and seeing unexplained latency, V8’s
--trace-deoptflag can reveal deoptimizations. I have seen a production case where a utility function was being deoptimized on every call because it received bothnullandundefinedas inputs — the types were polymorphic, preventing TurboFan from optimizing.
let/const variable being hoisted (the engine knows it exists) and the line where it is actually initialized. Accessing it in that window throws a ReferenceError. It exists because var’s behavior of silently returning undefined before initialization was a prolific bug source. The TDZ makes these bugs loud and immediate. From a specification standpoint, let and const bindings are created when the enclosing lexical environment is instantiated (hoisted), but they are not initialized until the declaration is evaluated. This is a deliberate design choice to catch “use before declaration” errors that var would silently swallow.Explain every falsy value in JavaScript. Why is an empty array truthy but an empty string falsy?
Explain every falsy value in JavaScript. Why is an empty array truthy but an empty string falsy?
Strong Answer:
- The complete falsy list in JavaScript is:
false,0,-0,0n(BigInt zero),""(empty string),null,undefined,NaN, and the historical odditydocument.all. Everything else is truthy, including empty arrays[], empty objects{}, the string"0", the string"false", andnew Boolean(false). - An empty array is truthy because truthiness in JavaScript is about the nature of the value, not its “emptiness.” Arrays and objects are reference types — they point to a location in memory. That reference exists and is not null, so it is truthy. An empty string, by contrast, is a primitive with no characters — it is conceptually “nothing,” like zero.
- This distinction creates one of the most common bugs in JavaScript:
if (myArray)is always true, even when the array is empty. You must checkif (myArray.length)orif (myArray.length > 0). Similarly,if (myObject)does not tell you if the object has properties — you needif (Object.keys(myObject).length > 0). - In production, this bites people most often with API responses. An endpoint returns
{ items: [] }and the code checksif (response.items)— always true, so the UI renders an empty container instead of a “no results” message. The fix is always checking the actual content, not the container.
== to compare null and 0? Walk me through the coercion steps.null == 0 evaluates to false. This surprises people because null == undefined is true and null in numeric context is 0. But the == algorithm has a special rule: null is only loosely equal to undefined and to itself. It does not trigger numeric coercion when compared to numbers, strings, or booleans. The spec explicitly defines null == 0 as false without going through the ToNumber path. This is one of the reasons == is treacherous — the rules are not consistently “convert to a common type and compare.” There are special cases baked into the algorithm.When would you use the nullish coalescing operator (??) instead of logical OR (||), and what real bug does it prevent?
When would you use the nullish coalescing operator (??) instead of logical OR (||), and what real bug does it prevent?
Strong Answer:
- The
||operator returns the first truthy value. The??operator returns the first value that is notnullorundefined. The difference matters when0,"", orfalseare valid, intentional values. - The classic bug:
const port = config.port || 3000. Ifconfig.portis0(a valid port, though unusual),||treats0as falsy and returns3000. The user explicitly set port to0and the code silently overwrites it. Withconst port = config.port ?? 3000, the value0is preserved because it is notnullorundefined. - I have seen this in production with a feature flag system. A flag called
maxRetrieswas set to0for a specific client to disable retries entirely. The code usedconst retries = flagValue || 3, which silently replaced0with3, causing that client’s failed requests to retry three times and hammer a downstream service. The fix was a one-character change:flagValue ?? 3. - The mental model: use
||when you want to fall back on any “empty-ish” value (falsy). Use??when you only want to fall back when the value genuinely was not provided (null/undefined). In modern codebases,??is almost always what you actually want for default values.
const city = user?.address?.city ?? "Unknown". The evaluation: user?.address — if user is null/undefined, short-circuit to undefined. If not, access .address. Then ?.city — if address is null/undefined, short-circuit to undefined. If not, access .city. Now ?? kicks in: if the result is null or undefined, use "Unknown". Otherwise, keep the value. The key detail: optional chaining produces undefined on short-circuit (never null), and ?? catches both. This is the modern replacement for the verbose const city = user && user.address && user.address.city ? user.address.city : "Unknown" pattern.Explain NaN. Why is NaN !== NaN, and how do you correctly check for it?
Explain NaN. Why is NaN !== NaN, and how do you correctly check for it?
Strong Answer:
NaNstands for “Not a Number” but its type isnumber(typeof NaN === 'number'). It represents the result of a nonsensical numeric operation:0/0,parseInt("hello"),Math.sqrt(-1),undefined + 1.NaN !== NaNis true because the IEEE 754 floating-point specification (which JavaScript follows) defines NaN as not equal to anything, including itself. The rationale: NaN represents an indeterminate value.0/0andparseInt("hello")both produce NaN, but they are not “the same value” in any meaningful sense. Making NaN unequal to itself prevents false equivalences between fundamentally different failed computations.- To check for NaN: use
Number.isNaN(value). Do NOT use the globalisNaN()function, which coerces its argument to a number first.isNaN("hello")returnstruebecauseNumber("hello")is NaN.Number.isNaN("hello")returnsfalsebecause"hello"is not NaN — it is a string.Number.isNaNonly returnstruefor the actual NaN value. - A lesser-known check:
value !== valueistrueonly forNaN(since NaN is the only value not equal to itself). This was the idiomatic check beforeNumber.isNaNexisted, and you still see it in older codebases and polyfills. - Production gotcha: NaN propagates through calculations silently.
NaN + 5isNaN.NaN * 100isNaN. If an early step in a financial calculation produces NaN (say, parsing a user input that is not a number), the final result is NaN, displayed as “NaN” in the UI or stored asnullin the database. Always validate inputs at the boundary.
+0 and -0. They are === equal (-0 === 0 is true), but Object.is(-0, 0) returns false. -0 appears from operations like Math.round(-0.1), -1 * 0, or parseFloat("-0"). In practice, -0 matters in mathematical contexts where the sign carries directional information (e.g., a velocity of -0 means “stopped but was moving left”). The sneaky bug: JSON.stringify(-0) produces "0", losing the sign. If you round-trip through JSON, the sign is lost. String(-0) also returns "0". The only reliable ways to detect -0 are Object.is(value, -0) or checking 1/value === -Infinity.