Advanced jQuery Interactions & AJAX
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
Advanced jQuery Interactions & AJAX
TL;DR
You'll understand how jQuery's event delegation, .on()/.off(), and AJAX methods ($.ajax, .then(), $.Deferred) actually work under the hood. You'll be able to explain why delegated events survive DOM changes, how to chain async calls cleanly, and how to debug the classic "it works once then breaks" jQuery bugs interviewers love to probe.
1. The Mental Model
jQuery events don't attach to elements directly when you delegate — they attach to a parent and listen for bubbling. AJAX in jQuery is just a wrapper around XMLHttpRequest that returns a Promise-like object called a Deferred. Once you see delegation as "catch the bubble, not the element" and AJAX as "a promise factory," the weird edge cases stop being weird. Everything advanced in jQuery is really just event bubbling plus promises wearing a jQuery costume.
2. The Core Material
2.1 Event Delegation — .on() vs direct binding

Photo by Ann H on Pexels
When you write $('.item').on('click', fn), jQuery finds every .item element right now and attaches a listener to each one. If you later add a new .item via AJAX or .append(), it has no listener — this is the #1 jQuery interview bug.
Delegation fixes this by attaching the listener to a stable ancestor and checking event.target against a selector as the event bubbles up:
// BAD: only binds to elements that exist NOW
$('.item').on('click', function() {
console.log('clicked', $(this).text());
});
// GOOD: delegated — works for future .item elements too
$('#item-list').on('click', '.item', function() {
console.log('clicked', $(this).text());
});
Under the hood, .on('click', '.item', fn) stores .item as a filter. When any click bubbles up to #item-list, jQuery walks event.target up the DOM checking .closest('.item') against the list container. This is why delegation works even if .item didn't exist when you registered the handler — the check happens at click time, not bind time.
Removing delegated handlers requires matching the same signature:
// This does NOT remove the delegated handler above:
$('#item-list').off('click');
// This does — you must specify the selector too:
$('#item-list').off('click', '.item');
2.2 The Event Object & stopPropagation vs stopImmediatePropagation

Photo by Sami Abdullah on Pexels
Three methods get confused constantly:
event.preventDefault()— stops the browser's default action (form submit, link navigation) but lets the event keep bubbling.event.stopPropagation()— stops the event bubbling to ancestors, but sibling handlers on the same element still fire.event.stopImmediatePropagation()— stops bubbling AND stops any other handler on the same element from firing.
$('.btn').on('click', function(e) {
console.log('handler 1');
e.stopImmediatePropagation();
});
$('.btn').on('click', function(e) {
console.log('handler 2'); // never runs
});
Interviewers ask this because it separates people who copy-paste jQuery from people who understand the DOM event model it sits on top of.
2.3 AJAX: $.ajax, Deferreds, and chaining

Photo by Jan van der Wolf on Pexels
$.ajax() returns a jqXHR object — jQuery's Deferred-based wrapper around Promise. It supports both the old callback style and modern .then():
$.ajax({
url: '/api/users',
method: 'GET',
dataType: 'json'
})
.done(function(data) {
console.log('success', data);
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.error('failed', textStatus, errorThrown);
})
.always(function() {
console.log('runs no matter what');
});
.done() / .fail() / .always() are jQuery-specific aliases. .then(successFn, failFn) also works and is closer to native Promise syntax — this matters because jQuery Deferreds are NOT fully Promise/A+ compliant (a jqXHR's .then() didn't propagate exceptions like native promises until jQuery 3.0 fixed this). If asked "is a jqXHR a real Promise," the honest answer: since jQuery 3.0, yes it's spec-compliant enough to interoperate, but historically no.
Chaining sequential requests — the naive way nests callbacks (callback hell); the clean way chains .then():
$.ajax({ url: '/api/user/1' })
.then(function(user) {
return $.ajax({ url: '/api/orders/' + user.id }); // returning a jqXHR chains it
})
.then(function(orders) {
console.log('orders for user', orders);
})
.catch(function(err) {
console.error('chain broke', err);
});
Returning a jqXHR (or any thenable) inside .then() flattens the chain — the next .then() waits for it, just like native Promises.
Running requests in parallel with $.when():
$.when(
$.ajax({ url: '/api/users' }),
$.ajax({ url: '/api/products' })
).done(function(usersResp, productsResp) {
// each arg is an array: [data, statusText, jqXHR]
var users = usersResp[0];
var products = productsResp[0];
console.log(users, products);
});
Note the destructuring gotcha: $.when resolves with an array per request, not the raw data — a very common interview trip-up.
2.4 Request lifecycle & common config

