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
- rules-engine
- point-in-time
- scd2
- temporal-join
- window-functions
- null-handling
- pricing
- uber
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:
- Filter on status first. Rule 107 is the highest version for its group and is DISABLED -- if you rank before you filter, it wins.
- 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.
- 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.
- One winner per (product, country, tier): row_number() over a window partitioned by those three, ordered by version descending.
- 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
- Fix the Broken Pipeline
- Optimize the 100M-Row Join
- Parse Apache Logs with Regex
- Optimize Small DataFrame Join with Broadcast
- Optimize Average Rating Calculation for Products
- Deduplicate and Aggregate User Actions with Latest Session
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.