Core jQuery Concepts & DOM Manipulation
From the I want notes on jQuery, html and mysql to prepare for an interview in 2 hours. Advanced hardness. Make each a topic curriculum
Core jQuery Concepts & DOM Manipulation
TL;DR
You'll understand how jQuery wraps DOM elements, why chaining works, and the real difference between .html(), .text(), and .val(). You'll know event delegation cold — the #1 jQuery interview question. You'll also see common performance traps interviewers love to probe, like re-querying the DOM in loops.
1. The Mental Model
jQuery takes raw DOM elements and wraps them in a special array-like object with dozens of built-in methods. Every jQuery method (unless it explicitly returns data) returns that same wrapped object, which is why you can chain .addClass().fadeIn().on() in one line. Selecting is expensive; jQuery objects are cheap to reuse once cached. The core idea: jQuery is a wrapper around a set of matched DOM elements, and almost everything is designed to be chained or cached.
2. The Core Material
2.1 Selection, the jQuery Object, and Chaining

Photo by Suki Lee on Pexels
$('selector') runs a CSS-style query against the DOM and returns a jQuery object — not a raw DOM node, not an array, but an array-like structure with indexed elements ($('.item')[0] gives you the raw DOM node) plus jQuery methods attached.
const $items = $('.item'); // jQuery object, cached
console.log($items.length); // number of matches
console.log($items[0]); // raw DOM element
console.log($items.get(0)); // same thing, official API
Chaining works because most jQuery methods return this; internally — this being the same wrapped set. That's why this works:
$('.alert')
.addClass('visible')
.css('color', 'red')
.fadeIn(200)
.delay(3000)
.fadeOut(200);
Interviewers test whether you know which methods break the chain. Getter methods like .text() (no args), .val() (no args), .html() (no args), .attr('href') (no args), and .data('key') return the value, not the jQuery object — chaining stops there. Setter versions (with an argument) return the jQuery object, so chaining continues.
$('#name').text(); // returns a string — chain broken
$('#name').text('Alice').fadeIn(); // returns jQuery object — chain continues
2.2 DOM Manipulation: html() vs text() vs val() vs append/prepend

Photo by Antonio Batinić on Pexels
This trio gets confused constantly, and interviewers ask "what's the difference" almost as a warm-up.
.html()— gets/sets the inner HTML (parses tags). Vulnerable to XSS if you inject unsanitized user input..text()— gets/sets plain text, escapes HTML entities automatically. Safe against injection..val()— gets/sets the value of form elements (input,select,textarea)..html()/.text()don't work on form values.
$('#box').html('<b>Bold</b>'); // renders bold text
$('#box').text('<b>Bold</b>'); // renders literal string "<b>Bold</b>"
$('#username').val(); // reads current input value
For inserting elements, know the four directional methods:
$('#list').append('<li>last</li>'); // inside, at the end
$('#list').prepend('<li>first</li>'); // inside, at the start
$('#list').after('<p>sibling after</p>'); // outside, after element
$('#list').before('<p>sibling before</p>'); // outside, before element
Reverse direction versions exist too: .appendTo(), .prependTo(), .insertAfter(), .insertBefore() — same operation, but the target is the argument instead of the caller. Useful when moving existing elements rather than creating new ones:
$('#existing-item').appendTo('#new-list'); // moves the actual node
2.3 Event Binding and Delegation

Photo by Pavel Danilyuk on Pexels
The most important interview topic in this note. Direct binding with .on('click', handler) only attaches to elements that exist at bind time. Elements added later via AJAX or .append() won't have the handler — a classic bug.
// BROKEN for dynamically added items
$('.delete-btn').on('click', function() {
$(this).closest('li').remove();
});
$('#list').append('<li>New <button class="delete-btn">X</button></li>');
// clicking the new button does nothing
Event delegation fixes this by attaching the listener to a stable ancestor and filtering by selector when the event bubbles up:
$('#list').on('click', '.delete-btn', function() {
$(this).closest('li').remove();
});
// works for ANY .delete-btn, including ones added after this line runs
Why it works: DOM events bubble up the tree. #list catches the click, checks if event.target (or an ancestor of it) matches .delete-btn, and only then runs the handler. This is also far more memory-efficient than binding 1,000 individual click handlers to 1,000 list items — you get ONE handler total.
flowchart TD
A["User clicks button.delete-btn"] --> B["Click event fires on button"]
B --> C["Event bubbles up DOM tree"]
C --> D["Reaches #list (has delegated listener)"]
D --> E{"Does event.target match '.delete-btn'?"}
E -- Yes --> F["Handler runs, li.remove() executes"]
E -- No --> G["Event ignored, continues bubbling"]
Also know .on() replaced .bind(), .live(), and .delegate() — all deprecated. .on() unifies direct and delegated binding depending on whether you pass a selector as the second argument.
3. Worked Example
Task: Build a to-do list where items can be added dynamically, marked complete on click, and removed with a delete button — all handled efficiently with jQuery.
<ul id="todo-list">
<li>Buy milk <button class="del">X</button></li>
</ul>
<input id="new-todo" type="text" placeholder="New task">
<button id="add-btn">Add</button>
$(function() {
// 1. Add new items dynamically
$('#add-btn').on('click', function() {
const text = $('#new-todo').val().trim();
if (text === '') return;
// .text() used here, NOT .html(), to prevent XSS from user input
const $li = $('<li>').text(text).append(
$('<button>').addClass('del').text('X')
);
$('#todo-list').append($li);
$('#new-todo').val(''); // clear input, chain not needed here
});
// 2. Delegated event for marking complete — works on items added AFTER page load
$('#todo-list').on('click', 'li', function(e) {
// don't toggle complete if the delete button itself was clicked
if ($(e.target).is('.del')) return;
$(this).toggleClass('completed');
});
// 3. Delegated event for deletion
$('#todo-list').on('click', '.del', function(e) {
e.stopPropagation(); // prevent bubbling into the li's click handler above
$(this).closest('li').remove();
});
});
Walking through what's actually happening: $(function(){...})` is shorthand for `$(document).ready(), ensuring the DOM exists before we bind anything. The add-button handler reads .val() from the text input (not .text() or .html(), since it's a form field), builds a new <li> using .text() for the user-supplied string (never .html() — that would let someone type <img src=x onerror=alert(1)> and execute it), and appends a nested button built with jQuery's element constructor $('<button>').
Both click handlers on #todo-list are delegated — bound once on the parent, filtered by selector (li and .del respectively). This means new to-do items added after page load are automatically clickable and deletable with zero extra binding code. The e.stopPropagation() call is the subtle but critical piece: without it, clicking delete would also bubble up and trigger the "mark complete" toggle on the same click, because both handlers live on the same ancestor and both match the bubbling event.
This example demonstrates every core concept in one flow: selection and caching, .val() vs .text() correctness, dynamic element creation, and delegation solving the "elements added later" problem — while also showing why event propagation control matters when handlers overlap.
4. Production Pitfalls & Best Practices
4.1 Real-World Best Practices

