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.
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'
// 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' } }]
eq/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.field: '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.{ op: 'and', conditions: [] } matches every row; { op: 'or', conditions: [] } matches none. This mirrors Array.prototype.every([]) and Array.prototype.some([]).comparator or unknown op should throw. Silent false hides typos.rows or the query. Preserve input order.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.