**What is a closure?**
A closure is a function that *remembers* the variables from its outer scope even after that outer function has returned. In JavaScript, every function creates a closure over the scope where it was defined:
```js
function makeGreeter(greeting) {
// 'greeting' is in makeGreeter's scope
return function(name) {
// inner function closes over 'greeting' — it's still accessible
return `${greeting}, ${name}!`;
};
}
const sayHello = makeGreeter('Hello');
const sayHi = makeGreeter('Hi');
sayHello('Alice'); // 'Hello, Alice!' — greeting is still 'Hello'
sayHi('Bob'); // 'Hi, Bob!' — separate closure, greeting is 'Hi'
```
Each call to `makeGreeter` creates a *new* closure with its own `greeting`. Closures are not shared — they each capture their own copy of the enclosing scope.
**Lexical scope — scope is determined at write time, not call time**
JavaScript uses *lexical* (static) scoping: a function's scope is determined by where it is *written* in the source, not where it is *called* from:
```js
const x = 'global';
function outer() {
const x = 'outer';
function inner() {
console.log(x); // 'outer' — determined at write time
}
return inner;
}
const fn = outer();
fn(); // 'outer' — even though called from global scope
```
**The classic loop bug — `var` vs `let`**
`var` is function-scoped; all iterations of a `for` loop share the same `i`. By the time the callbacks fire, the loop has finished and `i` is its final value:
```js
// BUG with var:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // logs 3, 3, 3
}
// FIX 1: use let (block-scoped — each iteration gets its own i):
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // logs 0, 1, 2
}
// FIX 2: IIFE to capture i per iteration (pre-ES6 pattern):
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 0);
})(i);
}
```
Implement `makeCounter()` that returns an object with `increment()`, `decrement()`, `reset()`, and `value()` methods. The counter state must be private — not accessible directly on the returned object.
function makeCounter() {
// return object with increment, decrement, reset, value
}
The code below logs `3, 3, 3` instead of `0, 1, 2`. Explain why, and provide two fixes: one using `let`, one using an IIFE.
// This logs 3, 3, 3 — fix it to log 0, 1, 2
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
Solution
// Fix 1: use let (each iteration gets its own block-scoped i)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0, 1, 2
}
// Fix 2: IIFE to capture i per iteration
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 0);
})(i);
}
Implement `once(fn)` that returns a wrapper function. The wrapper calls `fn` only on the first invocation, then returns the cached result for all subsequent calls without calling `fn` again.
function once(fn) {
// fn should only be called the first time
// subsequent calls return the cached result
}
Solution
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn(...args);
}
return result;
};
}
const init = once(() => { console.log('initialising'); return 42; });
init(); // logs 'initialising', returns 42
init(); // returns 42, no log
Implement `memoize(fn)` that returns a memoized version of `fn`. On the first call with a given set of arguments the result is computed and cached. On subsequent calls with the same arguments the cached value is returned directly.
function memoize(fn) {
// cache results keyed by arguments
}
Solution
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args));
}
return cache.get(key);
};
}
const slowSquare = memoize(n => { console.log('computing'); return n * n; });
slowSquare(5); // 'computing' → 25
slowSquare(5); // → 25 (no log, from cache)
slowSquare(6); // 'computing' → 36
Implement `partial(fn, ...presetArgs)` that returns a new function with `presetArgs` pre-filled. When the returned function is called with additional arguments, they are appended after the preset ones.
function partial(fn, ...presetArgs) {
// return a function with presetArgs pre-filled
}
Implement `makeBankAccount(initialBalance)` using a closure to keep `balance` private. Return an object with `deposit(amount)`, `withdraw(amount)`, and `getBalance()`. `deposit` should throw if amount ≤ 0; `withdraw` should throw if amount exceeds balance.
function makeBankAccount(initialBalance) {
// balance must be private
// return { deposit, withdraw, getBalance }
}
Solution
function makeBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
if (amount <= 0) throw new Error('Deposit must be positive');
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) throw new Error('Insufficient funds');
balance -= amount;
return balance;
},
getBalance() { return balance; },
};
}
const acc = makeBankAccount(100);
acc.deposit(50); // 150
acc.withdraw(30); // 120
acc.getBalance(); // 120
acc.balance; // undefined — private
Implement `makeAdder(n)` that returns a function. The returned function takes one argument `x` and returns `x + n`. Each call to `makeAdder` should create an independent adder that remembers its own `n`.
function makeAdder(n) {
// return a function that adds n to its argument
}
Implement `debounce(fn, delay)` that returns a debounced version of `fn`. The debounced function delays calling `fn` until `delay` milliseconds have passed since its last invocation. If called again before the delay expires, the timer resets.
function debounce(fn, delay) {
// return a debounced version of fn
// fn should only be called delay ms after the last invocation
}
Solution
function debounce(fn, delay) {
let timerId;
return function(...args) {
clearTimeout(timerId);
timerId = setTimeout(() => fn(...args), delay);
};
}
const handleInput = debounce((value) => {
console.log('searching for:', value);
}, 300);
// Fast typing — only the last call fires after 300ms:
handleInput('h');
handleInput('he');
handleInput('hel');
handleInput('hell');
handleInput('hello');
// After 300ms: 'searching for: hello'
Implement `compose(...fns)` that applies functions right-to-left: `compose(f, g, h)(x)` is equivalent to `f(g(h(x)))`. Then implement `pipe(...fns)` that does the same left-to-right.
What does this code log? Explain why each closure produces its result independently without interfering with the others.
```js
function makeMultiplier(x) {
return function(y) {
return x * y;
};
}
const triple = makeMultiplier(3);
const quadruple = makeMultiplier(4);
console.log(triple(5));
console.log(quadruple(5));
console.log(triple(quadruple(2)));
```
// triple(5) → 3 * 5 = 15
// quadruple(5) → 4 * 5 = 20
// quadruple(2) = 8; triple(8) = 3 * 8 = 24
// Output: 15, 20, 24
// Why: each call to makeMultiplier creates a new closure
// capturing its own 'x'. triple closes over x=3,
// quadruple closes over x=4. They don't share state.
No split tab
Cookie preferences
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.