Learn an Unfamiliar SQL-Like Query Class, Then Solve Tasks with Its Map and Fold Actions

Read the full interview experience this question came from →

Quick 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.

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

Palantir logo
Palantir
Oct 30, 2026
hardSoftware EngineerOnsiteSoftware Engineering Fundamentals
0
0

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.

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
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 Guidance

  • 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:

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)

What This Part Should Cover Guidance

  • 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.

What This Part Should Cover Guidance

  • 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.

What This Part Should Cover Guidance

  • 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 Guidance

  • 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 Guidance

  • 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?