Advanced Filtering and Sorting — Crafting High-Performance APIs

An in-depth exploration of advanced filtering and sorting techniques in APIs. We unravel strategies, syntax conventions, scalability challenges, performance tips, and practical implementation patterns in REST and GraphQL.

ShareShare

Advanced Filtering and Sorting — Crafting High-Performance APIs

Filtering and sorting are fundamental functionalities in any data-driven application. Whether it's an e-commerce product search, a user directory for an enterprise dashboard, or an issue tracker for a DevOps tool, the ability to filter and sort results is not a luxury, but a necessity.

However, the implementation of filtering and sorting logic across APIs is often inconsistent, fragile, or poorly documented. This article aims to provide comprehensive insights into the intricacies of building flexible, performant, and developer-friendly APIs by understanding the evolution and best practices of filtering and sorting in both REST and GraphQL APIs.


The Evolution of Filtering and Sorting in APIs

In the early 2000s, REST API designs were comparatively rigid. For each filter or sort operation, a new route was required, thus increasing the API's surface area and fragmenting logic. For example:

GET /users
GET /users/active
GET /users/recently-created

With the growing dynamism in web applications, the need for more composable and queryable interfaces became evident. API teams needed a mechanism to expand the capabilities of their interfaces without having to exponentially increase the number of endpoints.

This led to the introduction of:

  • Query string parameters in REST (?filter=name&sort=-createdAt)
  • Structured arguments in GraphQL
  • Declarative query objects in Firebase, Firestore, and MongoDB

Today, flexible filtering and sorting are considered essential for modern APIs, particularly those powering dashboards, analytics, catalogs, feeds, and search functionalities.


REST API: Filtering and Sorting Conventions

REST does not provide a strict specification for filtering, but several conventions have emerged over time. Here are some common patterns:

Basic Key-Value Filters

This is the simplest form of filtering, where the key-value pairs in the query string represent the fields and values to filter:

GET /products?category=shoes&brand=Nike

Range Queries

Range queries allow for filtering of numerical values within a certain range:

GET /products?price[gte]=50&price[lte]=100

In/Not In Lists

This allows querying of fields that match any value within a list:

GET /users?status=in(active,pending)

Or:

GET /users?status=active&status=pending

Nested/Relational Filters

Nested or relational filters provide a way to express complex, nested filtering logic:

GET /orders?customer.name=John&total[gt]=100

A pro tip here is to use a consistent prefix (filter[...]) to avoid parameter pollution:

GET /products?filter[price][gte]=20&filter[category]=electronics

REST Sorting Conventions

Sorting in REST typically looks like:

GET /products?sort=-price,+name

Where:

  • -price means descending
  • +name (or just name) means ascending

You can also support:

  • Multiple fields (with fallback)
  • Default sorts (e.g., createdAt DESC)
  • Custom aliases (relevance, popular, trending)

Ensure to document the available fields and directions, and reject unsupported fields with clear errors.


GraphQL: Filtering and Sorting

In GraphQL, filtering and sorting are usually done via structured input types, providing a more declarative approach:

query {
  products(filter: {
    category: "books",
    price: { gte: 10, lte: 50 }
  }, sort: {
    field: "rating",
    direction: DESC
  }) {
    id
    title
    price
  }
}

Many teams define reusable types for filtering and sorting. Filtering types might look like this:

input ProductFilter {
  category: String
  brand: String
  price: RangeFilter
  rating: RangeFilter
}

input RangeFilter {
  gte: Float
  lte: Float
}

And sorting types could look like this:

input SortInput {
  field: String
  direction: SortDirection
}

enum SortDirection {
  ASC
  DESC
}

Real-World Implementations

Shopify

Shopify's Storefront API allows filtering of collections with GraphQL filters (first, query, sortKey). Their REST Admin API uses structured query parameters and supports a wide array of filter types.

Stripe

Stripe’s API supports highly flexible filters for invoices, payments, and customers. For example:

GET /v1/invoices?customer=cus_123&status=paid&created[gte]=1680000000

Internally, Stripe parses and validates every filter parameter against a known schema — making their API robust and secure.

GitHub

GitHub’s GraphQL API enables compound filters on issues, pull requests, repositories, and users — using full type-safe query definitions and powerful filter objects.


Backend Schema Design and Performance

When designing filters, consider:

  • Which fields are safe and performant to filter on?
  • Can you expose raw DB columns? Or should you alias them?
  • Do filters map directly to SQL clauses? Or require transformation?

Here is an example of a SQL-safe filter parser using PostgreSQL:

WHERE
  category = $1 AND
  price >= $2 AND
  price <= $3
ORDER BY created_at DESC
LIMIT $4 OFFSET $5

Indexing is key. Use EXPLAIN ANALYZE to test the performance of filtered queries — especially when combining filters with sorts.


Frontend Considerations

  • Always show active filters to the user.
  • Allow filters to be changed without a full page reload (React Query, SWR, Apollo can help).
  • Store filters in query string or global state for persistence (Zustand, Redux).
  • Use dropdowns, pills, chips, toggles for a better user experience, but preserve semantic URL state.
  • Disable sort options when fields aren't available (e.g., can’t sort by price if no price filter is active).

Testing and Validation

  • Test for bad filters (sort=abcxyz).
  • Validate data types (gte=abc).
  • Enforce filter whitelisting to prevent SQL injection.
  • Write integration tests with deep combinations of filters (status + date + price + search).

Anti-Patterns to Avoid

  • Mixing filters and unrelated query params (&debug=true&filter=name).
  • Exposing raw SQL columns or data model fields directly.
  • Allowing unlimited unindexed fields in filters.
  • Making sort fields behave inconsistently (name sorts a string, but price fails).

Conclusion: Filters Are APIs, Not Just UX

Filtering and sorting are not just user experience enhancers — they’re core parts of your API contract. They shape the performance of every query, the experience of every user, and the safety of your backend under heavy load.

Design them deliberately:

  • Type your filters
  • Document everything
  • Validate aggressively
  • Always benchmark the impact

Good filters empower developers. Great filters scale the product.

Strive to build both.

Subscribe for Deep Dives

Get exclusive in-depth technical articles and insights directly to your inbox.

about

Ehsan Hosseini

Ehsan Hosseini

me [at] ehosseini [dot] info

Staff Software Engineer and Tech Lead with a track record of leading high-performing engineering teams and delivering scalable, end-to-end technology solutions. With experience across web, mobile, and backend systems, I help companies make smart architectural decisions, scale efficiently, and align technical strategy with business goals.

© 2025 Ehsan Hosseini. All rights reserved.