The 60 jQuery questions interviewers actually ask, with direct answers, runnable code, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.
60 questions with answersKey Takeaways
jQuery is a small JavaScript library, first released in 2006, that wraps the browser DOM in a short, chainable API. Instead of writing document.querySelectorAll and looping, you write $(".card") to grab a set of elements and then call methods like .addClass(), .on(), or .fadeIn() directly on that set. Its headline job was smoothing over the browser inconsistencies of the 2000s so one line of code worked everywhere. According to the official jQuery API documentation at api.jquery.com, the library's design goals are selecting DOM elements, creating and manipulating them, handling events, and running AJAX, all through a consistent interface. In interviews, jQuery questions probe whether you understand the wrapped-set model, event delegation, and how jQuery relates to plain JavaScript, not memorized method names. This page collects the 60 questions that come up most, each with a direct answer and runnable code. Increasingly the first frontend round is a live AI coding interview, so pair this bank with our AI coding interview prep for the format side.
Watch: The Legend of jQuery in 100 Seconds
Video: The Legend of jQuery in 100 Seconds (Fireship, YouTube)
Test yourself and earn a certificate
10 quick questions. Score 70%+ to download your jQuery certificate.
The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.
jQuery is a JavaScript library that makes DOM selection, event handling, animation, and AJAX shorter and consistent across browsers. You select elements with CSS-style selectors through $() and call methods on the matched set.
It was created in 2006 to fix a real pain: browsers of that era had inconsistent DOM APIs, so the same code broke differently everywhere. jQuery gave you one line that worked in all of them, which is why it spread so fast.
Key point: A one-line definition plus the historical reason (cross-browser pain) beats reciting features. Interviewers open with this to hear how you frame an answer.
Watch a deeper explanation
Video: jQuery Tutorial #1 - jQuery Tutorial for Beginners (LearnCode.academy, YouTube)
$() returns a jQuery object: an array-like wrapper around zero or more DOM elements, carrying all the jQuery methods. A raw DOM element is the plain browser node with native properties like .innerHTML.
You get the raw node out with indexing or .get(): $('#box')[0] or $('#box').get(0). Trying to call a jQuery method on a raw node, or a DOM property on a jQuery object, is a common beginner error.
var $box = $('#box'); // jQuery object
var node = $box[0]; // raw DOM element
var node2 = $box.get(0); // same raw element
$box.addClass('active'); // jQuery method
node.innerHTML = 'hi'; // native propertyKey point: Interviewers use this to check whether you know jQuery wraps the DOM, not replaces it. The [0] and .get(0) escape hatch is the detail they listen for.
jQuery selectors use the same syntax as CSS: $('#id') matches an id, $('.cls') matches a class, $('div') matches a tag, and combinations like $('ul li.active') match nested elements. jQuery finds every matching element and wraps them in one object.
It also supports filters like $(':checked'), $(':first'), and $('[type=email]'). Under the hood modern jQuery uses the browser's querySelectorAll when it can, falling back to its own engine for jQuery-only pseudo-selectors.
$('#login') // by id
$('.btn') // by class
$('ul li:first-child') // CSS combinator
$('input[type="email"]') // attribute selector
$(':checked') // filter| Selector | Matches |
|---|---|
| $('#id') | Element with that id |
| $('.class') | All elements with that class |
| $('tag') | All elements of that tag |
| $('a[href^="http"]') | Links whose href starts with http |
Key point: The follow-up is often 'what does that map to in plain JS?'. the technical answer is document.querySelectorAll. Say it to show you know the abstraction.
Watch a deeper explanation
Video: jQuery Crash Course [1] - Intro & Selectors (Traversy Media, YouTube)
It runs your code once the DOM is fully parsed and elements are available, but before images and other heavy assets finish loading. That's the right moment to attach handlers and touch elements safely.
The short form $(function(){ ... }) is the same thing. Putting script at the end of the body or using defer achieves a similar effect in plain JavaScript.
$(document).ready(function () {
$('#save').on('click', save);
});
// shorthand for the same thing
$(function () {
$('#save').on('click', save);
});ready() fires as soon as the DOM tree is built, so it runs early and is what you usually want. load waits for every asset, including all images and iframes, so it runs later.
Use ready for wiring up behavior. Use load only when you genuinely need final image sizes or positions, since it can be noticeably slower on media-heavy pages.
| $(document).ready() | $(window).on('load') | |
|---|---|---|
| Fires when | DOM is parsed | All assets loaded |
| Timing | Earlier | Later |
| Typical use | Attach handlers | Needs final image sizes |
Key point: Naming that load waits for images is the detail the question needs. Most candidates know ready but blur the difference.
Most jQuery methods return the same jQuery object, so you can call the next method directly on the result instead of re-selecting. That lets you write a whole sequence of operations on one matched set in a single readable statement.
Chaining is faster too, because the expensive part (finding the elements) happens once, not per line.
$('#alert')
.removeClass('hidden')
.addClass('show')
.text('Saved')
.fadeIn(200);
// one selection, four operationsKey point: If asked why chaining works, say each method returns 'this' (the jQuery object). That one sentence separates memorizers from understanders.
.text() gets or sets plain text, escaping any HTML. .html() gets or sets the inner HTML markup. .val() gets or sets the current value of form fields like inputs, selects, and textareas.
The safety note interviewers like: use .text() for user-supplied content, because .html() will render tags and can open an XSS hole if the data isn't trusted.
$('#label').text('<b>hi</b>'); // shows the literal <b>hi</b>
$('#label').html('<b>hi</b>'); // renders bold hi
var name = $('#name').val(); // reads input valueKey point: Volunteering the XSS point (.text escapes, .html doesn't) is a strong signal you think about security, not just output.
.attr() reads and writes HTML attributes as they appear in the markup. .prop() reads and writes the live DOM property, which reflects the current state.
The classic case is a checkbox: .attr('checked') gives you the original HTML attribute, while .prop('checked') gives the true current true/false. For checked, selected, and disabled, use .prop().
// user just ticked the box
$('#agree').attr('checked'); // may still be undefined
$('#agree').prop('checked'); // true, the live state
$('#submit').prop('disabled', true); // correct way to disableKey point: The checkbox example is the expected proof. Reaching for .prop() on checked/disabled is what marks a candidate who has hit this bug for real.
Use .addClass('name'), .removeClass('name'), and .toggleClass('name'). .hasClass('name') returns true or false so you can branch. Each maps directly to the native classList API.
toggleClass is handy for open/closed and active states because one call flips the class either way.
$('#menu').addClass('open');
$('#menu').removeClass('open');
$('#menu').toggleClass('open'); // flips it
if ($('#menu').hasClass('open')) {
// do something
}The main method is .on('event', handler). $('#btn').on('click', function(){ ... }) runs the function when the button is clicked. Inside the handler, 'this' is the raw element and the event object gives details.
.on() replaced older methods like .click() and .bind() as the single way to attach events, and it also supports event delegation, which the shortcuts don't do well.
$('#btn').on('click', function (e) {
e.preventDefault();
console.log('clicked', this.id);
});
// multiple events at once
$('#field').on('focus blur', handleState);Watch a deeper explanation
Video: jQuery Crash Course [2] - Events (Traversy Media, YouTube)
event.preventDefault() cancels the browser's default action, like a link navigating or a form submitting, while letting your handler run. event.stopPropagation() stops the event from bubbling up to parent elements.
They solve different problems: one blocks the default behavior, the other blocks the travel path. Returning false from a jQuery handler does both at once, which is convenient but can hide intent.
$('#link').on('click', function (e) {
e.preventDefault(); // don't follow the href
loadInline();
});
$('.card').on('click', function (e) {
e.stopPropagation(); // don't trigger the parent's click
});Key point: Interviewers love the trap 'what does return false do in a jQuery handler?'. Answer: both preventDefault and stopPropagation. Many candidates only know one.
.show() displays an element, .hide() removes it from view by setting display to none, and .toggle() flips between the two. Passing a duration animates the change: .fadeIn(300), .slideUp(200).
These change inline CSS. If an element is hidden by a stylesheet class, .show() may not override it, which is a common 'why won't it appear' bug.
$('#panel').hide();
$('#panel').show();
$('#panel').toggle(); // flip visibility
$('#panel').fadeIn(300); // animated.append() and .prepend() add content inside an element, at the end or start. .before() and .after() add content outside it. .remove() deletes an element, and .empty() clears just its children.
You can pass an HTML string or a jQuery object, which lets you build an element and insert it in one flow.
$('#list').append('<li>New item</li>'); // last child
$('#list').prepend('<li>First</li>'); // first child
$('#old').remove(); // delete it
$('#box').empty(); // clear childrenWatch a deeper explanation
Video: jQuery Crash Course [3] - DOM Manipulation (Traversy Media, YouTube)
.css('property') reads a computed style; .css('property', 'value') sets one; and .css({ ... }) sets several at once. It writes inline styles on the element.
For anything you'll toggle repeatedly, prefer adding and removing a class over setting .css() directly, because classes keep styling in the stylesheet where it belongs.
$('#box').css('color'); // read
$('#box').css('color', 'red'); // set one
$('#box').css({ color: 'red', padding: '8px' }); // set many.each() runs a callback once per matched element. The callback gets the index and the raw DOM element, and inside it 'this' is also the raw element, so wrap it with $(this) to call jQuery methods.
There's also a static $.each(collection, fn) for looping over plain arrays and objects, which is a slightly different tool with the same name.
$('.price').each(function (index, el) {
var value = parseFloat($(this).text());
$(this).text('$' + value.toFixed(2));
});Key point: The $(this) wrapping is the detail. Candidates often call jQuery methods on the raw element inside .each() and it fails; knowing why is the point.
.children() returns only the direct children of an element, one level down. .find() searches all descendants at any depth. Both take an optional selector to filter the result.
Reach for .children() when The technical sequence is shallow and predictable, and .find() when the target may be nested deeper.
$('#menu').children('li'); // only direct <li> children
$('#menu').find('a'); // every <a> anywhere insideInclude it with a script tag before your own scripts, either from a CDN like Google or jsDelivr, or from a local copy. Loading from a CDN can be faster because the file may already be cached from another site.
Your code that uses jQuery must run after the library loads, which is why the jQuery script tag comes first and your logic sits in a document ready block.
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="app.js"></script>
<!-- app.js can now use $ -->You can bind mouseenter and mouseleave with .on(), or use the .hover(enterFn, leaveFn) shortcut that takes two handlers. mouseenter and mouseleave don't fire for child elements, which is why they beat the older mouseover and mouseout for hover effects.
$('.card').hover(
function () { $(this).addClass('lift'); }, // enter
function () { $(this).removeClass('lift'); } // leave
);They do the same insertion but read in opposite directions. $('#list').append(item) means 'put item inside list'. $(item).appendTo('#list') means 'put this item into list'. The target and the content swap places.
The practical reason to know both: .appendTo() returns the inserted content, so you can keep chaining on the new element, while .append() returns the parent.
$('#list').append('<li>A</li>'); // returns #list
$('<li>B</li>').appendTo('#list') // returns the new <li>
.addClass('new'); // chain on it$(el).index() returns the position of an element among its siblings, starting at 0. Passed a selector or element, $(el).index(target) returns el's position within that set instead. It's how you find 'which one was clicked'.
Inside a delegated click handler, indexing the clicked row against all rows tells you the row number without storing it in the markup.
$('#rows').on('click', 'tr', function () {
var n = $(this).index(); // 0-based row position
console.log('clicked row', n);
});For candidates with working experience: event delegation, AJAX, effects, and the questions that separate users from understanders.
Delegation binds one handler on a stable ancestor and passes a selector, so the handler fires for matching children, including elements added to the DOM after binding. $('#list').on('click', 'li', fn) works even for list items created later.
It matters for two reasons: dynamically added elements get handled without rebinding, and one listener on a parent is lighter than hundreds on individual children.
// direct: only binds to <li> that exist right now
$('#list li').on('click', handle);
// delegated: works for future <li> too
$('#list').on('click', 'li', handle);
$('#list').append('<li>added later</li>'); // still handledKey point: The 'works for elements added later' point is the whole reason this question exists. Say it explicitly and give the parent-selector form.
.on() unified all of them. .bind() attached direct handlers, .live() attached delegated handlers at the document level (removed in jQuery 1.9), and .delegate() delegated from a chosen parent. .on() covers every case with one signature.
Knowing this history is useful for reading old code: if you see .live() or .delegate(), you're looking at a legacy codebase and .on() is the modern replacement.
| Old method | What it did | Modern .on() form |
|---|---|---|
| .bind('click', fn) | Direct handler | .on('click', fn) |
| .delegate('.x','click',fn) | Delegated from parent | .on('click', '.x', fn) |
| .live('click', fn) | Delegated at document (removed) | $(doc).on('click', '.x', fn) |
$.ajax() is the full-control method: you pass a settings object with url, method, data, and callbacks. It returns a jqXHR object you attach .done(), .fail(), and .always() to. $.get() and $.post() are shorthands for the common cases.
jQuery normalized AJAX across browsers long before fetch existed, which is a big part of why it stayed popular in older stacks.
$.ajax({
url: '/api/users',
method: 'GET',
dataType: 'json'
})
.done(function (users) { render(users); })
.fail(function (xhr) { showError(xhr.status); });Watch a deeper explanation
Video: jQuery Crash Course [5] - Ajax (Traversy Media, YouTube)
They're shorthand wrappers over $.ajax for the common cases. $.get(url, data, cb) does a GET, $.post(url, data, cb) does a POST, and $.getJSON(url, cb) does a GET and parses the response as JSON.
They cover most everyday calls with less boilerplate; reach for the full $.ajax when you need headers, error handling, timeouts, or a non-standard method.
$.getJSON('/api/config', function (cfg) {
applyConfig(cfg);
});
$.post('/api/save', { id: 7, name: 'Asha' }, function (res) {
console.log('saved', res);
});$.ajax handles JSON parsing, cross-browser quirks, and treats HTTP error statuses as failures automatically. fetch is built into browsers, returns a real promise, but only rejects on network errors, so you check response.ok yourself and call response.json() to parse.
On a modern greenfield project, fetch or axios is the usual pick. On a page already loading jQuery, $.ajax is fine and saves a dependency.
| $.ajax | fetch | |
|---|---|---|
| Built in | Needs jQuery | Native to browsers |
| Rejects on 404/500 | Yes (.fail runs) | No, only on network error |
| JSON parsing | Automatic with dataType | Manual response.json() |
| Returns | jqXHR (promise-like) | Real Promise |
Key point: The 'fetch doesn't reject on 404' point is the gotcha interviewers probe. It shows you understand both tools, not just that fetch is newer.
A Deferred is jQuery's own promise implementation. You create one with $.Deferred(), resolve or reject it, and hand callers its .promise() so they attach .then(), .done(), and .fail(). $.ajax returns a promise-like object built on this.
$.when(a, b, c) waits for several async operations and runs a callback when all finish, similar to Promise.all. Modern jQuery Deferreds are largely Promises/A+ compatible, so they interoperate with native promises.
function loadUser(id) {
var d = $.Deferred();
$.getJSON('/api/user/' + id)
.done(function (u) { d.resolve(u); })
.fail(function () { d.reject('failed'); });
return d.promise();
}
$.when(loadUser(1), loadUser(2)).done(renderBoth);.animate() tweens numeric CSS properties from their current value to a target over a duration, with an optional easing and a completion callback. The built-in shortcuts .fadeIn/.fadeOut, .slideUp/.slideDown, and .show/.hide with a duration cover the common cases.
It animates numeric properties only, so colors need a plugin. Effects queue per element by default, which is both handy for sequences and a source of 'why is my animation delayed' confusion.
$('#box').animate(
{ left: '250px', opacity: 0.5 },
400,
function () { console.log('done'); }
);Every animated element has an internal queue: effects run one after another in the order you call them. Chaining .fadeOut().fadeIn() plays them in sequence, not together.
You control it with .stop() to halt the current animation, .stop(true) to clear the queued ones too, and .finish() to jump every queued animation to its end state. .stop(true, true) is the standard fix for hover animations that stack up on fast mouse movement.
$('.card').hover(
function () { $(this).stop(true, true).fadeTo(150, 1); },
function () { $(this).stop(true, true).fadeTo(150, 0.6); }
);
// stop(true, true) clears the queue and jumps to endKey point: The stacking-hover bug and the .stop(true, true) fix is a favorite. it in practice matters.
.data('key') reads and .data('key', value) writes data associated with an element. On read, it also picks up HTML5 data-* attributes, so .data('userId') returns the value of data-user-id.
One subtlety trips people up: .data() stores writes in jQuery's internal cache, not back on the attribute, so after $(el).data('x', 5) the DOM attribute doesn't change. Use .attr('data-x') if you need the attribute itself updated.
<div id="row" data-user-id="42"></div>
$('#row').data('userId'); // 42, camelCased from data-user-id
$('#row').data('userId', 99); // updates jQuery cache, not the attributeKey point: The 'writes don't update the DOM attribute' detail separates people who read the docs from people who guessed. It causes real bugs.
From a matched set you move around the tree: .parent() and .parents() go up, .children() and .find() go down, .siblings(), .next(), and .prev() move sideways, and .closest(selector) walks up to the nearest matching ancestor.
.closest() is the workhorse inside delegated handlers, letting you find the container of whatever was clicked.
$('#list').on('click', '.delete', function () {
$(this).closest('li').remove(); // remove the whole row
});.filter(selector) keeps only matching elements, .not(selector) drops them, .eq(index) keeps the one at that position, and .first() and .last() keep the ends. They reduce a matched set without re-querying the DOM.
.filter() also accepts a function, so you can keep elements by any condition you compute.
$('li').filter('.active'); // only active items
$('li').not('.disabled'); // everything except disabled
$('tr').eq(2); // the third row
$('input').filter(function () { // by computed condition
return $(this).val().length > 0;
});.clone() makes a deep copy of matched elements, ready to insert elsewhere. By default event handlers are not copied. .clone(true) copies data and events too.
Watch for duplicate ids: cloning an element with an id gives you two elements sharing that id, which is invalid HTML and a source of selector bugs. Clone by class or strip the id.
var $copy = $('#template').clone(true); // copies events too
$copy.removeAttr('id').appendTo('#list');.serialize() turns a form's fields into a URL-encoded query string, ready to send with $.post. .serializeArray() gives the same data as an array of name/value objects if you'd rather build JSON.
Only successful controls are included: disabled fields and unchecked checkboxes are left out, which matches how a normal form submit behaves.
$('#signup').on('submit', function (e) {
e.preventDefault();
$.post('/api/signup', $(this).serialize())
.done(showSuccess);
});.trigger('click') fires a handler programmatically. You can also define your own event names: bind with .on('cartUpdated', fn) and fire with .trigger('cartUpdated', data). Custom events let separate parts of a page communicate without direct calls.
It's a light pub/sub pattern: one part triggers, others listen, and neither needs a reference to the other.
$(document).on('cartUpdated', function (e, count) {
$('#badge').text(count);
});
// somewhere else
$(document).trigger('cartUpdated', [3]);noConflict() releases the $ shortcut so another library that also uses $ (like Prototype in older stacks) can have it back. You then call jQuery instead of $, or scope $ locally.
The common pattern is an IIFE that receives jQuery as $, keeping the short name inside your code without owning the global.
jQuery.noConflict();
(function ($) {
// $ is jQuery again, only in here
$('#app').addClass('ready');
})(jQuery);.each() runs a function for its side effects and returns the original set for chaining. $.map() (and .map()) builds and returns a new array from what the callback returns, so it's for transforming, not just visiting.
Returning null or undefined from $.map skips that item, which makes it double as a filter-and-transform in one pass.
var ids = $('.row').map(function () {
return $(this).data('id');
}).get(); // .get() turns the jQuery result into a plain array
// ids -> [1, 2, 3]jQuery fires global AJAX events on the document: ajaxStart, ajaxStop, ajaxError, and ajaxComplete. Bind ajaxStart to show a spinner and ajaxStop to hide it, and every request on the page is covered without touching each call.
ajaxStart fires when the first request begins and ajaxStop when the last one finishes, so overlapping requests are handled correctly.
$(document)
.on('ajaxStart', function () { $('#spinner').show(); })
.on('ajaxStop', function () { $('#spinner').hide(); });Add a function to $.fn, the object every jQuery instance inherits from. Inside it, 'this' is the matched set, and you return this so the plugin stays chainable. Wrap the whole thing so $ is safe to use.
This is how tools like sliders and date pickers plug in: $(el).myPlugin(options) reads naturally and joins any chain.
(function ($) {
$.fn.highlight = function (color) {
return this.each(function () {
$(this).css('background', color || 'yellow');
});
};
})(jQuery);
$('.note').highlight('lime'); // chainableKey point: Returning this.each(...) is the whole trick: it keeps the plugin chainable and applies to every matched element. That's what interviewers check for.
.wrap() puts an HTML structure around each matched element individually. .wrapAll() wraps the whole set in one shared container. .unwrap() removes an element's parent, keeping the element in place. They restructure the DOM without rebuilding it by hand.
The individual-versus-shared difference is the point: wrapping three items with .wrap() gives three wrappers, with .wrapAll() gives one.
$('.field').wrap('<div class="cell"></div>'); // one wrapper each
$('.tag').wrapAll('<div class="tags"></div>'); // one shared wrapper.toggleClass('name', boolean) adds the class when the boolean is true and removes it when false. It saves an if/else: instead of branching to add or remove, you pass the condition straight in as the second argument.
It's handy for syncing a class to state, like marking a row active when a value crosses a threshold.
var overdue = daysLeft < 0;
$('#task').toggleClass('overdue', overdue);
// adds .overdue if true, removes it if false.closest(selector) walks up from the element and stops at the first match, returning at most one element (and it can match the element itself). .parents(selector) collects every matching ancestor all the way to the root.
In event handlers you almost always want .closest(), because you're looking for the one containing element, not the full ancestor chain.
| .closest(sel) | .parents(sel) | |
|---|---|---|
| Starts at | The element itself | The element's parent |
| Returns | First match, at most one | All matching ancestors |
| Typical use | Find the container in a handler | Collect a chain of ancestors |
Key point: The 'closest can match itself and stops at one' detail is the discriminator. Reaching for .closest() in a delegated handler is the everyday correct choice.
.one('click', handler) binds a handler that runs at most once and then removes itself automatically. It's the clean way to handle a first-time-only action, like a one-shot onboarding tooltip or an init that must not repeat.
Without it you'd bind with .on() and call .off() inside the handler, which .one() does for you in a single line.
$('#start').one('click', function () {
initGame(); // runs once, then the handler is gone
});advanced rounds probe performance, architecture judgment, and the reasons teams keep or drop jQuery. Expect every answer to draw a follow-up.
Cache selectors in variables instead of re-querying, since $() walks the DOM every call. Scope searches with a context ($('.item', $list)) or .find() so you search a subtree, not the whole document. Batch DOM writes: build markup as a string or detached fragment and insert once rather than in a loop.
Prefer id selectors (they hit getElementById), delegate events instead of binding hundreds of handlers, and minimize layout thrashing by not interleaving reads and writes.
// slow: touches the DOM every loop
for (var i = 0; i < items.length; i++) {
$('#list').append('<li>' + items[i] + '</li>');
}
// fast: build once, insert once
var html = items.map(function (t) { return '<li>' + t + '</li>'; }).join('');
$('#list').append(html);Key point: The build-once-insert-once pattern is the single biggest jQuery perf win. Show the before/after and you've answered the follow-up already.
id selectors are fastest because they map to getElementById. Tag and class selectors are fast via querySelectorAll. jQuery-only pseudo-selectors like :visible, :first, and :has fall back to jQuery's own engine and scan, so they're the slow ones.
The practical rule: filter with a plain CSS selector when you can, and if you must use :visible on a large set, narrow the set first with a fast selector.
Relative selector cost on a large DOM
Illustrative tiers, not benchmark numbers. id maps to getElementById; jQuery pseudo-selectors scan the matched set.
Key point: Naming that :visible and :has are jQuery extensions (not native CSS) is the senior detail. It explains why they're slower without hand-waving.
Map the patterns, then replace incrementally. $(sel) becomes document.querySelectorAll with a forEach, .addClass/.removeClass become classList.add/remove, .on becomes addEventListener (with a manual delegation helper), and $.ajax becomes fetch. Do it module by module behind tests, not in one sweep.
The honest part of the technical answer is scope: delegation, animations, and AJAX error handling are where jQuery did real work, so those need care. Confirm each replacement's behavior before deleting the jQuery version.
| jQuery | Vanilla equivalent |
|---|---|
| $('.x') | document.querySelectorAll('.x') |
| .addClass('y') | el.classList.add('y') |
| .on('click', fn) | el.addEventListener('click', fn) |
| $.getJSON(url) | fetch(url).then(r => r.json()) |
Key point: The incremental, test-backed migration story beats 'rewrite it all'. the risk awareness as much as the API mapping is the technical point.
The classic leak is removing elements with raw innerHTML or by detaching nodes while jQuery still holds their event handlers and data in its internal cache. Handlers on detached nodes, and closures capturing large objects, keep memory alive.
Prevent it by removing elements with .remove() (which cleans up handlers and data) or .off() before detaching, avoiding unnecessary global handlers, and being careful with long-lived delegated handlers on the document.
// leak risk: node gone from view but jQuery still tracks it
document.getElementById('list').innerHTML = '';
// clean: jQuery removes handlers and data cache
$('#list').empty(); // or .remove() for the element itselfWhen the app is a large stateful single-page app: jQuery has no component model or reactive rendering, so syncing the DOM to data by hand gets fragile fast, and a framework like React or Vue fits better. Also when bundle size is tight, since modern browsers cover most of what jQuery smoothed over.
The balanced coverage names where jQuery still earns its place: server-rendered pages, quick enhancements, WordPress and legacy stacks, and small internal tools where a build step isn't worth it.
Key point: The production-ready answer argues both sides. Blanket 'jQuery is dead' indicates dogma; naming the cases where it still fits indicates judgment.
Every jQuery method that isn't a getter returns the same jQuery object, the one referenced by 'this' inside the method. Because that object is array-like and carries all the methods on its prototype, the next call in the chain runs against the same matched set.
Getters break the chain deliberately: .val() with no argument, .text() with no argument, or .attr('x') return the value, not the jQuery object, which is why you can't chain after reading.
// simplified idea
$.fn.turnRed = function () {
this.css('color', 'red');
return this; // returning this is what enables chaining
};Key point: The getter-breaks-the-chain insight is the follow-up here. Knowing why .val() ends a chain but .val('x') continues it shows real understanding.
Install it from npm and import it: import $ from 'jquery'. The bundler pulls it into your build, so there's no global unless you expose one on purpose. Plugins that expect a global jQuery need it provided, often via the bundler's provide plugin or by assigning window.jQuery.
For tree-shaking and size, jQuery is monolithic, so you get the whole library. If size matters, that's an argument for a smaller alternative or native APIs.
import $ from 'jquery';
$(function () {
$('#app').addClass('ready');
});
// plugin needing a global:
window.jQuery = window.$ = $;Set a timeout in the $.ajax settings so a hung request fails instead of hanging forever. Handle .fail() to inspect the status and textStatus (including 'timeout' and 'abort'), and wrap the call in a helper that retries a bounded number of times with backoff for transient failures.
Distinguish retryable errors (network, 5xx, timeout) from ones you shouldn't retry (4xx like 401 or 422). Retrying a validation error just wastes calls.
function getWithRetry(url, tries) {
return $.ajax({ url: url, timeout: 5000 })
.catch(function (xhr) {
var retryable = xhr.status === 0 || xhr.status >= 500;
if (tries > 1 && retryable) return getWithRetry(url, tries - 1);
return $.Deferred().reject(xhr);
});
}.hide() keeps the element in the DOM and just sets display none. .detach() takes it out of the DOM but keeps its jQuery data and events, so you can reinsert it intact. .remove() takes it out and clears its data and handlers for good.
Use .detach() when you'll re-add the same element with its behavior later, and .remove() when you're done with it and want the cleanup.
| Method | In DOM after? | Keeps events/data? |
|---|---|---|
| .hide() | Yes (display: none) | Yes |
| .detach() | No | Yes, for reinsertion |
| .remove() | No | No, cleaned up |
Key point: The .detach() keeps-data-for-reinsertion distinction is the senior detail. Most candidates know remove and hide but blur where detach fits.
Modern jQuery Deferreds (since 3.0) are largely Promises/A+ compliant, so a jqXHR is thenable and you can await it. await $.getJSON('/api') works and resolves to the parsed data. You can also wrap one with Promise.resolve() to get a guaranteed native promise.
The caveat is older jQuery (before 3.0), where Deferreds weren't spec compliant and mixing them with native promise chains caused subtle timing and error-swallowing bugs. On legacy versions, convert deliberately.
async function load() {
try {
const users = await $.getJSON('/api/users'); // jqXHR is awaitable
render(users);
} catch (xhr) {
showError(xhr.status);
}
}You can tag an event binding with a namespace: .on('click.editor', fn). Later, .off('click.editor') removes only that binding, and .off('.editor') removes every handler in that namespace, leaving other click handlers untouched.
This matters in plugins and modular code: a component can clean up exactly its own handlers on teardown without knowing what else bound to the same element.
$('#doc').on('keydown.editor', handleKeys);
$('#doc').on('click.editor', handleClick);
// teardown removes only the editor's handlers
$('#doc').off('.editor');Key point: Namespaced teardown (.off('.editor')) is a plugin-author's signal. Bringing it up in practice marks someone who has written reusable jQuery components.
$(selector, context) scopes the search to within context instead of the whole document. $('td', row) finds cells inside that one row. It's shorthand for $(context).find(selector) and can be a meaningful speedup on a large DOM.
The context can be a DOM element, a jQuery object, or a document, which is why plugins often accept a root element and search inside it.
var $row = $('#order-42');
$('.qty', $row).val(3); // only the qty cell in that row
// equivalent to $row.find('.qty').val(3)The big one is XSS through .html(), .append(), or building elements from untrusted strings: any user data placed as HTML can inject scripts. Use .text() for untrusted content, or build elements and set values with .text()/.val() so the browser escapes them.
Also validate and encode data from AJAX before rendering it, and never trust that a selector built from user input is safe. Passing user input into $() as HTML (a string starting with <) creates elements, which has been an injection vector.
// unsafe: renders any tags in the comment
$('#out').html(userComment);
// safe: browser escapes it
$('#out').text(userComment);Key point: The $() treats a leading-< string as HTML point is the subtle one. Naming it shows you know jQuery's own injection surface, not just generic XSS.
what they have, not a rule comes first. If it's a server-rendered app or a large existing jQuery codebase that works, ripping it out for its own sake is churn with no user benefit; keep it and invest in tests. If they're building a new interactive app with lots of state, a framework fits better and new jQuery there is a liability.
Frame it as cost and risk: bundle size, hiring, maintenance, and how much the DOM-syncing burden grows with the app. The decision is contextual, and saying so is The production-ready answer.
Key point: The technical decision depends on whether you weigh trade-offs against a real context instead of reciting a trend.
Watch a deeper explanation
Video: Getting started with jQuery (tutorial) - Beau teaches JavaScript (freeCodeCamp.org, YouTube)
Check the obvious order first: does the element exist when you bind (is the code inside document ready, or does the element get added later)? If it's added later, you need delegation. Then confirm the selector actually matches: log $(sel).length in the console.
Next, check for a swallowed event: another handler calling stopPropagation, a return false higher up, or the wrong event name. Use the browser's event listener panel to see what's actually bound. The structure, does it exist, does the selector match, is the event reaching it, is what you're really testing.
Key point: The 'element added after binding, so you need delegation' cause is the single most common real answer. Leading with it shows pattern recognition from experience.
A click on a child element starts at that element and travels up the ancestor chain toward the document. jQuery's delegated handler sits on a parent; as the event passes through, jQuery checks whether the original target matches the delegated selector and, if so, runs your handler with 'this' set to the matched child.
Understanding this order explains why stopPropagation on the child kills a delegated parent handler, and why event.target and event.currentTarget differ inside it.
How a click reaches a delegated handler
A stopPropagation call anywhere below the delegated parent prevents the event from ever reaching it.
Key point: Knowing target vs currentTarget inside a delegated handler is the senior tell. It's the difference between using delegation and understanding it.
Keep a reference to the in-flight jqXHR and abort it before starting the next one, so a slow earlier response can't overwrite a newer result. $.ajax returns a jqXHR with an .abort() method for exactly this. Debouncing the input on top means you only fire after the user pauses.
The bug this prevents is the out-of-order response: request A is slow, B is fast, and without aborting, A's stale result lands last and wins.
var current = null;
$('#search').on('input', function () {
if (current) current.abort(); // cancel the previous one
current = $.getJSON('/api/search?q=' + this.value)
.done(render);
});Key point: The out-of-order-response problem and .abort() fix is the point. Mentioning debounce plus abort together indicates someone who has shipped a real search box.
$.extend(target, source) merges the source's properties onto the target and returns the target. The standard plugin pattern is $.extend({}, defaults, userOptions): start from an empty object, apply your defaults, then override with whatever the caller passed.
Pass true as the first argument for a deep merge that recurses into nested objects. The shallow default is fine for a flat options object and avoids surprises with shared nested references.
$.fn.slider = function (options) {
var settings = $.extend({}, {
speed: 300,
loop: true
}, options);
return this.each(function () { build(this, settings); });
};jQuery still wins when you need to add behavior to server-rendered HTML fast, with almost no build setup, and you don't want a full framework. It trades a small download and an extra abstraction layer for terse DOM code that reads the same in every browser. Where it loses is large stateful UIs: jQuery gives you no component model or reactive rendering, so keeping the DOM in sync with data by hand gets fragile as apps grow. That's the job React, Vue, and Angular do. Knowing when each fits, and saying it out loud, is itself an interview signal.
| Approach | Model | Best at | Watch out for |
|---|---|---|---|
| jQuery | Imperative DOM wrapping | Quick DOM work on server-rendered pages | No component or state model at scale |
| Vanilla JS | Native DOM APIs | Zero dependencies, full control | More verbose, you handle edge cases |
| React / Vue | Declarative components | Large stateful single-page apps | Build tooling, steeper learning curve |
| Angular | Full framework | Big enterprise apps, opinionated structure | Heaviest setup and concepts |
Prepare in layers, and practice out loud. Most jQuery rounds move from concept questions to a small live task where you wire up events or an AJAX call, so rehearse each stage rather than only reading answers.
The typical jQuery interview flow
Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.
10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.
Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These jQuery questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.
See how the AI Coding Interviewer works