How JavaScript uses decisions and repetition to turn a list of data into a full section of a real, living web page
Open the home page of almost any website and look closely. A grid of cards. A list of articles. A row of event announcements. Each item shares the same shape — the same heading style, the same spacing, the same layout — yet each one shows different content. That page was almost certainly not built by hand-typing ten nearly identical blocks of HTML. It was built once, as a pattern, and then filled in automatically from a list of data.
This chapter teaches you the two tools that make that possible: conditionals, which let JavaScript make decisions, and loops, which let JavaScript repeat an action across every item in a collection. Together, these tools let a few lines of code generate entire sections of a page — card grids, resource lists, filtered galleries — from nothing more than an array of data. This is one of the most important turning points in the whole textbook: pages stop being something you type by hand and start being something your code builds.
A resource directory holds twenty links. A class roster holds thirty students, each with a grade that must be checked against a passing threshold. A photo gallery holds fifty images, but the page should only show the ones tagged "featured." None of these pages should be written by hand, one item at a time. What single pattern — decisions plus repetition — lets one small block of JavaScript handle all three? By the end of this chapter, you will be able to write that pattern yourself, and you will recognize it every time you encounter it on the web.
By the end of this chapter, you should be able to:
&&, ||, and !.if, else if, and else statements.for, for...of, and forEach loops to iterate over an array.innerHTML.filter() method.&&, ||, and !if and elseConditional statements; else if chains; controlling page content with conditionals; the = versus === mistakelength property; objects and properties; arrays of objectsfor loop; the for...of loop; the forEach methodinnerHTML; building lists and card grids from data${}; building a complete data-driven card loopif inside a loop; filter buttons and event listeners; the array filter() methodBefore this chapter, your JavaScript handled one thing at a time: a single click, a single element, a single update. That kind of interaction is useful, but most real pages deal with collections — a list of articles, an array of resources, a set of categories. To work with collections, JavaScript needs two tools: conditionals, for making decisions, and loops, for repeating instructions across every item in a collection.
The page structure is defined once. The data fills it in. JavaScript uses logic to connect the two. This chapter builds both tools step by step. By the end, you will be able to take a list of data and produce a full section of HTML from it automatically — the same pattern that powers card grids, resource lists, announcement boards, and filtered galleries across the web.
By Section 23.7 you will combine a loop with a template string to generate an entire row of cards from an array. By Section 23.9 you will let users filter that same data with the click of a button. Keep that destination in mind as you work through the smaller pieces in between.
Every decision in JavaScript starts with a question that can be answered yes or no. JavaScript calls these answers Boolean values: true or false. A Boolean value is not a number or a string — it is a simple answer to a simple question, and it is the only kind of value a conditional statement can act on.
You create Boolean values with comparison operators. A comparison operator takes two values, compares them, and produces either true or false.
| Operator | Meaning | Example | Result |
|---|---|---|---|
=== | Strictly equal | 5 === 5 | true |
!== | Strictly not equal | 5 !== 3 | true |
> | Greater than | 10 > 3 | true |
< | Less than | 3 < 10 | true |
>= | Greater than or equal | 5 >= 5 | true |
<= | Less than or equal | 4 <= 3 | false |
Notice that equality uses three equals signs: ===. This is strict equality — it checks that two values share the same type and the same value. You may also encounter == (loose equality) in older code; it converts types before comparing, which can produce surprising results. Always use === in your own code.
const temperature = 28; const isHot = temperature > 25; console.log(isHot); // true "hello" === "hello" // true "hello" === "Hello" // false — uppercase H is different 7 !== 7 // false — they are equal, so "not equal" is false
Sometimes one comparison is not enough. You need to ask: is this AND that true? Or is this OR that true? JavaScript provides three logical operators.
| Operator | Meaning | Example |
|---|---|---|
&& | AND — both must be true | age > 12 && age < 18 is true only for ages 13–17 |
|| | OR — at least one must be true | day === "Sat" || day === "Sun" is true on weekends |
! | NOT — reverses the Boolean | !isLoggedIn is true if NOT logged in |
const score = 72; const passed = score >= 60 && score <= 100; console.log(passed); // true const day = "Saturday"; const isWeekend = day === "Saturday" || day === "Sunday"; console.log(isWeekend); // true const isLoggedIn = false; const showLoginButton = !isLoggedIn; console.log(showLoginButton); // true
Logical operators are especially useful when you need to check several conditions at once — for example, when deciding whether to show a particular card, highlight an item, or display an error message.
const score = 85; const maxScore = 100; const passing = 60; const passed = score >= passing; const perfect = score === maxScore; const needsReview = score < passing || score > maxScore; console.log(passed); console.log(perfect); console.log(needsReview);
passed?perfect?needsReview?score to 55. What would each variable become?if and elseA conditional statement tells JavaScript to run one block of code when a condition is true and, optionally, a different block when it is false.
const score = 88;
if (score >= 60) {
console.log("Passing score.");
}
The condition must produce a Boolean value, either directly or through a comparison expression. An else block runs when the condition is false:
const score = 45;
if (score >= 60) {
console.log("Passing score.");
} else {
console.log("Score below passing. Please review the material.");
}
When you need to check several possible conditions in order, use else if. JavaScript checks each condition from top to bottom and stops at the first one that is true — only one block runs.
const score = 72;
if (score >= 90) {
console.log("Excellent work.");
} else if (score >= 75) {
console.log("Good work.");
} else if (score >= 60) {
console.log("Passing.");
} else {
console.log("Below passing.");
}
else never runs because a match was already found.
In the browser, conditionals let you decide what content appears based on data or user input:
const username = "Martina";
const greeting = document.querySelector("#greeting");
if (username !== "") {
greeting.textContent = "Welcome back, " + username + "!";
} else {
greeting.textContent = "Welcome! Please sign in.";
}
This is a common pattern. The HTML does not change — the JavaScript decides which message belongs in it.
One of the most frequent JavaScript errors is using a single = (assignment) when you mean === (comparison). if (score = 10) does not compare anything — it sets score to 10 and then treats that value as the condition. Always write if (score === 10) when you intend to compare.
const hour = 14;
let greeting;
if (hour < 12) {
greeting = "Good morning.";
} else if (hour < 17) {
greeting = "Good afternoon.";
} else {
greeting = "Good evening.";
}
console.log(greeting);
hour to 9. What is the output now?hour to 19. What is the output?Most of the work in this chapter involves collections of data — not a single value, but a group of related values. JavaScript stores ordered collections in a structure called an array. Each value in the list is called an element, and the elements are enclosed in square brackets and separated by commas.
const colors = ["red", "green", "blue"]; const scores = [88, 95, 72, 61, 100]; const topics = ["HTML", "CSS", "JavaScript"];
Each element has a position in the array called its index. Array indexes start at 0, not 1. The first element is at index 0, the second at index 1, and so on.
const fruits = ["apple", "banana", "cherry"]; console.log(fruits[0]); // "apple" console.log(fruits[1]); // "banana" console.log(fruits[2]); // "cherry"
Zero-based counting is a frequent source of beginner errors. The last element of a three-item array is at index 2, not 3. Asking for index 3 in this array produces undefined — nothing.
Every array has a length property that tells you how many elements it contains. This will matter when you write loops, because you often need to know how many times to repeat an action.
const topics = ["HTML", "CSS", "JavaScript"];
console.log(topics.length); // 3
A single piece of data — just the string "HTML" — is useful, but most websites work with richer information: a title, a description, a category, a link. JavaScript handles this with objects. An object groups related pieces of information together under named labels called properties, accessed with dot notation.
const lesson = {
title: "What Is HTML?",
category: "HTML",
duration: "15 min"
};
console.log(lesson.title); // "What Is HTML?"
console.log(lesson.category); // "HTML"
An array of objects is a list where every element is an object. This is how almost all real web data is organized:
const lessons = [
{ title: "What Is HTML?", category: "HTML", duration: "15 min" },
{ title: "Writing CSS", category: "CSS", duration: "20 min" },
{ title: "JavaScript Basics", category: "JavaScript", duration: "25 min" }
];
console.log(lessons[0].title); // "What Is HTML?"
console.log(lessons[2].duration); // "25 min"
Read lessons[0].title as: "Go to the first item in the lessons array. Then look at its title property." Arrays of objects are the kind of data you will iterate through for the rest of this chapter, so make sure this reading feels natural before moving on.
const resources = [
{ name: "HTML Reference", type: "guide", free: true },
{ name: "CSS Selectors", type: "guide", free: true },
{ name: "JS Fundamentals", type: "course", free: false }
];
resources.length return?resources[1].name return?resources[2].free return?resources[3] return? Why?A loop repeats a block of code multiple times without writing it out multiple times. Imagine a list of ten resources: without loops, you would need ten separate blocks of code to build ten separate cards. Change the list to twenty resources and you would need twenty blocks. With a loop, you write the pattern once and it repeats automatically for every item.
for (let i = 0; i < 5; i++) {
console.log("Step " + i);
}
A for loop has three parts inside the parentheses, separated by semicolons: for (initialization; condition; update) { ... }
let i = 0 — runs once, creating the counter and setting it to 0.i < 5 — checked before every iteration. True runs the body; false stops the loop.i++ — shorthand for i = i + 1; runs after every iteration.const topics = ["HTML", "CSS", "JavaScript"];
for (let i = 0; i < topics.length; i++) {
console.log(topics[i]);
}
Using topics.length in the condition instead of a hard-coded number means the loop automatically adjusts if you later add or remove items from the array. This is good practice you should follow in your own loops.
const topics = ["HTML", "CSS", "JavaScript"];
for (const topic of topics) {
console.log(topic);
}
for...of automatically moves through every element, placing each one into the temporary variable topic for that iteration. You do not need to manage a counter or worry about going past the end of the array.
const topics = ["HTML", "CSS", "JavaScript"];
topics.forEach(function(topic) {
console.log(topic);
});
forEach produces the same result as for...of. Use for...of when you just need each element. Use a for loop when you also need the index — for example, to number items or access a specific position. You will see all three forms in real projects.
const scores = [88, 74, 92, 61];
let total = 0;
for (let i = 0; i < scores.length; i++) {
total = total + scores[i];
console.log("After step " + i + ", total is " + total);
}
console.log("Final total: " + total);
i <= scores.length instead of i < scores.length?Now combine what you know: use a loop to move through an array of objects, and use each object's properties during each iteration.
const lessons = [
{ title: "What Is HTML?", category: "HTML", duration: "15 min" },
{ title: "Writing CSS", category: "CSS", duration: "20 min" },
{ title: "JavaScript Basics", category: "JavaScript", duration: "25 min" }
];
for (const lesson of lessons) {
console.log(lesson.title + " — " + lesson.duration);
}
Loops and conditionals work together naturally. Inside the loop body, you can test a condition and only act on elements that match — a first glimpse of filtering, which Section 23.9 expands on.
for (const lesson of lessons) {
if (lesson.category === "HTML") {
console.log(lesson.title);
}
}
for (const resource of resources) {
const accessLabel = resource.free ? "Free" : "Paid";
console.log(resource.name + " (" + resource.type + ") — " + accessLabel);
}
What is resource.free ? "Free" : "Paid"? This is a ternary expression — a compact way of writing an if/else that produces a single value: condition ? valueIfTrue : valueIfFalse. Read it as: "If resource.free is true, use "Free". Otherwise use "Paid"." You will see this pattern constantly once you start embedding logic inside template strings.
Logging to the console is useful for learning and debugging, but the real goal in web development is building actual HTML on the page. The general pattern for building repeated HTML from data is: start with an empty string, loop through the data array, build one HTML fragment per item, append it to the growing result, and after the loop write the full result into the page with innerHTML.
<section id="lesson-list"> <h2>Lessons</h2> <ul id="lessons-output"></ul> </section>
const lessons = [
{ title: "What Is HTML?", category: "HTML" },
{ title: "Writing CSS", category: "CSS" },
{ title: "JavaScript Basics", category: "JavaScript" }
];
const listElement = document.querySelector("#lessons-output");
let listHTML = "";
for (const lesson of lessons) {
listHTML += "<li>" + lesson.title + " (" + lesson.category + ")</li>";
}
listElement.innerHTML = listHTML;
Walk through the code carefully. let listHTML = "" starts with an empty string. Inside the loop, listHTML += appends a new <li> string after whatever is already there — the += operator means "add this to whatever is already in the variable." After the loop finishes, listElement.innerHTML = listHTML writes the complete string of HTML into the <ul> in a single step.
A more realistic pattern uses cards, where each card draws on multiple properties from a data object:
const resources = [
{ name: "MDN Web Docs", type: "reference", free: true },
{ name: "CSS-Tricks", type: "guide", free: true },
{ name: "Frontend Masters", type: "course", free: false }
];
const container = document.querySelector("#cards-output");
let cardsHTML = "";
for (const resource of resources) {
const accessLabel = resource.free ? "Free" : "Paid";
cardsHTML += `
<div class="card">
<h3>${resource.name}</h3>
<p>${resource.type}</p>
<span class="badge">${accessLabel}</span>
</div>
`;
}
container.innerHTML = cardsHTML;
After the loop runs, container.innerHTML receives the entire set of card HTML, and the browser renders all of the cards in the grid. The JavaScript inside the loop is a template — it repeats for every item in the array. Add more items to resources and you get more cards automatically, without touching any HTML or CSS.
When you write content into innerHTML, the browser interprets it as real HTML. If any of the data comes from user input — something typed into a field — inserting it directly into innerHTML can be unsafe. The data in this chapter's examples is written directly in the JavaScript file, so it is safe here. Chapter 24 covers safe handling of user input in detail.
Template strings make building HTML fragments significantly cleaner than concatenating pieces with +. A template string uses backticks instead of quotes, can span multiple lines, and embeds expressions using ${}.
const name = "Martina";
const score = 92;
const message = `Hello, ${name}. Your score is ${score}.`;
console.log(message);
// Hello, Martina. Your score is 92.
Compare building HTML with string concatenation versus a template string:
cardsHTML += "<div class=\"card\"><h3>" + resource.name + "</h3><p>" + resource.type + "</p></div>";
cardsHTML += `
<div class="card">
<h3>${resource.name}</h3>
<p>${resource.type}</p>
</div>
`;
The template string can span multiple lines without escape characters, and its indentation visually matches the HTML it produces. Any JavaScript expression — including a ternary — can go inside ${}:
const item = { name: "JavaScript.info", free: true, rating: 4.8 };
const cardHTML = `
<div class="card">
<h3>${item.name}</h3>
<p>Rating: ${item.rating}/5</p>
<span class="badge">${item.free ? "Free" : "Paid"}</span>
</div>
`;
The ternary inside ${} produces different text based on the data. This is how a single template can produce visually different cards for different kinds of items — for example, changing a class name as well as the text it displays.
const announcements = [
{ title: "New Lessons Added", date: "2024-09-01", important: true },
{ title: "Site Maintenance", date: "2024-09-08", important: false },
{ title: "Registration Open", date: "2024-09-15", important: true }
];
let html = "";
for (const item of announcements) {
html += `
<article class="announcement${item.important ? " announcement--important" : ""}">
<h3>${item.title}</h3>
<time datetime="${item.date}">${item.date}</time>
</article>
`;
}
document.querySelector("#announcements").innerHTML = html;
<article> elements will be inserted into the page?class attribute be?class attribute be?The HTML file behind a data-driven section often contains no hand-written cards at all — only an empty container. The JavaScript builds every card from an array: loop through the data, build one template per item, append it to a growing string, and write the result into the container with innerHTML. To add new content, you add one more object to the array. The page updates with no changes to HTML or CSS.
A common web page feature is a filter: a set of buttons or controls that lets users show only certain items. At this stage, you can build a simple version with JavaScript: read the data array, apply a condition, and build HTML from only the items that match.
for (const resource of resources) {
if (resource.free === true) {
output += `
<div class="card">
<h3>${resource.name}</h3>
<p>${resource.type}</p>
</div>
`;
}
}
container.innerHTML = output;
This builds cards only for resources where resource.free === true. To let users control the filter, connect buttons to a function that rebuilds the list:
function buildCards(filter) {
const container = document.querySelector("#resource-list");
let output = "";
for (const resource of resources) {
const shouldShow =
filter === "all" ||
(filter === "free" && resource.free === true) ||
(filter === "paid" && resource.free === false);
if (shouldShow) {
output += `
<div class="card">
<h3>${resource.name}</h3>
<p>${resource.free ? "Free" : "Paid"}</p>
</div>
`;
}
}
container.innerHTML = output;
}
buildCards("all");
document.querySelector("#btn-free").addEventListener("click", function () {
buildCards("free");
});
The function takes a filter string. Inside the loop, shouldShow becomes true or false based on the filter and the current resource. When a user clicks a filter button, the event listener calls buildCards with a different filter string, the function re-runs, and container.innerHTML is replaced with new content.
One function that renders the page from data, called again whenever something changes, is one of the most important patterns in front-end web development. More advanced frameworks like React build on exactly this idea — you can already see its shape clearly in plain JavaScript.
JavaScript arrays have a built-in filter method that returns a new array containing only the elements that pass a test, without modifying the original array.
const freeResources = resources.filter(function(resource) {
return resource.free === true;
});
console.log(freeResources.length); // 3
The function you pass to filter runs once per element and should return true to keep the element or false to exclude it. The if-inside-a-loop approach is simpler while you are learning; the filter-then-loop approach is cleaner once you are comfortable. You will see both in real codebases.
Dynamic content introduces a new category of problems. The HTML you see in the browser is not in your .html file — it exists only after JavaScript runs. This can make problems harder to spot at first.
| Symptom | What to Check |
|---|---|
| Nothing appears on the page | Log the container with console.log(container) — null means the selector is wrong. Check resources.length for an empty array. Log inside the loop to confirm it runs. Check output.length before assigning innerHTML. |
| Some items missing | A conditional is blocking items. Check for = instead of ===, and check for comparing a Boolean to a string, such as resource.free === "true". |
| HTML looks malformed | Check the template string for unclosed tags, missing quotes on attributes, or mismatched backticks. Inspect the actual generated HTML in the Elements panel. |
| Cards appear but look wrong | Usually a CSS problem, not JavaScript. Confirm class names in the template string exactly match class names in the stylesheet. |
// Is the selector finding the right element? console.log(container); // Is the data array populated? console.log(resources.length); // Is the loop actually running? for (const resource of resources) { console.log("Processing:", resource.name); } // What type is this value, really? console.log(resource.name, "— free:", resource.free, "— type:", typeof resource.free);
typeof returns a value's data type as a string. If typeof resource.free returns "string" when you expected "boolean", you have found the bug in your data rather than your logic.
Before chasing the data or the logic, confirm the basics: the JavaScript file is linked correctly; the HTML id matches the selector; the array is populated and accessible at the point the loop runs; the loop is confirmed to iterate with a console log; the output string is non-empty before innerHTML is set; class names in the template string match the CSS; and the browser console shows no errors.
===, !==, >, <, >=, <=) produce Boolean values. Logical operators (&&, ||, !) combine conditions. Always use strict equality, ===.if, else if, and else run different code depending on a condition. JavaScript checks conditions top to bottom and stops at the first match. A single = inside a condition is assignment, not comparison.length property. Objects group named properties with dot notation. Arrays of objects represent real, multi-property web data.for loop counts with an index; for...of visits each element directly; forEach calls a function per element. All three repeat an action across a collection.if/else that produces a value.innerHTML.${}) make multi-line HTML fragments with embedded data dramatically easier to read than string concatenation.filter() method. Filter buttons call a render function with different parameters to rebuild the displayed content.true or false. The result of any comparison expression.===, !==, >, <, >=, <=.if, else if, else) that runs code only when a specified condition is true.&& (AND), || (OR), ! (NOT).object.propertyName.true only when both values share the same type and the same value.${}.if/else expression written as condition ? valueIfTrue : valueIfFalse.Answer these questions before beginning the full review questions. They check basic understanding, not deep analysis.
for loop and a for...of loop?+= operator do when building an HTML string inside a loop?if statement inside a loop to filter data and using the array's filter() method?= is the assignment operator — it sets a variable to a value and does not compare anything. if (score = 10) assigns 10 to score and then evaluates that assigned value as the condition, which can silently produce incorrect behavior. Comparison requires ===.0, and the last element of an array with length equal to n is at index n − 1. Asking for index n returns undefined rather than an error, which can hide the mistake.container.innerHTML inside the loop replaces the entire contents of the container on every iteration. Each new assignment erases the previous one, so only the last item ends up on the page. Build the full HTML string first, then assign it to innerHTML a single time after the loop finishes.filter() always returns a brand-new array containing only the elements that passed the test function. The original array is left completely unchanged. If you need the filtered result later, you must store it in a new variable.=== exclusively to avoid the surprising behavior of ==.These activities connect chapter concepts to real websites. No advanced tools required — only careful observation and a code editor.
length and one property from the first and last elements.if/else if/else decisions a streaming service might use to decide whether to show a "Continue Watching" badge on a video card.Build a one-page application called Resource Directory. The page displays a collection of learning resources as cards in a CSS Grid, and users filter them with category buttons. This project brings together semantic HTML, CSS Grid and variables, external JavaScript, DOM selection, event listeners, template strings, loops, conditionals, and content filtering.
name, type ("reference", "guide", or "course"), description, free, and url.renderResources(items) function that builds one card per item with a template string and writes the result into #resource-grid with innerHTML, plus an updated count display.applyFilter(filter) function that uses resources.filter() to narrow the data by type, then calls renderResources.data-filter attribute, wired up with addEventListener("click", ...) to mark themselves active and call applyFilter.function renderResources(items) {
const grid = document.querySelector("#resource-grid");
const countDisplay = document.querySelector("#count-display");
countDisplay.textContent = `Showing ${items.length} resource${items.length !== 1 ? "s" : ""}`;
if (items.length === 0) {
grid.innerHTML = `<p class="empty-message">No resources found for this filter.</p>`;
return;
}
let html = "";
for (const resource of items) {
html += `
<div class="card">
<h3>${resource.name}</h3>
<span class="badge badge--${resource.type}">${resource.type}</span>
<p>${resource.description}</p>
<a href="${resource.url}" target="_blank" rel="noopener noreferrer">Visit ${resource.name}</a>
</div>
`;
}
grid.innerHTML = html;
}
renderResources builds cards using a template string inside a loop, and updates the count display.applyFilter filters the data with filter() and calls renderResources.target="_blank" and rel="noopener noreferrer".Dynamic content introduces accessibility concerns that static pages do not have. Review any project you build against these points: filter controls should be real <button> elements, not styled <div>s, so they remain keyboard accessible by default. The active filter should be visually distinguishable, and can also be marked with aria-pressed="true" for assistive technologies. Card links should use meaningful text — "Visit MDN Web Docs" rather than a bare "Click here." A count display that updates with each filter change can be marked role="status" with aria-live="polite" so screen readers announce the new total automatically. Finally, tab through the finished page without a mouse — every filter button and every card link must be reachable and operable from the keyboard alone.
= and ===? Why does using = inside an if statement cause a bug?for loop's parentheses? Explain what each one does.for...of loop and a forEach method call?+= operator do when used with strings?innerHTML do, and what kind of value must be assigned to it?if statement inside a for loop and filtering with the array's filter() method. Which one creates a new array?i < array.length generally safer to use in a for loop's condition than a fixed number like i < 10?innerHTML once after a loop finishes is better than setting innerHTML inside the loop on every iteration.resource.free === true behave differently from comparing resource.free === "true"? What problem could this difference cause?name and grade properties. Write a loop that builds a list showing only students with a grade of 70 or higher, with each list item formatted as "Name: Grade".class="card ${resource.featured ? 'card--featured' : ''}" inside a template string. Explain what this produces when resource.featured is true and when it is false.id="output".These questions revisit earlier chapters alongside the new material in Chapter 23.
<article> as its outer element. Explain why <article> is a good semantic choice here rather than <div>.<a href="${resource.url}" target="_blank" rel="noopener noreferrer">. What does rel="noopener noreferrer" do, and why does it matter when opening links in a new tab?innerHTML once at the end, I'll set innerHTML inside the loop, once per card." What is the problem with this approach?if, else if, and else statements.for, for...of, and forEach loops over an array.innerHTML, only once, after the loop completes.if statement inside a loop or the filter() method.Chapter 23 gave you the tools to turn data into page content. Chapter 24 turns to one of the most common interfaces on the web: forms. You will learn how to read values from form fields with JavaScript, define validation rules, display error and success messages near the relevant fields, and prevent a form from submitting when its data is not yet valid. You will also learn about accessible form feedback — writing messages that assistive technologies can announce, so every user receives the guidance they need. The conditionals and loops from this chapter will return constantly: checking required fields is a conditional, and validating a whole form often means looping through its fields one at a time.