Common Filters And Sorting Bugs and How to Catch Them

Common Filters And Sorting Bugs and How to Catch Them

January 16, 2026 · 19 min read · Common Issues

Common Filters And Sorting Bugs and How to Catch Them

Filters and sorting are everyday features in web and mobile applications, yet they hide a surprising number of defects that slip past scripted tests. Users encounter missing results, duplicated entries, or chaotic ordering, which erodes trust and can lead to abandoned checkouts or faulty analytics. This guide walks through the most frequent filter and sorting bug patterns, shows how they manifest, explains why they arise, and gives concrete steps to reproduce, detect, fix, and prevent them. By the end you will have a ready‑to‑use test matrix, a checklist for manual and automated checks, and insight into how persona‑driven autonomous exploration surfaces issues that traditional automation overlooks.

1. Why Filters and Sorting Are Bug Prone

Filters and sorting sit at the intersection of data access, UI state, and business logic. A change in any layer—backend query, API pagination, client‑side state management, or UI component—can break the contract that the displayed list matches the user’s intent. Common root causes include:

Each of these can produce a visible symptom to the user while leaving unit tests green because the tests often hit a happy‑path dataset or mock deterministic responses.

2. Bug Pattern 1 – Off‑by‑One Page Index Errors

What it looks like

A user clicks “Next page” on a paginated table and sees the same items as the previous page, or the last page is completely empty. In mobile apps, infinite scroll may stop prematurely or duplicate the final batch.

Why it happens

Backend APIs often accept page (starting at 1) or offset (starting at 0). If the frontend mixes the two conventions, the calculated offset becomes page * pageSize instead of (page‑1) * pageSize. Conversely, when the backend returns totalCount and the frontend computes totalPages = Math.ceil(totalCount / pageSize), an off‑by‑one in the ceiling calculation can hide the final page when totalCount is an exact multiple of pageSize.

How to reproduce

  1. Set page size to 10.
  2. Insert exactly 30 records into the system.
  3. Navigate to page 3 (should show records 21‑30).
  4. Observe duplication or missing data.

Detection

Fix

Prevention checklist

3. Bug Pattern 2 – Incorrect Comparator Logic (NaN, null handling)

What it looks like

Sorting a column of numbers places NaN or null values at unpredictable positions—sometimes at the top, sometimes at the bottom, sometimes causing the entire list to appear unsorted.

Why it happens

JavaScript’s Array.prototype.sort converts elements to strings unless a compare function is supplied. A naïve compare like return a - b yields NaN when either operand is NaN or null, and the sort algorithm treats NaN as “greater than” any number, leading to erratic ordering. Similar issues appear in SQL (ORDER BY col) where NULLS FIRST/LAST defaults differ between databases.

How to reproduce

  1. Create a list: [5, null, 2, NaN, 9].
  2. Apply the buggy comparator.
  3. Observe that null and NaN end up interleaved or at the front.

Detection

Fix


function safeNumberComparator(a, b) {
  if (a === null && b === null) return 0;
  if (a === null) return 1;   // treat null as larger
  if (b === null) return -1;
  if (Number.isNaN(a) && Number.isNaN(b)) return 0;
  if (Number.isNaN(a)) return 1; // NaN after numbers
  if (Number.isNaN(b)) return -1;
  return a - b;
}

Prevention checklist

4. Bug Pattern 3 – Locale‑Specific Sorting Failures

What it looks like

A list of names sorted alphabetically places “Ångström” after “Z” in a Swedish locale but before “A” in an English locale, causing confusion for users who expect a consistent order across language settings.

Why it happens

Developers often rely on the default JavaScript String.prototype.localeCompare without specifying a locale, which defaults to the runtime environment’s locale. In CI containers the locale may be C, while production servers run en_US.UTF-8. The result is different sort orders between test and production.

How to reproduce

  1. Set environment locale to C.
  2. Sort an array containing ['Ångström', 'Zebra', 'Apple'].
  3. Observe order: ['Zebra', 'Apple', 'Ångström'].
  4. Switch locale to sv_SE and repeat; order changes.

Detection

Fix


function sortByName(a, b, locale = 'en') {
  return a.name.localeCompare(b.name, locale, { sensitivity: 'base' });
}

Prevention checklist

5. Bug Pattern 4 – Stale Cache Leading to Inconsistent Results

What it looks like

