Sitemap
JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.

You Know JavaScript. Your Brain Just Forgets. (Part 1: How JavaScript Thinks)

11 min readSep 30, 2026

--

Press enter or click to view image in full size

Before We Begin

You’ve shipped features. Fixed bugs at 2 a.m. Reviewed a mountain of pull requests.

Then the interviewer smiles and asks, “So, what’s the temporal dead zone?”

And your brain returns… undefined.

Or it’s closures. Or Promise.race. Or starvation. Or inversion of control.

Quick check: if you just answered all of those in your head, congratulations. Close this tab and go get a coffee, you’ve earned it. Still here? Welcome. You’re in very good company.

This isn’t a knowledge problem. It’s a recall problem. We use these things every day. We just stop calling them by their names.

So here’s your cheat sheet, in two parts. Part 1 is how JavaScript thinks: 11 fundamentals, each with a one-line answer, a tiny example and a picture.

Five minutes before the interview? Just read the one-liners at the top of each section and the Remember it lines.

Let’s go.

1. JavaScript and the Execution Context: Where It All Begins

In one line: JavaScript is a synchronous, single-threaded language, and all code runs inside an execution context.

Picture JavaScript as a chef with one pair of hands. One dish at a time, in order. No multitasking, no skipping ahead.

The kitchen: execution context

Every program gets one big kitchen, the Global Execution Context. It’s set up in two phases:

  1. Memory phase (gather the ingredients): variables and functions get memory as key-value pairs. Variables start as undefined.
  2. Code phase (start cooking): code runs one line at a time.

Call a function, and a new mini kitchen opens inside the big one. When the function returns, it closes.

var n = 10;
function square(num) {
var ans = num * num;
return ans;
}
square(n);
Press enter or click to view image in full size

Memory side = Variable Environment. Code side = Thread of Execution. (The diagram shows n: 10, which is memory after the code has run.)

Remember it: Two phases, memory then code. Every function call gets its own execution context, which disappears when it returns.

2. Hoisting: Using Things Before You Write Them

In one line: variables and functions get memory before the code runs, so you can use them before they’re declared.

Think of a restaurant that reserves your table before you arrive. The table is ready, but the chair is empty. That empty chair is undefined.

Functions are the VIP guests. They arrive fully seated, so calling them early works.

console.log(n);   // undefined
greet(); // "Hello!"
var n = 10;function greet() {
console.log("Hello!");
}

Remember it: Hoisting = memory first, code later. var gives undefined early, functions work fully.

3. let, const and the Temporal Dead Zone: The Waiting Room

In one line: let and const are hoisted too, but you can't touch them until their line runs.

var is the relaxed guest: "I'm here, I'm just empty."

let and const sit in a waiting room from the moment they're hoisted until their line runs. That waiting room is the temporal dead zone. Call them out early, and JavaScript throws an error.

console.log(a); // undefined
console.log(b); // ReferenceError: Cannot access 'b' before initialization
var a = 10;
let b = 20;

Interview trap: two errors that look alike

  • Cannot access 'b' before initialization: it exists, it's just still in the waiting room.
  • b is not defined: it was never declared at all.

Remember it: The temporal dead zone is the time between hoisting and initialization of let and const. Access them there, and you get a ReferenceError.

4. Scope and the Scope Chain: Who Can See What

In one line: scope is where a variable can be used, and it’s decided by where the code is written.

Think of rooms inside rooms. A child room can peek into its parent’s room, all the way out to the front door. But a parent can never peek into the child’s room.

“Lexical” sounds fancy, but it only means where the code physically sits.

  • Lexical environment = local memory + a reference to the parent’s lexical environment.
  • Scope chain = the path JavaScript follows to find a variable: own room, then parent, then all the way to global.
function a() {
var x = 7;
  function c() {
console.log(x); // 7, found in the parent's room
}
c();
}
a();
Press enter or click to view image in full size

Fun fact: even an empty JavaScript file gets a global execution context. In the browser, you also get a free window object and a this that points to it.

Remember it: Lexical environment = local memory + parent’s reference. Following those references upward is the scope chain.

5. Block Scope and Shadowing: What Happens Inside { }

In one line: let and const stay inside the curly braces. var escapes.

A block is code wrapped in { }, also called a compound statement. It lets you put many statements where JavaScript expects just one, like after an if.

Inside a block, let and const get their own private memory space. var doesn't care about walls. It walks right out.

