The Token Machine That Remembers
Walk into a busy clinic in any Indian town and you will see a token machine near the door. You press the button and it prints “OPD-014”. The next person gets “OPD-015”. The machine remembers the last number it gave, and nobody in the waiting room can reach inside and change that number.
How would you build that machine in JavaScript? A normal variable inside a function is forgotten as soon as the function finishes. A global variable remembers, but anyone can change it by mistake. The answer is a closure, one of the most useful ideas in JavaScript.
In this lesson you will first learn arrow functions, the short way of writing functions that modern code uses everywhere. Then you will use them to build closures: counters, private data and “function factories”. You should already know functions and scope from the Functions and Scope and Hoisting lessons.
Arrow Function Syntax
An arrow function is a shorter way to write a function expression. You remove the word function and put an arrow => between the parameters and the body. Then there are a few shortcuts:
| Situation | How to write it |
|---|---|
| Body is a single expression | (a) => a + 50: no braces, the value is returned automatically (implicit return) |
| Body has several statements | (a) => { ...; return x; }: braces and return are needed |
| Exactly one parameter | n => n * 2: brackets are optional |
| No parameters, or two or more | () => ... or (a, b) => ...: brackets are required |
// A regular function expression
const addFeeOld = function (amount) {
return amount + 50;
};
// The same as an arrow function with a body
const addFeeBody = (amount) => {
return amount + 50;
};
// One expression: no braces, no return needed (implicit return)
const addFee = (amount) => amount + 50;
// One parameter: the brackets are optional
const double = n => n * 2;
// No parameters: empty brackets are required
const clinicName = () => "Sunrise Clinic";
// Two or more parameters: brackets are required
const consultFee = (base, isEmergency) => isEmergency ? base * 2 : base;
console.log(addFeeOld(300), addFeeBody(300), addFee(300)); // 350 350 350
console.log(double(21)); // 42
console.log(clinicName()); // Sunrise Clinic
console.log(consultFee(400, true)); // 800
Returning an object from an arrow function
There is one trap. If you write => { name: name }, JavaScript thinks the curly brace starts a function body, not an object. The result is undefined. Wrap the object in round brackets: => ({ name: name }).
// Wrong: the { is read as a function body, so nothing is returned
const makePatientWrong = (name) => { name: name };
console.log(makePatientWrong("Asha")); // undefined
// Right: wrap the object in round brackets
const makePatient = (name, age) => ({ name: name, age: age });
console.log(makePatient("Asha", 34)); // { name: "Asha", age: 34 }
// Arrows are great as short callbacks
const ages = [34, 8, 67, 15];
console.log(ages.filter(age => age < 18)); // [8, 15]
Arrow functions shine as short callbacks for array methods like filter and map, and for event listeners.
When not to use an arrow function
Arrow functions do not get their own this. That matters for methods in an object that use this to reach the object. For those, use a regular method, as below. You will learn exactly how this works in the this Keyword lesson; for now, just remember the rule.
const doctor = {
name: "Dr. Rao",
// Good: a regular method, "this" is the doctor object
greet() {
return "Hello from " + this.name;
},
// Avoid: an arrow function does not get its own "this"
greetArrow: () => "Hello from " + this.name
};
Closures: Functions That Remember
A closure is a function together with the variables from the place where it was created. In the Scope lesson you saw that an inner function can see the variables of its outer function. The surprising part is this: the inner function keeps that access even after the outer function has finished.
Look at createTokenMachine. It creates lastToken, then returns an inner function. Normally lastToken would disappear when createTokenMachine ends. But the returned function still uses it, so JavaScript keeps it alive. Each time you call nextToken(), it adds one to the same remembered variable.
function createTokenMachine() {
let lastToken = 0; // lives inside createTokenMachine
return function () {
lastToken = lastToken + 1; // the inner function remembers lastToken
return lastToken;
};
}
const nextToken = createTokenMachine();
console.log(nextToken()); // 1
console.log(nextToken()); // 2
console.log(nextToken()); // 3
// A second machine has its own, separate memory
const dentistToken = createTokenMachine();
console.log(dentistToken()); // 1
console.log(nextToken()); // 4
Notice that dentistToken starts again from 1. Every call to createTokenMachine() creates a new lastToken, so each machine has its own memory, just like two real machines at two counters.
A simple way to picture a closure: the inner function carries a small backpack. In the backpack are the variables it could see when it was created. Wherever the function goes, the backpack goes too.
Private Data with Closures
In the token machine, lastToken is not a property of anything you can reach. The only way to change it is through the functions we chose to return. This is called private data, or encapsulation. Patients can press the button, but they cannot open the machine and change the counter to 500.
function createTokenMachine(prefix) {
let lastToken = 0; // private: no code outside can touch it directly
return {
next: () => {
lastToken++;
return `${prefix}-${String(lastToken).padStart(3, "0")}`;
},
current: () => lastToken,
reset: () => {
lastToken = 0;
}
};
}
const opd = createTokenMachine("OPD");
console.log(opd.next()); // OPD-001
console.log(opd.next()); // OPD-002
console.log(opd.current()); // 2
console.log(opd.lastToken); // undefined (the number is hidden)
opd.lastToken = 500; // this only adds a new, unrelated property
console.log(opd.next()); // OPD-003 (the real counter is safe)
opd.reset();
console.log(opd.next()); // OPD-001
This pattern of returning an object full of small functions that share private variables is used in many real projects. It protects your data from accidental changes by other code.
Closures in Loops: Why let Matters
Here is a classic interview question. We create three functions in a loop and call them later. With var, all three say “token 4”. With let, they say 1, 2 and 3. Why?
const announcersVar = [];
for (var i = 1; i <= 3; i++) {
announcersVar.push(() => `Calling token ${i}`);
}
const announcersLet = [];
for (let j = 1; j <= 3; j++) {
announcersLet.push(() => `Calling token ${j}`);
}
// Call them later, after the loops have finished
announcersVar.forEach(say => console.log(say()));
// Calling token 4
// Calling token 4
// Calling token 4
announcersLet.forEach(say => console.log(say()));
// Calling token 1
// Calling token 2
// Calling token 3
With var, there is only one i for the whole loop. All three functions remember that same variable, and by the time they run, the loop has finished and i is 4. With let, JavaScript creates a new j for every round of the loop, so each function remembers its own value. The same thing happens with setTimeout and event listeners created in a loop. One more reason to always use let.
Function Factories
A function factory is a function that builds and returns other functions, each one set up with a remembered value. The clinic’s pharmacy gives different discounts to senior citizens, staff and festival customers. Instead of writing three almost identical functions, we write one factory:
// A function that MAKES discount functions
function makeDiscount(percent) {
return price => Math.round(price - price * percent / 100);
}
const seniorCitizen = makeDiscount(20); // remembers 20
const staffDiscount = makeDiscount(50); // remembers 50
const festival = makeDiscount(10); // remembers 10
console.log(seniorCitizen(500)); // 400
console.log(staffDiscount(500)); // 250
console.log(festival(1299)); // 1169
// The same idea, written with two arrows
const makeFee = base => extra => base + extra;
const dentistFee = makeFee(600);
console.log(dentistFee(150)); // 750
seniorCitizen remembers percent = 20, and staffDiscount remembers percent = 50. Each is a closure with its own value in its backpack.
Real-Life Example: A Token System for Two Doctors
Our clinic has two doctors, each with their own queue. Each machine keeps a private counter and a private list of waiting patients. Reception can issue tokens and call the next patient, but nobody can edit the queue directly.
function createTokenMachine(doctor, prefix) {
let lastToken = 0;
const waiting = []; // private queue of patients
return {
issue: (patient) => {
lastToken++;
const token = `${prefix}-${String(lastToken).padStart(3, "0")}`;
waiting.push({ token, patient });
return `${patient}, your token is ${token} for ${doctor}.`;
},
callNext: () => {
const next = waiting.shift(); // take the first person in the queue
return next ? `Now calling ${next.token} (${next.patient})` : `No one is waiting for ${doctor}.`;
},
waitingCount: () => waiting.length
};
}
const general = createTokenMachine("Dr. Mehta", "GEN");
const child = createTokenMachine("Dr. Iyer", "CHD");
console.log(general.issue("Ramesh")); // Ramesh, your token is GEN-001 for Dr. Mehta.
console.log(general.issue("Fatima")); // Fatima, your token is GEN-002 for Dr. Mehta.
console.log(child.issue("Baby Anu")); // Baby Anu, your token is CHD-001 for Dr. Iyer.
console.log(general.waitingCount()); // 2
console.log(general.callNext()); // Now calling GEN-001 (Ramesh)
console.log(child.callNext()); // Now calling CHD-001 (Baby Anu)
console.log(child.callNext()); // No one is waiting for Dr. Iyer.
console.log(general.waitingCount()); // 1
Try these changes:
- Add a third machine for an eye specialist with the prefix
EYE. - Add a
peekfunction that returns the next token without removing it from the queue (hint:waiting[0]). - Try
console.log(general.waiting). Why is itundefined?
Common Mistakes
- Returning an object without brackets.
=> { a: 1 }returnsundefined. Write=> ({ a: 1 }). - Adding braces but forgetting
return. As soon as you use{ }in an arrow function, the implicit return is gone. - Forgetting to call the factory.
const next = createTokenMachine;stores the factory itself. You needcreateTokenMachine()with brackets to get a machine. - Creating a new machine on every click. If you call
createTokenMachine()inside an event handler, every click gets a fresh counter that starts at 1. Create it once, outside, and reuse it. - Using an arrow function as an object method that needs
this. Use the regular method syntax for those.
Practice Task: An Appointment Book
Each doctor can see only a fixed number of patients per day. Write a closure-based appointment book that:
- Is created with
createAppointmentBook(doctor, maxPerDay) - Keeps a private count of booked patients
- Has a
book(name)function that returns a token message, or a “fully booked” message when the limit is reached - Has a
remaining()function that returns the number of free slots - Uses arrow functions for
bookandremaining
// TODO 1: Write createAppointmentBook(doctor, maxPerDay).
// Keep a private count of booked patients.
// Return an object with:
// book(name) -> "Token 1: Asha with Dr. Rao", or "Sorry, Dr. Rao is fully booked today."
// remaining() -> how many slots are left
// TODO 2: Use arrow functions for book and remaining.
// TODO 3: Test it:
// const drRao = createAppointmentBook("Dr. Rao", 2);
// console.log(drRao.book("Asha"));
// console.log(drRao.book("Vinod"));
// console.log(drRao.book("Kiran"));
// console.log(drRao.remaining());
Show one possible answer (try it yourself first!)
function createAppointmentBook(doctor, maxPerDay) {
let booked = 0; // private, thanks to the closure
return {
book: (name) => {
if (booked >= maxPerDay) {
return `Sorry, ${doctor} is fully booked today.`;
}
booked++;
return `Token ${booked}: ${name} with ${doctor}`;
},
remaining: () => maxPerDay - booked
};
}
const drRao = createAppointmentBook("Dr. Rao", 2);
console.log(drRao.book("Asha")); // Token 1: Asha with Dr. Rao
console.log(drRao.book("Vinod")); // Token 2: Vinod with Dr. Rao
console.log(drRao.book("Kiran")); // Sorry, Dr. Rao is fully booked today.
console.log(drRao.remaining()); // 0
const drSen = createAppointmentBook("Dr. Sen", 5);
console.log(drSen.book("Kiran")); // Token 1: Kiran with Dr. Sen
console.log(drSen.remaining()); // 4
Key Takeaways
- Arrow functions are short function expressions:
(a, b) => a + b. One expression means an implicit return. - Wrap returned objects in brackets:
=> ({ ... }). Use regular methods, not arrows, when a method needsthis. - A closure is a function that remembers the variables from where it was created, even after the outer function has finished.
- Closures give you private data: values that can be changed only through the functions you return.
letin a loop creates a new variable for each round, so closures made in loops work as expected.- Function factories return customised functions, such as one discount function per customer type.