Learn an Unfamiliar SQL-Like Query Class, Then Solve Tasks with Its Map and Fold Actions
Company: Palantir
Role: Software Engineer
Category: Software Engineering Fundamentals
Difficulty: hard
Interview Round: Onsite
This round tests how quickly you can learn code you have never seen. You are handed a short class that implements a small SQL-like query language. First, read it and explain to the interviewer how it works. Then use the class's own operations to solve small tasks: some with an action that maps the query's rows into a data structure, and some with an action that works like a for loop, accumulating a result over the rows. The coding portion lasted about 30 minutes.
The actual code and tasks were not reported. The class and table below are an illustrative stand-in with the same two kinds of action; treat them as the code you were handed.
```python
class Query:
"""A tiny SQL-like query over a list of rows; each row is a dict."""
def __init__(self, rows, steps=()):
self._rows = rows
self._steps = tuple(steps)
# Transformations: each returns a new Query and runs nothing.
def where(self, predicate):
return Query(self._rows, self._steps + (("where", predicate),))
def select(self, *columns):
return Query(self._rows, self._steps + (("select", columns),))
def _run(self):
for row in self._rows:
out = row
for kind, arg in self._steps:
if kind == "where":
if not arg(out):
break
else:
out = {c: out[c] for c in arg}
else:
yield out
# Actions: each one runs the query.
def map_to(self, make, insert):
"""Build a data structure: make() creates it, insert(ds, row) adds one row."""
ds = make()
for row in self._run():
insert(ds, row)
return ds
def fold(self, initial, step):
"""Accumulate over the rows: acc = step(acc, row) for each row, in order."""
acc = initial
for row in self._run():
acc = step(acc, row)
return acc
```
```python
orders = [
{"order_id": 1, "customer": "acme", "amount": 120, "status": "completed"},
{"order_id": 2, "customer": "globex", "amount": 80, "status": "cancelled"},
{"order_id": 3, "customer": "acme", "amount": 45, "status": "completed"},
{"order_id": 4, "customer": "initech", "amount": 300, "status": "completed"},
{"order_id": 5, "customer": "globex", "amount": 60, "status": "completed"},
{"order_id": 6, "customer": "acme", "amount": 200, "status": "cancelled"},
]
```
### Clarifying Questions
- May I add small helper functions, or must every task be a single chain of the class's operations?
- Should results keep the order of the input rows, and how should ties be broken?
- Can one query object be run more than once, and can the source rows change between runs?
- What should a task return when no row matches?
### Part 1 — Read the code and explain it
Explain what happens when a query such as `Query(orders).where(...).select(...)` is built, and what happens when one of its actions is called: what each step does to a row, in which order the steps run, and which rows `_run` yields. Then predict the result of each expression without running it:
```python
Query(orders).where(lambda r: r["amount"] > 100).select("order_id").fold(0, lambda n, r: n + 1)
Query(orders).select("customer").where(lambda r: r["status"] == "completed").fold(0, lambda n, r: n + 1)
```
```hint Trace one row
Follow one row that a `where` step rejects and one that it accepts through `_run`, and note exactly when the body of `_run` starts executing.
```
#### What This Part Should Cover
- What building a query does, compared with what an action does
- The per-row control flow in `_run`, including how a rejected row is skipped and what a `select` step produces
- How the chained order of steps limits what a later step can see, with both predictions explained
- What running an action twice, or mutating a yielded row, does to the source
### Part 2 — Tasks with `map_to`
Using `map_to`, together with `where` and `select` as needed, build:
1. A dict that maps each customer to the list of ids of their completed orders, in input order.
2. The set of customers that have at least one cancelled order.
```hint What happens to the insert result
Look at what `map_to` does with the value `insert` returns, and choose container methods that change the container in place.
```
#### What This Part Should Cover
- Correct `make` factories and in-place `insert` callbacks
- Filtering with `where` before the structure is built
- Grouping into lists in input order, with the expected results on the sample table and when no row matches
### Part 3 — Tasks with `fold`
Using `fold`, compute:
1. The total amount of the completed orders.
2. A dict that maps each customer to the total amount of their completed orders.
```hint The accumulator is the answer so far
Decide what `acc` holds after each row, and make sure `step` hands it back for the next row.
```
#### What This Part Should Cover
- A suitable initial value and a `step` that returns the new accumulator
- Accumulating into a dict, and the cost of mutating versus copying the accumulator
- The expected results on the sample table, and the result when no row matches
### What a Strong Answer Covers
- An accurate, concise explanation of the code, anchored by a concrete trace, before any new code is written
- Idiomatic use of the class's own operations rather than a hand-written loop over `orders`
- Correct results on the sample table, with edge cases such as no matching rows or a column removed by `select`
- Thinking aloud and recovering quickly after a hint, since the round measures how fast new code is absorbed
### Follow-up Questions
- Add a `group_by(column)` action that returns a dict of lists. Can you write it with only `map_to`, and with only `fold`?
- `fold` can build anything `map_to` builds. Why might the library still offer both?
- Add a `limit(n)` transformation. How does `_run` keep count, and how does it stop reading the source early?
- The rows now come from a file with a billion lines, read as a stream. Which parts of the class still work, and which of your answers would not?
Overview: Read a short, unfamiliar SQL-like query class and explain how its chained filter and projection steps run, then solve small data tasks with its two actions: one maps rows into a data structure and one accumulates over rows like a loop. It tests fast code comprehension and fluent use of callbacks.
Read the full Palantir Software Engineer interview experience this question came from