Photo by Rafael Minguet Delgado on Pexels
flowchart TD
A["$.ajax() called"] --> B["beforeSend callback (optional)"]
B --> C["Request sent via XHR"]
C --> D{Response status}
D -->|"2xx success"| E["dataFilter → dataType parsing (json/html/text)"]
E --> F["done() / .then(success) fires"]
D -->|"4xx/5xx or network error"| G["fail() / .catch() fires"]
F --> H["always() / .finally() fires"]
G --> H
Key config flags interviewers probe:
- async: false — makes the call synchronous (blocks the UI thread). Deprecated, but you should know it exists and why it's bad (freezes the browser).
- dataType: 'json' — tells jQuery to JSON.parse the response automatically; mismatch here causes silent parsing failures.
- contentType: 'application/json' — tells the server what you're sending; forgetting this + JSON.stringify(data) is the classic "backend receives [object Object]" bug.
$.ajax({
url: '/api/users',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ name: 'Sam' }), // must stringify manually here
dataType: 'json'
});
3. Worked Example
Scenario: Build a live search box that queries a MySQL-backed endpoint /api/search?q=term, debounces keystrokes, cancels stale requests, and delegates click handling on results (since results are re-rendered on every search).
<input type="text" id="search-box" placeholder="Search users...">
<ul id="results"></ul>
$(function() {
var currentRequest = null;
var debounceTimer = null;
$('#search-box').on('input', function() {
var term = $(this).val().trim();
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function() {
if (term.length < 2) {
$('#results').empty();
return;
}
// Abort any in-flight request so slow old responses
// can't overwrite fresh results (a classic race condition)
if (currentRequest) {
currentRequest.abort();
}
currentRequest = $.ajax({
url: '/api/search',
method: 'GET',
data: { q: term },
dataType: 'json'
});
currentRequest
.done(function(users) {
var html = users.map(function(u) {
return '<li data-id="' + u.id + '">' + u.name + '</li>';
}).join('');
$('#results').html(html);
})
.fail(function(jqXHR, status) {
if (status !== 'abort') {
console.error('search failed:', status);
}
});
}, 300); // debounce: wait 300ms after last keystroke
});
// Delegated click — results are re-rendered every search,
// so a direct .on('click', 'li', ...) bound once still works
$('#results').on('click', 'li', function() {
var userId = $(this).data('id');
console.log('selected user', userId);
});
});
Why each piece matters:
1. Debounce (setTimeout + clearTimeout) — without it, every keystroke fires a request, hammering MySQL with LIKE '%term%' queries.
2. Abort stale requests — without .abort(), if you type "j" then "jo" then "john", the "j" response might arrive after the "john" response and overwrite it with wrong results. This is a real race condition, not a hypothetical.
3. Delegation on #results — since <li> elements are destroyed and recreated every search via .html(), binding directly to li would only work for the first render.
4. dataType !== 'abort' check in .fail() — calling .abort() itself triggers .fail(), so without this check you'd log a false error every time the user keeps typing.
On the MySQL side, this endpoint should use a parameterized query (WHERE name LIKE CONCAT('%', ?, '%')) and ideally an index on name — but that's the SQL note's job.
4. Production Pitfalls & Best Practices
4.1 Real-World Best Practices
- Always delegate for dynamic content. Any element added after page load (AJAX-rendered lists, modals, tabs) needs
.on(event, selector, fn), not direct binding. - Namespace your events (
'click.myFeature') so.off('click.myFeature')doesn't accidentally remove other plugins' click handlers on the same element. - Set a global AJAX error handler with
$(document).ajaxError()for things like auth-token expiry, so you don't repeat 401-handling logic in every call. - Cache jQuery selections (
var $el = $('#thing')) instead of re-querying the DOM every time you need it — cheap but real perf win in loops. - Use
.data()for element state, not custom attributes you parse manually — it avoids type-coercion bugs (data-id="5"comes back as a number, not a string). - Abort or ignore stale requests in any typeahead/search feature — race conditions are a top real-world bug source.
- Set
timeouton$.ajaxfor anything hitting an external service, so a hung request doesn't hang your UI indefinitely.
4.2 Common Bugs & Anti-Patterns
Bug 1: Binding before DOM elements exist
// BAD
$('.result-item').on('click', fn); // results not loaded yet
$.ajax({ url: '/api/items' }).done(render);
// FIXED
$('#results').on('click', '.result-item', fn); // delegated, works regardless of load order
Bug 2: Forgetting contentType on JSON POST
// BAD — server receives urlencoded garbage
$.ajax({ url: '/api/save', method: 'POST', data: { name: 'Sam' } });
// FIXED
$.ajax({
url: '/api/save',
method: 'POST',
contentType: 'application/json',
data: JSON.stringify({ name: 'Sam' })
});
Bug 3: Not handling the .fail() case at all
// BAD — silent failure, users see nothing
$.ajax({ url: '/api/data' }).done(render);
// FIXED
$.ajax({ url: '/api/data' })
.done(render)
.fail(function() { $('#error').text('Could not load data.').show(); });
Bug 4: Memory leaks from never unbinding delegated handlers on removed containers
```javascript
// BAD — old container's handlers linger if you re-create #results repeatedly via replaceWith
$('#results').replaceWith('
// old #results handlers are gone with the element, but if you
// bound to a PARENT that persists, they silently accumulate
// FIXED — bind once to a stable, never-replaced ancestor
Frequently asked about Advanced jQuery Interactions & AJAX
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