# What the Day Left Open

> Every fill moves a position. Reconstruct where the book settled by the close.

Canonical URL: <https://datadriven.io/problems/net-positions-ledger>

Domain: Python · Difficulty: medium · Seniority: junior

## Problem

A brokerage's position-keeping service replays the day's executed `trades` to reconstruct each account's holdings. Each trade names a `ticker`, a `side` of `buy` or `sell`, and a share count, where buys add to a position and sells subtract from it. Return the net shares held per ticker, dropping any ticker whose position was fully closed out by the close, and keeping the tickers that went net short.

## Worked solution and explanation

### Why this problem exists in real interviews

This is a signed group-and-sum wearing a portfolio costume. The skill being probed is whether you can fold the `side` field into the sign of the quantity BEFORE you accumulate, and whether you understand that a fully closed position is not a zero you report, it is a key you drop. Anyone can tally shares per ticker. The two things that separate candidates are the sign convention (sells are negative) and the final filter (net zero disappears, net short stays). Get the filter backwards and you either leak closed-out tickers into the book or, worse, throw away the short positions the risk desk most wants to see.

---

### Break down the requirements

#### Step 1: Normalize each trade to a signed quantity

A buy of 40 and a sell of 40 should cancel. The cleanest way is to make the quantity itself carry the direction: keep shares as-is for a buy, negate it for a sell. Now every trade is just a number to add, and the buy/sell distinction never has to be revisited downstream.

#### Step 2: Accumulate per ticker in one pass

Use a dict keyed by ticker and add the signed quantity with positions.get(ticker, 0) + qty. One pass, O(1) per trade. Reaching for collections.Counter or defaultdict(int) is equally fine; the point is that you never scan the list more than once.

#### Step 3: Drop the closed-out positions at the end

A ticker that nets to zero was fully closed during the day and must not appear in the result. Filter it out in a final dict comprehension: keep entries where net != 0. Do this AFTER accumulating, not during, because a position can cross zero mid-day and reopen. Negatives are real net-short positions and stay.

---

### The solution

**Signed accumulate, then filter zeros**

```python
def net_positions(trades):
    positions = {}
    for trade in trades:
        qty = trade['shares']
        if trade['side'] == 'sell':
            qty = -qty
        positions[trade['ticker']] = positions.get(trade['ticker'], 0) + qty
    return {ticker: net for ticker, net in positions.items() if net != 0}
```

> **Complexity**
>
> Time is O(n) over the trades with O(1) average dict access and append; the closing filter is O(t) over distinct tickers, which is at most n. Space is O(t) for the position map. A trading day for one account is thousands of fills at most, so this is effectively instant; the same shape feeds a streaming aggregator at firm scale, where the only change is that the dict lives in a stateful operator instead of memory.

> **Interviewers Watch For**
>
> Whether you encode the sign once at read time instead of branching again inside the accumulation, and whether you ask what to do with a position that nets to zero before you assume. Strong candidates also confirm the short-position behavior out loud: a negative net is not a bug to clamp at zero, it is a valid result the prompt explicitly wants kept.

> **Common Pitfall**
>
> Filtering with 'if net > 0', which silently deletes every net-short ticker along with the closed-out ones. The spec wants only EXACTLY zero dropped, so the test must be 'net != 0'. The second common miss is filtering inside the loop, which breaks the moment a ticker is sold to flat and then bought again later in the day.

---

## Common follow-up questions

- How would you also return the total traded volume per ticker (shares bought plus shares sold, ignoring direction) alongside the net position? _(Tests accumulating two aggregates in one pass, one signed and one absolute, without a second scan.)_
- The fills now arrive as an unbounded stream rather than a finished list. How does your design change? _(Tests moving from a batch dict to a stateful operator and deciding when net-zero tickers are emitted or suppressed.)_
- What if a trade can carry an unknown side value or a negative share count from a bad upstream feed? _(Tests input validation and whether bad records are skipped, raised on, or quarantined rather than silently corrupting the book.)_

## Related

- [All practice problems](https://datadriven.io/problems)
- [Mock interview mode](https://datadriven.io/interview/net-positions-ledger)
- [Python Interview Questions](https://datadriven.io/python-interview-questions)
- [Data Engineering Interview Prep Guide](https://datadriven.io/data-engineer-interview-prep)
- [Daily Challenge](https://datadriven.io/daily)

---

Source: DataDriven (https://datadriven.io). 100% free data engineering interview prep. Live code execution against Postgres 16, Python 3.11, and Spark sandboxes. No paywall, no premium tier, no signup gate.