Photo by Following NYC on Pexels
- Cache selectors in variables.
const $rows = $('.row');once, reuse it — don't call$('.row')five times in the same function; each call re-walks the DOM. - Prefer delegation for any list that can grow. Comments, notifications, search results — always bind on a stable container.
- Use
.text()by default for user-generated content; only use.html()when you control the source or have sanitized it (e.g., with DOMPurify). - Batch DOM writes. Build HTML as a string or use a detached fragment, then insert once, instead of calling
.append()in a loop (each call can trigger reflow). - Namespace your events (
'click.todoApp') so you can unbind precisely later without nuking other plugins' handlers on the same element. - Use
.prop()for booleans,.attr()for HTML attributes.checked,disabled,selectedshould use.prop()—.attr()gives inconsistent results across browsers for these.
4.2 Common Bugs & Anti-Patterns
Bug 1: Re-querying inside a loop
// BAD — queries the DOM on every iteration
for (let i = 0; i < 100; i++) {
$('#status').append('<p>Item ' + i + '</p>');
}
// FIXED — build once, insert once
let html = '';
for (let i = 0; i < 100; i++) {
html += `<p>Item ${i}</p>`;
}
$('#status').append(html);
Bug 2: Direct binding on dynamic content
// BAD — new .row elements added later get no handler
$('.row').on('click', handler);
// FIXED — delegate from a static parent
$('#table-body').on('click', '.row', handler);
Bug 3: Using .attr('checked') to read checkbox state
// BAD — returns "checked" string or undefined, inconsistent
if ($('#agree').attr('checked')) { ... }
// FIXED — .prop() gives a real boolean
if ($('#agree').prop('checked')) { ... }
Bug 4: Injecting raw user input with .html()
// BAD — XSS vulnerability
$('#comment').html(userInput);
// FIXED — escapes automatically
$('#comment').text(userInput);
4.3 When to Use What
| Situation | Right Tool | Why |
|---|---|---|
| Reading/writing form input value | .val() |
.html()/.text() don't touch form values |
| Rendering trusted HTML (server-templated) | .html() |
Need actual tag parsing |
| Rendering user-typed text | .text() |
Auto-escapes, prevents XSS |
| Elements added after page load need handlers | .on() with delegation |
Direct .on() misses future elements |
| Toggling checkbox/disabled/selected state | .prop() |
Returns real booleans, cross-browser safe |
Reading a static HTML attribute (e.g. data-id, href) |
.attr() |
.prop() is for DOM properties, not raw attributes |
5. Now Try It
Write the jQuery code (no HTML boilerplate needed, assume a <ul id="cart"> exists) to: (1) delegate a click handler on .remove-item buttons inside #cart that removes the parent <li>, and (2) delegate a handler on .qty-input (a text field inside each <li>) that, on change, reads the new value with the correct method and logs it — without breaking when new <li> items are added dynamically after page load.
Success looks like: your code uses .on() delegation (not direct binding) for both handlers, uses .val() (not .text()) to read the quantity input, and correctly scopes .remove-item deletion to .closest('li') rather than removing the wrong element.
Frequently asked about Core jQuery Concepts & DOM Manipulation
More from I want notes on jQuery, html and mysql to prepare for an interview in 2 hours. Advanced hardness. Make each a topic
Get the full I want notes on jQuery, html and mysql to prepare for an interview in 2 hours. Advanced hardness. Make each a topic curriculum
Clone the complete plan to your dashboard for unlimited AI-generated notes, practice quizzes, and a personalised revision schedule.
Create Free Account