All questions

Data Selection

Premium

Data Selection

You have an array of row objects (think: a list of users, orders, log lines) and a separate object describing which rows to keep. The description is a small, declarative tree — it mixes per-field comparisons (age >= 18, country === 'US') with logical operators (and, or, not) that can nest arbitrarily. Implement select(rows, query) so it returns a new array of rows that match. This is the same idea as a SQL WHERE clause, a MongoDB find filter, or lodash's _.filter with a richer spec — a query language for an in-memory dataset.

Signature

select(rows: object[], query: Query): object[]

type Query =
  | { op: 'and'; conditions: Query[] }
  | { op: 'or';  conditions: Query[] }
  | { op: 'not'; condition: Query }
  | { field: string; comparator: Comparator; value: unknown }

type Comparator =
  | 'eq' | 'neq'
  | 'gt' | 'gte' | 'lt' | 'lte'
  | 'in' | 'nin'
  | 'contains' | 'startsWith'

Examples

// 1. A single comparator — keep rows where age is at least 18.
select(
  [{ name: 'Ana', age: 25 }, { name: 'Bo', age: 15 }],
  { field: 'age', comparator: 'gte', value: 18 },
);
// → [{ name: 'Ana', age: 25 }]
// 2. AND / OR / NOT — keep US adults who are not banned.
select(rows, {
  op: 'and',
  conditions: [
    { field: 'age', comparator: 'gte', value: 18 },
    { field: 'country', comparator: 'eq', value: 'US' },
    { op: 'not', condition: { field: 'banned', comparator: 'eq', value: true } },
  ],
});
// 3. Nested logical — admins or owners in California.
select(rows, {
  op: 'and',
  conditions: [
    {
      op: 'or',
      conditions: [
        { field: 'role', comparator: 'eq', value: 'admin' },
        { field: 'role', comparator: 'eq', value: 'owner' },
      ],
    },
    { field: 'address.state', comparator: 'eq', value: 'CA' },
  ],
});
// 4. Dot-path field access — read nested fields on each row.
select(
  [{ name: 'Ana', address: { city: 'NYC' } }, { name: 'Bo', address: { city: 'LA' } }],
  { field: 'address.city', comparator: 'eq', value: 'NYC' },
);
// → [{ name: 'Ana', address: { city: 'NYC' } }]

Notes

  • Comparatorseq/neq are strict ===/!==. gt/gte/lt/lte use JS >/>=/</<= (numeric on numbers, lexicographic on strings). in/nin expect value to be an array. contains works on strings AND arrays. startsWith is strings only.
  • Dot pathsfield: 'a.b.c' walks row.a.b.c. If any intermediate segment is null or undefined, the lookup returns undefined (no throw). A literal dot inside a key ({ "a.b": 1 }) is not addressable through this syntax — same limitation as lodash _.get.
  • Empty logical groups — vacuous truth applies. { op: 'and', conditions: [] } matches every row; { op: 'or', conditions: [] } matches none. This mirrors Array.prototype.every([]) and Array.prototype.some([]).
  • Errors — unknown comparator or unknown op should throw. Silent false hides typos.
  • Purity — return a new array; do not mutate rows or the query. Preserve input order.
  • Out of scope — no SQL-style projection (you return the whole row), no joins, no aggregation. The query is pure filter.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium