Bikes Before Rush Hour

A hard Pipeline Design interview practice problem on DataDriven. Design the pipeline on the canvas: sources, transforms, storage and checks, then get feedback on it.

Domain
Pipeline Design
Difficulty
hard
Seniority
L5

Problem

Trip history lands once a day as files from about 40 city operators, each in its own schema, and every hourly station forecast depends on scheduling those files through ingest, cleaning and feature build before it runs. Design the pipeline from raw ingestion to a model-ready feature store that the forecast reads.

Bikes in, bikes out. The city needs to predict demand.

Business requirements

Worked solution and explanation

What this problem really is

Under the demand-forecast costume, this is two clocks feeding one feature store, plus a filter that has to drop rows from the features without deleting them. Trip history lands once a day as operator files in forty different shapes. Dock availability changes every minute and is worthless once it goes stale. The trap is pulling both through the same nightly batch: the supply feature ends up a day old, and the model pre-positions bikes against docks that emptied hours ago.


Building it

Step 1: Schedule the daily trip files

The operator files are the slow clock, so an orchestrator (Airflow here) owns it. Each hour it picks up whatever files have arrived, lands them untouched in object storage, runs the quality check and kicks off feature build before the forecast fires. Normalizing the forty operator schemas belongs in feature build, after the raw copy exists, so a mapping bug can be fixed and replayed from raw.

Step 2: Stream the dock supply

Dock availability is the fast clock: a snapshot an hour old tells the model a full dock is empty. The feed comes in on Kafka, and a Flink job keeps current supply per station under a minute old, writing into the same feature store. Nothing on this path waits for the trip schedule.

Step 3: Filter corrupt trips between raw and features

About two percent of trips are junk: negative durations from dock clock skew and zero-distance test rides. The raw layer keeps them for audit and reprocessing, so the filter sits after raw_trips and before trip_feature_build. Raw holds everything, the gate drops the bad rows, and only clean trips become features.

The shape that fits

Two freshness tiers into one store: the scheduled trip path lands raw, passes `corrupt_trip_filter` and builds features hourly; the dock feed streams through `dock_supply_snapshot`; the forecast reads `feature_store` on the hour.
  • trip_files (source)
  • orchestrator (transform, Airflow)
  • raw_trips (storage, object storage)
  • corrupt_trip_filter (quality gate, Great Expectations)
  • trip_feature_build (transform, Spark); freshness: < 1h
  • dock_availability_feed (source, Kafka)
  • dock_supply_snapshot (transform, Flink); freshness: < 1min
  • feature_store (storage, warehouse)
  • demand_forecast (consumer)
  • trip_files -> orchestrator
  • orchestrator -> raw_trips
  • raw_trips -> corrupt_trip_filter
  • corrupt_trip_filter -> trip_feature_build
  • trip_feature_build -> feature_store
  • dock_availability_feed -> dock_supply_snapshot
  • dock_supply_snapshot -> feature_store
  • feature_store -> demand_forecast
Filter during ingest
This saves one hop, and it silently breaks retention: the corrupt rows never reach storage. When the definition of corrupt changes, there is nothing to reprocess.
Filter after the raw landing
Raw is a faithful copy of what operators sent. The quality gate reads from it and passes only clean trips forward, so the features stay clean and the history stays complete.

Common follow-up questions