Dynamic Pricing Rules: Point-in-Time Resolution (PySpark)

PYSPARK coding challenge · Difficulty: hard · Topic: Rules Engine · +150 XP

Scenario

You are a Data Engineer at Uber on the pricing platform.

Prices change constantly — by country, by customer tier, by product — and they

change on a schedule. A price rise agreed today may not take effect until next

quarter. Business users must be able to add and schedule those changes without

anyone shipping code.

So price is not a column. Price is the result of asking a table of rules a

question: **what should we charge, for this product, in this country, at this

tier, right now?**

Your job is the "right now" part.

Table: pricing_rules

| Column | Type | Description |
| --- | --- | --- |
| `rule_id` | long | Unique rule id |
| `product` | string | Product name |
| `country` | string | ISO country code |
| `tier` | string | Customer tier |
| `currency` | string | ISO currency code |
| `price` | double | The amount to charge |
| `effective_from` | timestamp | When the rule starts applying |
| `effective_to` | timestamp | When it stops. **NULL means it never expires** |
| `version` | int | Revision number of the rule |
| `status` | string | `ACTIVE` or `DISABLED` |

The DataFrame pricing_rules is already created for you. Do not rebuild it.

You are resolving prices as of:

`python

AS_OF = '2026-10-01 00:00:00'

`

Task

Using the PySpark DataFrame API, return exactly one row per

(product, country, tier) that has a price in force at AS_OF:

| Column | Meaning |
| --- | --- |
| `product` | Product |
| `country` | Country code |
| `tier` | Customer tier |
| `rule_id` | The rule that won |
| `currency` | Its currency |
| `price` | Its price |

Ordered by product, country, tier.

Rules

1. Only ACTIVE rules count. A DISABLED rule is invisible no matter how

new it is.

2. A rule is in force when effective_from <= AS_OF < effective_to. The

window is half-open. A rule that ends exactly at AS_OF is already over; a

rule that starts exactly at AS_OF has already begun.

3. effective_to IS NULL means no expiry. Such a rule is in force forever

once it starts.

4. If more than one rule is in force for the same (product, country, tier), the highest version wins. version is a

revision number, not a date — a higher version may well have started

earlier.

5. A group with no rule in force at AS_OF produces no row. There is no

price to charge, and inventing one is worse than returning nothing.

Worked example

At AS_OF = 2026-10-01 00:00:00:

(CHALLENGE, US, standard) has two rules. Rule 102 at $3.99 runs until

2026-10-01 00:00:00; rule 103 at $4.99 starts at 2026-10-01 00:00:00.

They meet at exactly our cutoff. The window is half-open, so 102 has ended and

103 has started: the answer is 103, $4.99. Treating the end as inclusive

keeps a retired price alive; treating the start as exclusive charges a price

that was withdrawn.

(CHALLENGE, UK, standard) has rule 107 at £9.99, version 2 — newer and

dearer than rule 104 at £2.49. But 107 is DISABLED, so it does not exist.

The answer is 104, £2.49. Filtering status *after* ranking picks the

switched-off price.

(MOCK, UK, standard) has rule 113 (version 2, starting 2026-01-01) and rule

112 (version 1, starting 2026-06-01). Both are in force. Version decides, not

recency, so the answer is 113, £249.0.

(MOCK, US, standard) has one rule, 111, which ends exactly at AS_OF. It is

over, and nothing replaces it, so that group does not appear at all.

(CHALLENGE, US, premium) has rule 108 at $7.99 waiting to start on

2026-11-01. It is ACTIVE, but not yet in force. The answer is the rule

that is: 105, $5.99.

Note that six of the winning rules have effective_to = NULL. Comparing NULL

with a timestamp yields NULL, not False, so a filter written only as

effective_to > AS_OF discards every open-ended rule and returns almost

nothing.

Expected output

`text

+---------+-------+--------+-------+--------+-----+

|  product|country|    tier|rule_id|currency|price|

+---------+-------+--------+-------+--------+-----+

|CHALLENGE|     IN|domestic|    101|     INR| 50.0|
|CHALLENGE|     UK|standard|    104|     GBP| 2.49|
|CHALLENGE|     US| premium|    105|     USD| 5.99|
|CHALLENGE|     US|standard|    103|     USD| 4.99|
|     MOCK|     IN|domestic|    110|     INR|320.0|
|     MOCK|     UK|standard|    113|     GBP|249.0|

+---------+-------+--------+-------+--------+-----+

`

What this PYSPARK challenge teaches you

“Dynamic Pricing Rules: Point-in-Time Resolution (PySpark)” is a hard-level PYSPARK challenge focused on Rules Engine. Working through it gives you hands-on practice with rules-engine, point-in-time, scd2, temporal-join, window-functions, null-handling — the kind of transformation you are asked to write in real data engineering work and in technical interviews. You can solve it directly in the browser: the dataset is pre-loaded, so you write the query or DataFrame code, run it, and compare your output against the expected result immediately.

Concepts covered

How to approach it

If you get stuck, work through these steps in order before looking at a full solution — each one narrows the problem down:

  1. Filter on status first. Rule 107 is the highest version for its group and is DISABLED -- if you rank before you filter, it wins.
  2. The validity window is half-open: effective_from <= AS_OF < effective_to. Two rules in this dataset touch AS_OF exactly, one at each end, and they are there to catch the off-by-one.
  3. effective_to IS NULL means no expiry. Comparing NULL to a timestamp gives NULL, not False, so you need an explicit isNull() branch or every open-ended rule vanishes.
  4. One winner per (product, country, tier): row_number() over a window partitioned by those three, ordered by version descending.
  5. Order by version, not by effective_from. They agree for most groups here and disagree for exactly one, which is the point.

Where this comes up

Variations of this problem have been reported in interviews at Uber. Interviewers use it to check whether you can express the logic cleanly and reason about correctness on edge cases such as ties, nulls and empty groups.

How to practise it on PySpark.in

Open the challenge, write your PySpark code in the editor and press Run to execute it against the sample dataset. Submitting checks your output against every test case, including hidden ones, so you find out straight away whether your logic holds up. You can retry as often as you like, and each solved challenge adds to your XP.

Related PYSPARK challenges

Helpful resources

Frequently asked questions

Do I need to install Spark or a database to solve this?

No. The PYSPARK environment runs in your browser with the sample data already loaded, so there is nothing to install or configure.

Is this challenge free?

Yes - the problem, the sample dataset, the hints and unlimited test runs are free.

What level is it?

It is rated hard and covers Rules Engine.

Solve this challenge free on PySpark.in