After deleting a record, the user still sees it in the filtered list until they manually refresh the page. In some cases, applying a filter after an edit shows outdated counts, causing mistrust in the UI.

Why it happens

Frontend state management libraries (Redux, MobX, React Query) often cache query results to avoid unnecessary network calls. If the cache invalidation logic does not listen to the relevant mutation events (e.g., deleteItem, updateItem), the stale data persists.

How to reproduce

  1. Load a list of 20 items, apply a filter that matches 5 of them.
  2. Delete one of the matching items via a separate API call.
  3. Observe that the filtered list still shows 5 items, and the deleted item is present.
  4. Refresh the page; the list now shows 4 items.

Detection

Fix

Prevention checklist

6. Bug Pattern 5 – Missing Secondary Sort Keys Causing Non‑Deterministic Order

What it looks like

When two records have identical primary sort values (e.g., same timestamp), their order changes each time the list is reloaded, leading to flickering UI and difficulty reproducing bugs.

Why it happens

A sort comparator only considers the primary key. If the comparator returns 0 for equal keys, the underlying sort algorithm may leave the items in whatever order they happened to be in the array, which can vary due to asynchronous data fetching or pagination.

How to reproduce

  1. Insert two records with identical createdAt values (e.g., both at 2024-01-01T12:00:00Z).
  2. Sort by createdAt descending.
  3. Reload the page multiple times; note that the two records swap positions intermittently.

Detection

Fix


function compareItems(a, b) {
  if (a.timestamp !== b.timestamp) return b.timestamp - a.timestamp;
  return a.id.localeCompare(b.id); // assuming string IDs
}

Prevention checklist

7. Bug Pattern 6 – Filter Combination Logic Errors (AND/OR Misuse)

What it looks like

A user selects “Category = Electronics” AND “Price < $100”. The results show items that are either electronics or cheap, effectively treating the criteria as OR. Conversely, a filter meant to show “Sale OR New Arrival” may incorrectly require both conditions, hiding valid items.

Why it happens

When building query objects or SQL WHERE clauses programmatically, developers sometimes mistakenly use || instead of && (or vice versa) when combining predicate functions, or they mishandle nested condition groups. UI state that stores each filter as an independent boolean can also lead to logic errors when the UI combines them incorrectly.

How to reproduce

  1. Create a dataset:
  1. Apply filters: Category = Electronics AND Price < $100.
  2. Expected result: only Item C.
  3. Observe that Item A and B also appear (OR behavior) or that nothing appears (incorrect AND).

Detection

Fix


function passesAllFilters(item, filters) {
  return Object.entries(filters).every(([key, value]) => {
    if (value === null) return true; // no filter
    return item[key] === value;
  });
}

Prevention checklist

8. Bug Pattern 7 – Performance‑Related Sorting Timeouts

What it looks like

On a product listing page with 100 000 items, clicking the “Sort by price” button causes the UI to freeze for several seconds, sometimes triggering a “page unresponsive” warning in the browser.

Why it happens

Client‑side sorting of large arrays blocks the main thread. If the dataset is fetched unsorted from the backend and the frontend attempts to sort it in‑place, the operation can exceed the frame budget (≈16 ms). Similar timeouts occur on the server when an unindexed ORDER BY forces a filesort on millions of rows.

How to reproduce

  1. Load a list with 150 000 records (e.g., via a seed script).
  2. Open the browser’s performance profiler.
  3. Trigger the sort action and record the main‑thread blocking time.
  4. Observe a blocking period > 200 ms.

Detection

Fix

Prevention checklist

9. Bug Pattern 8 – Accessibility‑Related Filter UI Bugs

What it looks like

A screen‑reader user announces that the “Apply Filters” button is unavailable, or keyboard users cannot reach the filter dropdown because it is trapped inside a modal that loses focus.

Why it happens

Developers often build custom filter widgets using

elements without proper ARIA roles, states, or keyboard event handlers. When the filter panel is opened via a button, focus is not moved into the panel, and escaping does not return focus to the trigger.

How to reproduce

  1. Navigate to the filter page using only the Tab key.
  2. Attempt to open the filter panel with Enter or Space.
  3. Verify that focus lands inside the panel and that arrow keys can change options.
  4. Close the panel with Escape and confirm focus returns to the button that opened it.
  5. Run a screen‑reader (NVDA, VoiceOver) and listen for announcements of state changes.

Detection

Fix