Skip to content
wannacode

Comments that lie and comments that help

In short: A good comment says why the code is the way it is; a comment that retells the code goes stale and starts to lie, so it is replaced with a good name or a function.

Problem

The comment everyone trusts

The shop gives free delivery above a certain amount. Last spring the threshold was €50, and the comment above deliveryFee says exactly that. Then marketing raised it to €100. The number in the code was changed. The comment was not.

This fall a colleague writes the banner in the cart. They read the comment, not the code, and put 50:

// Free delivery for orders of €50 or more.
function deliveryFee(total) {
if (total >= 100) {
return 0;
}
return 4.9;
}
function banner(total) {
if (total >= 50) {
return "Delivery is free!";
}
return `€${(50 - total).toFixed(2)} more for free delivery`;
}
const total = 60;
console.log(`Cart: €${total.toFixed(2)}`);
console.log(banner(total));
console.log(`Delivery: €${deliveryFee(total).toFixed(2)}`);
Output
Cart: €60.00
Delivery is free!
Delivery: €4.90

A customer with a €60 cart reads "Delivery is free!" and then pays €4.90 for delivery. Support looks at deliveryFee and finds no bug: the function does exactly what its code says. The bug is in text that nobody runs.

See it

The code moved on, the comment stayed

Here is what happened, step by step:

// Free delivery for orders of €50 or more.
function deliveryFee(total) {
if (total >= 100) {
return 0;
}
return 4.9;
}
function banner(total) {
if (total >= 50) {
return "Delivery is free!";
}
return `€${(50 - total).toFixed(2)} more for free delivery`;
}
const total = 60;
console.log(`Cart: €${total.toFixed(2)}`);
console.log(banner(total));
console.log(`Delivery: €${deliveryFee(total).toFixed(2)}`);
Step by step, in words
  1. The first version: the comment (line 1) and the check (line 3) say the same thing: €50.
  2. The threshold becomes €100. Someone changes the number on line 3 and leaves the comment. The compiler checks code. Nobody checks comments.
  3. A colleague writes the banner. They read the comment, not the code, and put 50 on line 10.
  4. A cart of €60. The banner asks its own threshold: 60 ≥ 50, so "Delivery is free!"
  5. The fee asks the real threshold: 60 < 100, so delivery costs €4.90.
  6. The customer reads "free" and pays €4.90. The lie is on line 1. The bug it caused is on line 10.

The compiler checks every line of code. Tests check what the code does. Nobody checks the comment: it is text next to the code, and every change to the code can leave it behind. This is called a stale comment. It is worse than no comment, because it sounds sure of itself.

The idea

Let the code say it

The threshold is a shop rule, and the rule lives in two places: in the fee code and in the head of everyone who read the comment. Give it a name, and both functions ask the name:

const FREE_DELIVERY_FROM = 100;
function deliveryFee(total) {
if (total >= FREE_DELIVERY_FROM) {
return 0;
}
return 4.9;
}
function banner(total) {
if (total >= FREE_DELIVERY_FROM) {
return "Delivery is free!";
}
return `€${(FREE_DELIVERY_FROM - total).toFixed(2)} more for free delivery`;
}
const total = 60;
console.log(`Cart: €${total.toFixed(2)}`);
console.log(banner(total));
console.log(`Delivery: €${deliveryFee(total).toFixed(2)}`);
Output
Cart: €60.00
€40.00 more for free delivery
Delivery: €4.90

The comment is no longer needed: the name says the same thing. The difference is that a name cannot fall behind. If the threshold becomes €150, you change one line and banner gets the new threshold. If someone deletes the constant, the program stops compiling (in JavaScript it stops at the first call, with an error that the name does not exist).

This is the rule from the lesson on names (Names in code): before you write a comment, check whether a name can say the same thing.

Try it

Predict the output

A hotel sells nights online. What does the program print?

// The price with 20% VAT.
function withVat(net) {
return net * 1.09;
}
console.log(`€${withVat(100).toFixed(2)}`);
Answer

The comment says 20%, and the code multiplies by 1.09. Hotel nights in Bulgaria have 9% VAT, and someone changed the number without touching the comment. The program listens to the code.

See it

Comments that repeat the code

Every comment here says the same thing as the line below it:

// The price of the product.
const price = 25;
// The quantity.
const quantity = 3;
// Multiply the price by the quantity.
const total = price * quantity;
// Print the total.
console.log(`Total: €${total.toFixed(2)}`);
Output
Total: €75.00

You read everything twice and learn nothing new. Worse, you learn to skip comments, and you skip the one that matters too. Delete them. The program is the same, and so is the output.

See it

A comment instead of a name, a comment instead of a function

Here the comments explain things the code hides:

const n = 7; // number of orders of the customer
const s = 420; // what the customer spent, in euros
// The customer is VIP: at least 5 orders or at least €500 spent.
if (n >= 5 || s >= 500) {
console.log("VIP discount: 10%");
}
Output
VIP discount: 10%

The comment next to n is there because the name says nothing. Give it the name from the comment: orders. The comment above the if describes a rule. A rule with a name is a function:

function isVip(orders, spent) {
return orders >= 5 || spent >= 500;
}
const orders = 7;
const spent = 420;
if (isVip(orders, spent)) {
console.log("VIP discount: 10%");
}
Output
VIP discount: 10%

The output is the same, and there are no comments. The if line reads like a sentence. If the rule becomes "at least 10 orders", you change the body of isVip and every place that calls it gets the new rule. How to pull out a function like this is in the lesson Functions.

See it

Commented-out code and a TODO without an owner

