Naming and Readability
Code is read many more times than it is written, including by you in three months when you no longer remember any of it. Almost everything in this lesson follows from that single fact, and it is worth reading each practice below as an answer to the question "what will this look like to somebody who has never seen it?"
Name things for what they mean, not for what type they are or how they happen to be stored. d tells the reader nothing; daysUntilExam tells them everything and costs three seconds to type. Booleans read best with is, has or can in front, because the if statement that uses them then reads as an English sentence. Functions should start with a verb — calculateTotal, formatDate, loadStudents — so a reader knows at a glance whether a line performs an action or merely produces a value.
Follow the conventions of the language rather than inventing your own: camelCase for variables and functions, PascalCase for classes, UPPER_SNAKE_CASE for fixed configuration values. Consistency is worth more than any individual choice, because once naming is consistent an inconsistency becomes a signal that something is different, rather than just noise.
The best comment explains why, not what. A comment restating the code goes stale the moment the code changes and then actively misleads whoever trusts it. A comment recording why an unobvious decision was taken — a workaround for a known quirk, a rule from a specification, a deliberate rounding choice — stays valuable for years. If you feel a comment is needed to explain what a line does, a better name almost always removes the need for it.
// Hard to read - the reader has to reverse-engineer the intent
function calc(a, b, c) {
if (c) return a * b * 1.18;
return a * b;
}
// Easy to read - the names carry the explanation
const GST_RATE = 0.18;
function calculateLineTotal(unitPrice, quantity, isTaxable) {
const subtotal = unitPrice * quantity;
if (!isTaxable) return subtotal;
return subtotal * (1 + GST_RATE);
}
console.log(calculateLineTotal(250, 2, true)); // 590
console.log(calculateLineTotal(250, 2, false)); // 500
// A comment that explains WHY
// Rounded before comparing, because an exact comparison on floating
// point values is unreliable - see Lesson 14.
const value = 0.1 + 0.2;
const rounded = Math.round(value * 100) / 100;
console.log(rounded === 0.3); // true - Abbreviations you would not say out loud do not belong in names.
usrMrkAvgsaves eight characters once and costs a moment of translation on every single reading afterwards.
Small Functions That Do One Job
A function should do one thing, and its name should say what that thing is. A reliable test: if you cannot name a function without using the word "and", it is doing two things and wants to become two functions.
The benefits are concrete rather than aesthetic. A small function can be tested on its own. It can be reused somewhere you had not anticipated. It fits on a screen, so you can hold all of it in your head at once instead of scrolling and remembering. And when it throws, the stack trace names it, which tells you where to look before you have read a line.
The single most valuable split is separating logic from display. A function that works out a grade should return the grade and print nothing; a separate function should put it on the page. That one split is what makes the calculation reusable in a report, in a test and in a Node script without modification, and it is probably the clearest difference between a beginner's code and a professional's.
Keep parameter lists short. Beyond about three parameters, switch to an options object with destructuring and defaults, as Lesson 20 showed — the call site then labels its own arguments instead of relying on the reader remembering an order. Avoid boolean parameters in particular: createUser(name, true, false) is unreadable at the call site, while createUser(name, { isAdmin: true, sendEmail: false }) explains itself completely.
// Doing three things at once: reading, deciding and displaying
function processMarks() {
const raw = document.getElementById('marks').value;
const marks = Number(raw);
const grade = marks >= 75 ? 'Distinction' : marks >= 40 ? 'Pass' : 'Fail';
document.getElementById('out').textContent = grade;
}
// Split apart: each piece is testable and reusable on its own
function readMarks(inputId) {
return Number(document.getElementById(inputId).value);
}
function getGrade(marks) { // pure logic - no DOM, no printing
if (marks >= 75) return 'Distinction';
if (marks >= 40) return 'Pass';
return 'Fail';
}
function showGrade(outputId, grade) {
document.getElementById(outputId).textContent = grade;
}
function handleSubmit() {
showGrade('out', getGrade(readMarks('marks')));
}
// An options object instead of positional booleans
function createUser(name, { isAdmin = false, sendEmail = true } = {}) {
return { name, isAdmin, sendEmail };
}
console.log(createUser('Ananya', { isAdmin: true })); getGradeabove is a pure function: same input, same output, no side effects. Pure functions are the easiest code in any language to test, to reuse and to reason about, so make as much of your logic pure as the problem allows.
Prefer Not Changing Things
The hardest class of bug to track down is a value being changed by code you were not looking at. Preferring not to change things is the cheapest defence available, and it costs almost nothing to adopt.
Start with const by default and reach for let only where you genuinely reassign. Then remember precisely what const promises: the name will not move, while the object's contents remain wide open. That is exactly why the second habit matters just as much — prefer the array methods that do not mutate.
map, filter, slice and spread all return new arrays. sort, reverse, splice, push and pop change the original in place. Both groups have their place, and the working rule is: only mutate something you created in the same function. Sorting an array that arrived as a parameter reorders it for the caller, who has no idea you touched it and no way to find out except by debugging.
The same applies to objects. Build a modified copy with { ...original, field: newValue } rather than assigning into an object somebody else is holding. It costs a little memory and removes an entire category of bug — and it happens to be the model every modern framework is built on, so the habit transfers directly to whatever you learn next.
const marks = [82, 45, 91];
// Mutating what you were given changes it for the caller
function highestBad(list) {
return list.sort((a, b) => b - a)[0]; // reorders the caller's array
}
console.log(highestBad(marks)); // 91
console.log(marks); // [91, 82, 45] - silently reordered
// Copy first, and the caller's data is safe
function highest(list) {
return [...list].sort((a, b) => b - a)[0];
}
// Objects: build a modified copy rather than assigning into one
const student = { name: 'Ananya', marks: 82 };
const updated = { ...student, marks: 90 };
console.log(student.marks, updated.marks); // 82 90
// const protects the binding, not the contents
const cart = ['pen'];
cart.push('notebook'); // allowed - the array itself changed
// cart = []; // TypeError - the name cannot be moved - When a function must mutate, say so in its name.
sortInPlace(list)andaddItemTo(cart, item)warn the reader;getHighest(list)that quietly sorts does not, and that mismatch is where the bug lives.
Structure That Survives Growth
Beyond individual functions, three structural habits keep a project workable as it grows past the point where one person can hold it all in mind.
Use modules. One file per concern, with explicit import and export. Each file then has its own scope, the dependencies are visible at the top of every file, and your editor can find and rename things reliably. A single two-thousand-line script works right up until the moment two people need to change it in the same week.
Name your constants. A literal 0.18 appearing in four files is four places to change and four chances to miss one. const GST_RATE = 0.18 in one module is a single place. The same applies to magic strings — status values, storage keys, event names — and those are worse than numbers, because a typo in a raw string produces silence while a typo in a named constant produces an immediate ReferenceError.
Separate concerns. Keep the code that talks to the server, the code that decides things, and the code that touches the page in different files. When they are tangled together, changing a page's layout means editing your business rules and hoping. When they are separated, you can rewrite the entire interface without touching the logic — which is exactly the migration every real project eventually goes through.
// ---- constants.js ----
export const GST_RATE = 0.18;
export const STORAGE_KEY = 'priodemy:cart';
export const STATUS = {
PENDING: 'pending',
SHIPPED: 'shipped',
DELIVERED: 'delivered'
};
// ---- pricing.js ---- pure logic: no DOM, no network
import { GST_RATE } from './constants.js';
export function addGst(amount) {
return amount * (1 + GST_RATE);
}
// ---- api.js ---- talks to the server, and nothing else
export async function loadCart() {
const response = await fetch('/api/cart');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
}
// ---- ui.js ---- touches the page, and nothing else
import { addGst } from './pricing.js';
export function renderTotal(element, subtotal) {
element.textContent = 'Rs ' + addGst(subtotal).toFixed(2);
}
// STATUS.SHIPPED misspelt is a ReferenceError you see immediately.
// The raw string 'shiped' is silence, and a branch that never runs. - A good sign that concerns are properly separated: you could run your logic functions in Node, with no browser at all, and they would work unchanged. If they cannot, they are entangled with the page.
Defensive Habits That Pay for Themselves
A handful of small rules prevent most of the specific bugs this course has shown you, and none of them cost anything to follow.
Convert at the boundary. The moment a value enters your code from a form, a URL or an API, put it into the type you actually want and keep it there. Every line after that can then trust it, and you stop scattering Number(...) and .trim() through logic that should not be thinking about where the data came from.
Use === always, and ?? rather than || for defaults. Strict equality removes the whole coercion table from your head, and there is no case in ordinary code where you need loose equality. ?? replaces only null and undefined, so a deliberate 0 or an intentionally empty string survives — which matters on any page displaying a quantity, a discount or a score.
Guard clauses, and fail loudly. Check the invalid cases first and return, so the main path finishes unindented and readable. And when something is wrong that you cannot handle, throw an error rather than returning null and hoping the caller remembers to check. A loud failure during development costs minutes; a quiet one in production costs a week and somebody's trust.
// Convert once, at the boundary
function handleSubmit(form) {
const input = {
name: form.elements.name.value.trim(),
marks: Number(form.elements.marks.value),
isRegular: form.elements.regular.checked
};
return saveStudent(input); // everything below can trust these types
}
function saveStudent(student) {
// Guards first
if (!student.name) throw new Error('Name is required');
if (!Number.isFinite(student.marks)) throw new Error('Marks must be a number');
// Main path, unindented and easy to read
return { ...student, savedAt: Date.now() };
}
// ?? keeps a deliberate zero; || throws it away
const settings = { discount: 0 };
console.log(settings.discount || 10); // 10 - wrong
console.log(settings.discount ?? 10); // 0 - correct
// === always
console.log('0' == false); // true - do not build on this
console.log('0' === false); // false - say what you mean - Returning
nullfor failure is not always wrong —findandquerySelectorboth do it. What makes it work there is that the absence is expected and every caller knows it. Where absence means something has genuinely gone wrong, throw.
Security, Tooling, and Knowing When to Stop
Three security rules cover most of what a front-end developer can realistically get wrong, and all three have appeared earlier in this course. Never put user-supplied text into innerHTML — use textContent, or build the elements yourself. Never put a secret key in browser code, because anything your page can read, any user can read from the network tab. And never trust client-side validation, because a request can be sent without your page being involved at all; the server must repeat every check.
For tooling, three things repay their setup time within a day. A linter such as ESLint catches unused variables, forgotten awaits, accidental globals, missing break statements in a switch, and the == versus === confusion — all before you run anything. A formatter such as Prettier ends every argument about spacing and makes your diffs show only real changes instead of reindentation. And strict mode, which every module enables automatically, turns several silent mistakes into visible errors.
Finally, know when to stop. Measure before optimising, because almost every intuition about what is slow in JavaScript turns out to be wrong, and clear code that is fast enough — which it usually is — beats clever code that nobody wants to change. The next lesson covers how to measure properly, and how to recognise the small number of cases where it genuinely matters.
- Name for meaning: verbs for functions,
isorhasfor booleans - One job per function, and keep logic separate from display
constby default; copy an array before you sort it- Named constants instead of repeated literals and magic strings
- Convert types once, at the boundary where data enters
===always;??for defaults rather than||- Guard clauses first, so the main path is unindented
- Throw on what you cannot handle instead of returning
nulland hoping textContentfor user text; never a secret key in browser code- Linter, formatter and modules — set up once, useful every day afterwards
- None of this is about being tidy for its own sake. Every item on that list exists because somebody, somewhere, lost a day to the bug it prevents — and most of those bugs have already appeared by name in this course.