{
var a = 10;
let b = 20;
}
console.log(a); // 10, var escaped
console.log(b); // ReferenceError, let stayed inside

Shadowing: the inner one wins

Same name inside and outside a block? Inside the block, the inner variable “shadows” the outer one. Outside, the outer one is untouched.

let b = 100;
{
let b = 20;
console.log(b); // 20
}
console.log(b); // 100

Illegal shadowing: when var breaks the rules

let a = 10;
{
var a = 20; // SyntaxError: Identifier 'a' has already been declared
}

var tries to escape the block and crashes into the let that already lives outside. (The reverse, var outside and let inside, is perfectly legal.)

Remember it: Block = code in { }. Shadowing = the inner variable hides the outer one. Shadowing a let with a var is illegal.

6. Closures: The Function With a Backpack

In one line: a closure is a function bundled together with its lexical environment.

When a function leaves home, it packs a backpack with every variable it could see. Even after its parent function is long gone, the backpack stays on.

function x() {
var a = 7;
function y() {
console.log(a);
}
a = 100;
return y;
}
var z = x();   // x is done and gone
z(); // 100
Press enter or click to view image in full size

Plot twist: it prints 100, not 7. The backpack holds references, not photocopies. Change the value, and the closure sees the change.

The famous setTimeout loop

for (var i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), i * 1000);
}
// 4, 4, 4
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), i * 1000);
}
// 1, 2, 3

With var, all three callbacks share one i. With let, every loop round gets its own fresh i.

Data hiding: a secret only the closure knows

function createCounter() {
let count = 0;
return () => ++count;
}
const counter = createCounter();
counter(); // 1
counter(); // 2
// nobody outside can touch count

Where you’ll meet closures: the module pattern, currying, once, memoize, async state, setTimeout and iterators.

One caution: a backpack you never take off gets heavy. Closures keep variables alive, so careless use can hold memory longer than needed.

Get Sonia Goplani’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember it: A closure remembers the variables from where it was written, even after the outer function has returned. It holds references, not copies.

7. Currying: One Argument at a Time

In one line: currying turns f(a, b) into f(a)(b), and it works because of closures.

Ordering coffee: “Size?” Large. “Milk?” Oat. “Sugar?” None. One question at a time, and the barista remembers every answer. That memory is a closure.

const add = a => b => a + b;
add(2)(3);             // 5const addTwo = add(2); // a = 2 is remembered
addTwo(10); // 12

Where it helps: reusing a function with some arguments already fixed, like addTwo above.

Remember it: Currying = one argument at a time, powered by closures.

8. The this Keyword: It Depends Who’s Asking

In one line: this is decided by how a function is called, not where it is written.

this is like the word "here". Where "here" points depends on where you're standing when you say it.

Where does this point?

  • Global scope: the global object (window in the browser).
  • Inside a regular function call: undefined in strict mode, window otherwise.
  • Inside a method, obj.fn(): the object before the dot.
  • Inside an arrow function: it has no this of its own, so it borrows one from the surrounding code.
  • With call, apply or bind: whatever you choose.
  • Inside an event listener (regular function): the element that received the event.
const user = {
greet() {
console.log(this === user);
}
};
user.greet();            // true, called with user before the dotconst greet = user.greet;
greet(); // false, the object got left behind

Interview trap: copy a method into a variable, and it forgets its object. this is not stored with the function, it's decided at every call.

Remember it: this is decided at call time. Quick trick: look left of the dot.

9. Arrow Functions: Short, Sweet, and Borrowing Their this

In one line: arrow functions are a shorter way to write functions, and they don’t have their own this.

// Regular function
function square(n) {
return n * n;
}
// Arrow function
const squareArrow = n => n * n;

An arrow function is like a guest who never brings their own umbrella. When it rains, they share yours. Their this is whatever this was around them when they were written.

Where that actually helps

const timer = {
seconds: 0,
start() {
setTimeout(function () {
console.log(this.seconds); // undefined, this is not timer
}, 1000);
    setTimeout(() => {
console.log(this.seconds); // 0, borrowed from start()
}, 1000);
}
};
timer.start();

How arrows are different

  • No own this. It comes from the surrounding code. This is called lexical this.
  • call, apply and bind can't change it.
  • No arguments object. Use rest parameters, (...args), instead.
  • Can’t be used with new. They are not constructors.
  • Not hoisted like function declarations. They live in a variable, so a const arrow sits in the temporal dead zone until its line runs.

Interview trap: don’t write object methods as arrow functions. They won’t see the object as this.

