# How to Implement Complex Boolean Queries Using the FlexSearch Resolver

> Master complex Boolean queries in FlexSearch. Learn to chain .and(), .or(), .xor(), and .not() methods for advanced search logic. Execute your custom queries efficiently.

- Repository: [Nextapps GmbH/flexsearch](https://github.com/nextapps-de/flexsearch)
- Tags: how-to-guide
- Published: 2026-02-23

---

**To implement complex Boolean queries in FlexSearch, initiate an unresolved search with `resolve: false`, chain the `.and()`, `.or()`, `.xor()`, or `.not()` methods to compose your logic, and finalize with `.resolve()` to execute the set operations.**

The FlexSearch library provides a **chainable Resolver API** that enables sophisticated Boolean algebra across search indices. This architecture allows you to combine multiple sub-queries using intersection, union, symmetric difference, and exclusion operations before final result resolution.

## Understanding the FlexSearch Resolver Architecture

The Resolver system operates on **unresolved result sets**—intermediate data structures that store document IDs and metadata without final ranking or formatting.

### The Resolver Class and State Management

At the core of this system is the `Resolver` class defined in [[`src/resolver.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolver.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolver.js). This class maintains:

- The current result set (document IDs)
- Pending promises for asynchronous sub-queries
- Boost values for relevance weighting
- Optional highlight and query state

When you chain Boolean methods, the Resolver stores the operation in an internal graph rather than executing immediately. The actual set algebra occurs only when you invoke `.resolve()`.

### Operator Handlers and Aggregation Logic

Each Boolean operator delegates to a generic handler in [[`src/resolve/handler.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/handler.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/handler.js), which normalizes arguments and manages cached results. The handler then routes to operator-specific aggregation functions:

- **AND**: Uses `intersect` logic from [[`src/intersect.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/intersect.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/intersect.js)
- **OR**: Uses union logic from [[`src/resolve/or.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/or.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/or.js)
- **XOR**: Uses symmetric difference logic from [[`src/resolve/xor.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/xor.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/xor.js)
- **NOT**: Uses set subtraction logic from [[`src/resolve/not.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/not.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/not.js)

## Boolean Operators Explained

The FlexSearch Resolver implements four fundamental set operations that correspond to Boolean logic.

### AND (Intersection)

The `.and()` method computes the **intersection** of the current result set and the new sub-query. Only document IDs present in **both** operands survive this operation.

```javascript
const results = index
  .search('javascript', { resolve: false })
  .and({ query: 'performance' })  // Must contain both terms
  .resolve();

```

### OR (Union)

The `.or()` method computes the **union** of sets. Document IDs present in **either** the current result set or the new sub-query are included.

```javascript
const results = index
  .search('python', { resolve: false })
  .or({ query: 'ruby' })          // Include either term
  .or({ query: 'javascript' })    // Chain multiple ORs
  .resolve();

```

### XOR (Symmetric Difference)

The `.xor()` method returns document IDs that appear in **exactly one** of the operand sets, excluding any overlap.

```javascript
const results = index
  .search('mobile', { resolve: false })
  .xor({ query: 'desktop' })       // Mobile or desktop, not both
  .resolve();

```

### NOT (Exclusion)

The `.not()` method performs **set subtraction**, removing document IDs found in the sub-query from the current result set.

```javascript
const results = index
  .search('cloud', { resolve: false })
  .not({ query: 'marketing' })      // Exclude marketing content
  .resolve();

```

## Implementing Complex Boolean Queries

The true power of the Resolver emerges when you combine operators, nest expressions, and configure execution parameters.

### Basic AND and OR Chains

Start with an unresolved search and chain operators to build Boolean expressions. The Resolver executes operations in the order chained, creating a left-to-right evaluation graph.

```javascript
const raw = index.search('apple', { resolve: false });

const result = raw
  .and({ query: 'pie' })          // Intersection: apple AND pie
  .or({ query: 'crumble' })       // Union: (apple AND pie) OR crumble
  .resolve({ limit: 20 });

console.log(result);

```

### Using XOR for Exclusive Matches

Use `.xor()` when you need mutually exclusive categories—content tagged with one term but definitively not tagged with another.

```javascript
const raw = index.search('red', { resolve: false });

const exclusive = raw
  .xor({ query: 'blue' })      // Symmetric difference: red XOR blue
  .resolve({ limit: 50 });

console.log(exclusive);

```

### Subtracting Results with NOT

The `.not()` operator functions as a filter pipeline, progressively narrowing results by excluding unwanted matches.

```javascript
const raw = index.search('technology', { resolve: false });

const filtered = raw
  .not({ query: 'advertisement' }) // Exclusion: technology NOT advertisement
  .resolve({ limit: 30 });

```

### Nested Boolean Expressions and Async Pipelines

For complex logic, pass nested objects containing `and`, `or`, `xor`, or `not` keys. The Resolver recursively processes these subgraphs. Combine with `async: true` for parallel execution of independent sub-queries.

```javascript
const resolver = new Resolver({
  index,
  query: 'javascript',
  async: true                // Execute sub-queries in parallel
});

resolver
  .and({ query: 'performance', boost: 2 })
  .or({
    and: [
      { query: 'web' },
      { query: 'worker' }
    ]
  })
  .not({ query: 'legacy' })
  .limit(100)
  .resolve();

resolver.await.then(result => {
  console.log('Async complex query result:', result);
});

```

### Caching Sub-Queries for Performance

Enable `cache: true` on individual sub-queries to store intermediate results and avoid redundant computation when the same query appears multiple times in a complex graph.

```javascript
const raw = index.search('node', { resolve: false, cache: true });

const result = raw
  .and({ query: 'express', cache: true })  // Cached sub-query
  .or({ query: 'koa' })
  .resolve();

```

## Key Source Files and Implementation Details

Understanding the underlying implementation helps debug complex queries and optimize performance.

| File | Purpose | Key Implementation Detail |
|------|---------|---------------------------|
| [[`src/resolver.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolver.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolver.js) | Core `Resolver` class, state management, async orchestration | Implements `execute()` method that processes pending promises before running set operations |
| [[`src/resolve/handler.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/handler.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/handler.js) | Argument normalization and sub-query execution | Normalizes nested objects and manages cached result retrieval |
| [[`src/resolve/and.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/and.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/and.js) | Intersection logic | Delegates to `intersect` function for set intersection |
| [[`src/resolve/or.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/or.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/or.js) | Union logic | Merges result arrays while preserving relevance scores |
| [[`src/resolve/xor.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/xor.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/xor.js) | Symmetric difference | Filters IDs appearing in both sets |
| [[`src/resolve/not.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/resolve/not.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/resolve/not.js) | Set subtraction | Removes operand IDs from current result set |
| [[`src/intersect.js`](https://github.com/nextapps-de/flexsearch/blob/main/src/intersect.js)](https://github.com/nextapps-de/flexsearch/blob/master/src/intersect.js) | Low-level set intersection algorithms | Optimized for sorted integer arrays (document IDs) |

## Summary

- **Start unresolved**: Begin every complex query with `resolve: false` to obtain a `Resolver` instance instead of final results.
- **Chain operators**: Use `.and()`, `.or()`, `.xor()`, and `.not()` to build Boolean expressions; each returns the Resolver for further chaining.
- **Nest expressions**: Pass objects containing `and`, `or`, `xor`, or `not` keys to create nested Boolean subgraphs.
- **Configure execution**: Apply `async: true` for parallel sub-queries, `cache: true` to reuse intermediate results, and `boost` to adjust relevance weighting.
- **Resolve finally**: Call `.resolve()` (or await the promise) to execute the set operations defined in `src/resolve/*.js` and return the final document list.

## Frequently Asked Questions

### How do I combine AND and OR operators in the same query?

Chain the methods sequentially or use nested objects. For sequential chaining, `index.search('a', { resolve: false }).and({ query: 'b' }).or({ query: 'c' })` produces `(a AND b) OR c`. For nested logic, pass an object to `.or()` containing an `and` array: `.or({ and: [{ query: 'x' }, { query: 'y' }] })` to create `a OR (x AND y)`.

### What is the difference between XOR and NOT in FlexSearch?

**XOR** (exclusive or) returns documents that match exactly one of the two queries, removing any overlap. **NOT** subtracts one set from another, keeping only documents from the original result set that do not appear in the negated query. Use XOR when you want mutually exclusive categories; use NOT when filtering out unwanted matches from an existing set.

### Can I execute boolean queries asynchronously?

Yes. Initialize the Resolver with `async: true` or enable `async` on individual sub-queries. When async mode is active, the Resolver stores pending promises and executes sub-queries in parallel, deferring set operations until all promises resolve. Access the final result via `resolver.await.then(...)` or by awaiting `resolver.resolve()` if using native async/await syntax.

### How does caching improve boolean query performance?

When you pass `cache: true` to a sub-query, the Resolver stores the intermediate result set after the first execution. If the same query appears again in a complex boolean graph—whether through repeated chaining or nested references—the Resolver reuses the cached IDs instead of re-running the search. This significantly reduces redundant computation in deeply nested or repetitive boolean expressions.