Inspiration

Getting to UBC is hard. From Richmond or Burnaby, the bus can take over an hour each way. An Uber costs around $27. Meanwhile, students drive to campus every day with empty seats, often from the same neighbourhoods at the same times.

Carpooling already makes sense. The problem is finding people. Group chats and posters don't work, because you need someone on your route, on your days, at your time, every week. We wanted to make that match automatic, and make it safe and predictable enough that students would actually rely on it.

What it does

Hopped matches UBC students into weekly carpool pods: one verified student driver plus a few riders who live along the route and need to be on campus at the same time.

  • Onboarding in under a minute. Pick driving or riding, enter home, choose your days and arrival time for each day. Drivers add their car and when they head home.
  • Pods for you. Riders get pods ranked by fit. Before joining, they preview the exact route through every pickup, their own pickup and arrival time for each day, the time saved vs transit (e.g. ~1 hr saved: bus 1h 31m → 33m), and the price.
  • Live matching. A rider asks to join and the driver approves instantly.
  • Per-day control. For each weekday: I'm in or Don't need a ride, for the ride to campus and the ride home.
  • Live trips. The driver taps Start pickup. Riders watch the car on the map, get "Alex is here", and the trip ends by itself on arrival.
  • Automatic payment. The rider's wallet is charged when the trip ends and the driver is paid. Nobody owes anyone.
  • Trust. UBC email sign-up, driver licence and car photo reviewed by a person, and only neighbourhoods are ever shown, never addresses.

Every ride is priced up front:

$$ \text{price} = \big(\$5_{\text{driver}} + \$2_{\text{company}} + \$0.15 \times d_{\text{km}}\big) \times 1.05 $$

The driver keeps the driver fee and the gas. A 10 km ride costs about $9, versus roughly $27 on Uber.

How we built it

  • Frontend: Next.js 14 (App Router), React, TypeScript and Tailwind, built as an installable PWA for phones.
  • Backend: TypeScript API routes running as serverless functions on Vercel. Every write goes through the server.
  • Database: Supabase Postgres with row-level security, Supabase Auth for UBC email sign-up, Realtime for live updates and GPS, and Storage for photos.
  • Maps: Google Maps JS for the map, Directions for driving routes, detours and transit times, and Places for address search.
  • Notifications: web push with an email fallback, plus a nightly Vercel cron that asks each driver "Driving tomorrow?".

Matching runs in two stages. First, a fast filter with no API calls. It checks that the rider's home is within $3.5$ km of the driver's cached route, that they share at least one day, and that the driver arrives between $0$ and $20$ minutes before the rider needs to be there. Only the shortlist goes to Google, which checks that the detour is at most $8$ minutes and compares against transit. Candidates are then scored:

$$ \text{score} = 10 \cdot \text{shared days} + 3\big(1 - \tfrac{\text{early gap}}{20}\big) + 3\big(1 - \tfrac{\text{detour}}{8}\big) + 2 \cdot \tfrac{\min(\text{saved},\,45)}{45} + \text{same faculty} $$

Payments run inside Postgres as a single locked function, so a ride can never be charged twice, even if two requests arrive at the same instant.

Challenges we ran into

  • The live car kept freezing. We first shared GPS with Supabase presence. By measuring it, we found it cuts a client off after about 6 updates in 30 seconds, so the car stopped moving about 15 seconds into every trip. We switched to Realtime broadcast, throttled to one update a second, with a heartbeat so late joiners still see the car.
  • Double charges. A rider and a driver can both end the same trip, and retries happen. Checking "already paid?" in application code has a race condition. We moved the charge into a Postgres function that locks the ride, so it runs all or nothing, at most once, backed by a unique index.
  • Times that add up. Commutes are about times, and early versions showed numbers that didn't match: "pickup 8:25, arrive by 9:00" for a 27-minute ride. We rebuilt time handling so riders see their own arrival (pickup + time in the car), per-day times shift pickups correctly, and time saved is rounded honestly.
  • Privacy on a map. Drawing a driver's route would reveal where they live. The public route line now starts about 400 m along the route, and only neighbourhoods are shown.
  • Google API cost and latency. Checking every rider against every driver with Google would be slow and expensive, which is why we built the fast filter first.

Accomplishments that we're proud of

  • A complete loop that actually works: sign up, onboard, match, preview, join, approve, ride-home tap-in, live trip, pickup, arrival and automatic payment.
  • Payments that are correct by design. We tested three identical "trip complete" requests at the same instant and got exactly one charge.
  • A demo recorded from the real app. Our video was made by scripts driving two phones through the real product on our real database, not mockups.
  • Solid fallbacks. If Google fails, matching falls back to estimates. If push isn't available, important notices go by email. If a driver is late or doesn't show, riders are told and can find a backup pod.

What we learned

  • Measure before you trust a service. Our live tracking bug only made sense once we measured the rate limit ourselves.
  • Put invariants where they can't be skipped. "Charge once" belongs in the database, not in code that two requests can run at the same time.
  • Cheap first, expensive second. A little geometry up front removed most of the Google calls and made matching fast.
  • Consistency is a feature. Users notice when numbers don't add up. Getting times, prices and time saved to agree everywhere made the product feel trustworthy.
  • Narrow beats broad. We built on-demand rides too, then cut them. Weekly pods are a clearer product that works even with only a few drivers.

What's next for Hopped

  • Real payments. Payouts through a payment provider, plus the licensing and commercial insurance BC requires when drivers earn a fee.
  • Live rides home. Riders can already plan and tap in. Next is live tracking and auto-pay for the afternoon ride.
  • Invite links, so drivers can bring friends straight into their pod.
  • Other campuses, starting with SFU and Langara, where the commute problem is the same.
  • Smarter matching that learns from real trip times and traffic.

Built With

Share this project:

Updates

Submission history