Remember it: Arrow functions are shorter, and they borrow this from where they are written.

10. call, apply and bind: Choosing Your this

In one line: call and apply run a function right now with the this you choose. bind gives you a new function with this locked in.

Now that this makes sense, here are the three ways to take control of it.

const person = { name: "Sonia" };
function greet(greeting, mark) {
console.log(greeting + ", " + this.name + mark);
}
greet.call(person, "Hello", "!"); // Hello, Sonia!
greet.apply(person, ["Hello", "!"]); // Hello, Sonia!
const later = greet.bind(person);
later("Hi", "!"); // Hi, Sonia!

The ABC trick:

  • Call = Comma-separated arguments.
  • Apply = Array of arguments.
  • Bind = Brings back a new function for later.

Bonus: give bind some arguments up front, and you get partial application, currying's close cousin.

const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
double(5); // 10

Remember it: call and apply run now, bind returns a function for later. None of them can change an arrow function's this.

11. Prototypes: JavaScript’s Family Tree

In one line: every object has a hidden link to another object, its prototype, and JavaScript follows that chain to find properties the object doesn’t have itself.

Your wallet is empty, so you ask your parent. Their wallet is empty too, so they ask your grandparent. That’s the prototype chain.

Ever wondered where push comes from? You never wrote it.

const arr = [1, 2, 3];
arr.push(4); // push lives on Array.prototype, not on arr

The chain: arr → Array.prototype → Object.prototype → null. When the chain reaches null, JavaScript gives up and returns undefined.

Building your own family tree

const animal = {
eat() { console.log("Eating"); }
};
const dog = Object.create(animal); // animal is dog's prototype
dog.bark = () => console.log("Woof");
dog.bark(); // found on dog itself
dog.eat(); // not on dog, found on its prototype

This is prototypal inheritance: objects inheriting directly from other objects.

Sound familiar? It works just like the scope chain. The scope chain finds variables, and the prototype chain finds properties.

The two names that confuse everyone

  • __proto__ is the link every object has to its prototype. (The modern way to read it is Object.getPrototypeOf(obj).)
  • prototype is a property on functions and classes. Objects created with new get linked to it.
class Person {
greet() { console.log("Hi"); }
}
const sonia = new Person();
Object.getPrototypeOf(sonia) === Person.prototype; // true

Interview trap: classes in JavaScript are syntactic sugar. Under the hood, it’s prototypes all the way down.

Remember it: Every object links to a prototype. JavaScript walks that chain to find properties, ending at null.

Two-Minute Revision: Read This in the Elevator

All of Part 1 in 16 lines. Screenshot it, save it, read it on the way in.

  • JavaScript: a synchronous, single-threaded language.
  • Execution context: built in two phases, memory creation and code execution.
  • Function call: creates a new execution context, deleted when the function returns.
  • Hoisting: memory is given before code runs, so var gives undefined early and functions work fully.
  • Temporal dead zone: the time between hoisting and initialization of let and const.
  • Lexical environment: local memory plus a reference to the parent’s lexical environment.
  • Scope chain: the chain of lexical environments JavaScript follows to find a variable.
  • Block scope: let and const stay inside { }, var leaks out.
  • Shadowing: an inner variable hides an outer one. Shadowing a let with a var is illegal.
  • Closure: a function bundled with its lexical environment. It remembers references, not copies.
  • Currying: f(a, b) becomes f(a)(b), powered by closures.
  • this: decided at call time. Look left of the dot.
  • Arrow functions: shorter, with no own this. They borrow it from where they're written.
  • call, apply, bind: comma-separated, array, and a new function for later.
  • Prototype chain: objects link to prototypes, and JavaScript walks the chain to find properties, ending at null.
  • Classes: syntactic sugar over prototypes.

One Last Thing

If a word slips away mid-interview, don’t panic. Your brain didn’t delete it. It’s just in the temporal dead zone for a moment.

Take a breath. Picture the boxes, the backpack and the VIP line. Start with the one-liner, and the rest will follow.

Coming up in Part 2, How JavaScript Waits and Reacts: callbacks, event bubbling, promises, the event loop, async/await, and a peek under the engine’s hood.

Save this. Share it with someone who has an interview this week. Come back whenever your memory returns undefined.

You’ve got this.

--

JavaScript in Plain English
JavaScript in Plain English

Published in JavaScript in Plain English

New JavaScript and Web Development content every day. Follow to join our 3.5M+ monthly readers.