function confirm(orderId) {
sendEmail(orderId);
// sendSms(orderId);
// if (smsEnabled) {
// sendSms(orderId);
// }
// TODO: fix
}
Output
Email for order #1063

Is an SMS sent? Nobody knows if it was turned off on purpose, for a while, or by mistake, and nobody dares to delete it. That is how commented-out code lives for years. Delete it: the old code is in the git history and comes back with one command.

A TODO without a name and without a date is a wish nobody owns. A good TODO says who, which ticket and when:

function confirm(orderId) {
sendEmail(orderId);
// TODO(maria, SHOP-412): add SMS once the new provider passes its tests.
}
Output
Email for order #1063

Better still, the task lives in the ticket, and only the link stays in the code.

The idea

What a good comment says

The code says what it does. A good comment says something the code cannot say:

// The courier gets 30% of the fee, rounded down to the cent.
// That is what the contract says (clause 4.2), so there is no Math.round here.
function courierShareCents(feeCents) {
return Math.floor((feeCents * 30) / 100);
}
const fee = 495; // SHOP-388: the courier sends the fee in cents, not in euros.
console.log(`Courier: €${(courierShareCents(fee) / 100).toFixed(2)}`);
Output
Courier: €1.48

There are three such things here:

  • why: rounding down looks like a bug until you read that the contract says so. Without the comment, someone will "fix" it;
  • where to learn more: ticket SHOP-388 tells the story that does not fit in the code;
  • the unit that the type does not say: cents, not euros. In the lesson on code smells you will see how this moves into the type itself.

One more: a warning like "Slow: every call is a request to the bank, about 300 ms. Do not call it in a loop." A comment like that saves the next person a day of searching.

A good comment can go stale too. But it rarely does, because it does not repeat the code: the contract does not change every time you change the function.

More precisely, if you are curious

git blame shows when each line was written. If the code below a comment is newer than the comment itself, be careful: someone touched the code and may not have reached the comment.

See it

Documentation for the caller

Whoever calls deliveryFee from another module does not want to read its body. They want to know what to pass and what they get back. For this every language has a documentation comment, which the editor shows when you hover over the name:

const FREE_DELIVERY_FROM = 100;
/**
* The delivery fee for an order.
* @param {number} total the total of the goods in euros, without delivery
* @returns {number} 0 from FREE_DELIVERY_FROM up, otherwise the courier fee
*/
function deliveryFee(total) {
if (total >= FREE_DELIVERY_FROM) {
return 0;
}
return 4.9;
}
console.log(`Delivery for €60: €${deliveryFee(60).toFixed(2)}`);
console.log(`Delivery for €120: €${deliveryFee(120).toFixed(2)}`);
Output
Delivery for €60: €4.90
Delivery for €120: €0.00
This is JSDoc: /** … */ above the function, with @param {number} for each parameter and @returns for the result. Node does not read it at all. The editor (VS Code) shows it at every call, and with a // @ts-check line at the top of the file it also checks the types from {number}.

Documentation states the contract: what the parameter means and in which units, what comes back and when there is an error. It does not say how it works inside. The numbers from the code are not repeated in it: instead of "from €100" it names the constant, so it cannot fall behind.

More precisely, if you are curious
With // @ts-check the editor underlines deliveryFee("60"), because JSDoc says {number}. This is the same check TypeScript does, without changing the language of the file.

Fill in the missing part:

const FREE_DELIVERY_FROM = 100;

/**
 * The delivery fee for an order.
 * @param {} total the total of the goods in euros, without delivery
 * @returns {number} 0 from FREE_DELIVERY_FROM up, otherwise the courier fee
 */
function deliveryFee(total) {
  if (total >= FREE_DELIVERY_FROM) {
    return 0;
  }
  return 4.9;
}

Try it

Find the bug

The shop sends a "How was your order?" email a day after delivery. The customer's latest order is from Oct 24, and the email asks about the one from Oct 12:

const orders = ["2026-10-19 #1057", "2026-10-24 #1063", "2026-10-12 #1041"];
console.log(reminder(orders));
Output
How was your order 2026-10-12 #1041?

Which line takes the wrong order?

Pick the line with the bug.

Exercise

Clean up the comments

Solve it in your own editor.

Discount for loyal customers. The function has a comment that lies, a comment that repeats the code, a commented-out line and a TODO without an owner:

// Discount for loyal customers: 5%.
function loyaltyTotal(total, orders) {
// if the customer has more than 10 orders
if (orders >= 20) {
// total = total * 0.95;
total = total * 0.9; // reduce total
}
// TODO: rounding
return total;
}
console.log(`Sum: €${loyaltyTotal(200, 25).toFixed(2)}`);
console.log(`Sum: €${loyaltyTotal(200, 12).toFixed(2)}`);
Output
Sum: €180.00
Sum: €200.00

Clean it up: remove the noise and the dead code, replace the numbers with names, and either do the TODO or give it an owner and a ticket. The output must stay the same.

More tasks

  • Document the order. Write a documentation comment for oldestFirst from the orders task: what it takes, what it returns and in which order.
  • The banner at €99.99. Run the banner from the card "Let the code say it" with a cart of €99.99 and of €100. Check that both functions agree right at the threshold.
  • Your project. Open your latest project and find three comments. For each one decide: does it repeat the code, say why, or lie?

Test

Check yourself

1. The comment above a function says one thing and the code below it does another. Which one do you trust?
2. What is wrong with "count++; // increase count by 1"?
3. The function has a commented-out line "// sendSms(order);". What do you do?
4. Which comment is worth keeping?
5. Which TODO will ever get done?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·