Introduction
There's something about pickup sports that feels increasingly rare in our digital age: real, spontaneous community. No algorithms deciding who you meet, no endless scrolling through feeds. Just you, a basketball, and whoever shows up at the park.
That's the vision behind Pickup. Not another app where you're the product, but a tool to make real-world connection accessible. A way to find that Tuesday night volleyball game at the park down the street, or discover which courts have the best basketball runs on weekends.
What I didn't expect was just how complex building a "simple" sports coordination app would turn out to be.
The Unexpected Complexity of Social Apps
When I started Pickup, I imagined it as straightforward: users, games, parks. Maybe some chat functionality. How hard could it be?
Turns out, social apps are deceptively complex. The moment you introduce relationships between entities, the complexity multiplies exponentially. Here's what I mean:
- Users have relationships with other users - friendships, blocked users, pending requests
- Users have relationships with games - joining, leaving, hosting, getting kicked
- Users have relationships with parks - favorites, preferences, arrival notifications
- Games have relationships with parks - location data, availability, capacity
- Chats have relationships with everything - game chats, park chats, direct messages, series chats
- Series tie multiple games together - recurring events with their own context and continuity
This was one of those humbling moments in software development where you realize the iceberg is much bigger than you thought. Each relationship required careful consideration of state management, permissions, notifications, and edge cases. The data model ended up being the most critical architectural decision of the entire project. Get it wrong early, and you're stuck refactoring hundreds of files later. Trust me, I know.
Core Features: Building the Social Layer
At the heart of Pickup is communication. Finding a game is one thing, but coordinating with people is what makes it work. The chat system became surprisingly sophisticated:
Park Chats
Every park has a community chat. Think of it as a digital bulletin board for that location. Someone asks "anyone up for tennis at 6?", another person responds, and suddenly you've got a game. These chats are persistent and location-based, creating micro-communities around physical spaces.
Game Chats
Once you've joined a game, you get access to the game-specific chat. This is where the coordination happens: "Running 10 mins late", "Bringing an extra ball", "Should we switch to the covered courts?" These chats are ephemeral by design - they exist for the lifetime of the game and then archive.
Series Chats
For recurring games - like your weekly Thursday night basketball - series chats provide continuity. The same group, the same banter, week after week. This ended up being crucial for building actual community rather than just one-off interactions.
Group Chats
Beyond location and game-based chats, you can create private group chats with your friends. Planning a multi-park basketball run across the city? Group chats give you a dedicated space for your regular playing partners, separate from the public park chats and temporary game chats.
Reviews and Reputation
After playing together, users can leave reviews for each other. It's not about ratings or scores - it's about building trust in the community. "Great teammate, always on time" or "Friendly player, good energy" help people know who they're playing with. The system is opt-in and lightweight, designed to highlight good community members without creating toxicity around arbitrary numbers.
Real-Time Coordination
All of this chat infrastructure had to be real-time. Nobody wants to refresh to see if the game is still on. Firebase's Firestore handled this beautifully with its snapshot listeners, though managing subscription lifecycles across platform boundaries proved tricky.
Maps: Different Platforms, Different Worlds
One of the more interesting technical challenges was map integration. On the surface: just show pins on a map. In practice: two completely different implementations.
Android: Google Maps
On Android, we use Google Maps with clustering for performance. When you have dozens of parks in view, individual markers get overwhelming fast. The clustering algorithm groups nearby parks, expanding as you zoom in. The implementation lives in MapTabContent.android.kt, using the Maps Compose library which gives us that nice declarative API.
iOS: MapKit
iOS gets the native MapKit treatment (MapTabContent.ios.kt). Apple's maps look and feel different - more refined in some ways, though the annotation customization APIs are more restrictive. The expect/actual pattern in Kotlin Multiplatform made this split implementation surprisingly clean.
Both implementations share the same business logic for filtering parks, calculating distances, and managing selected locations. Only the actual rendering diverges. This is Kotlin Multiplatform at its best: write the complex stuff once, customize the UI per platform.
Privacy and Data Ownership
Building a social sports app means handling sensitive data - location, contacts, real-time whereabouts. Privacy isn't an afterthought; it's fundamental to the platform.
Location Data
Pickup only uses location data when you explicitly choose to share it. Arrival notifications are opt-in, and your precise location is never shared with other users - just the fact that you've arrived at the park. Location data is processed on-device first, and only the minimum necessary information is sent to the server.
Chat and Communication
All chat messages are encrypted in transit using TLS. While messages aren't end-to-end encrypted (we need to support features like offline message delivery and cross-device sync), they're stored securely and never sold or shared with third parties. You control who can message you, and blocking/reporting tools are built in from day one.
Privacy Controls
You control your visibility on the platform. Want to use Pickup but not be searchable by strangers? Toggle off discoverability. Don't want random friend requests? Turn that off too. Every game can be set to private - invite only, no public visibility. Games can also require approval to join, giving hosts control over who participates. You can participate in pickup sports without broadcasting your presence to everyone.
Content Moderation
Every piece of user-generated content - messages, game descriptions, user profiles - runs through OpenAI's moderation model before it's saved. This catches most problematic content automatically. Severe violations get flagged immediately and ping my Slack instance directly, so I can respond fast. The built-in reporting system also notifies me instantly when users flag content or behavior. It's a small app right now, so this manual oversight works. As it scales, the automated moderation provides a first line of defense.
No General Ads, No Data Selling
Pickup's business model is simple: optional paid features like leagues and tournaments. Your data is never sold to advertisers. Your game history, chat logs, and preferences exist to make your experience better - not to build an advertising profile. While we might explore sport-specific ads in the future (think local sports leagues, equipment deals), there will never be generic banner ads or tracking pixels from third-party ad networks.
Features I'm Proud Of
Some features came together better than expected:
Arrival Notifications
The ParkArrivalManager monitors your location and notifies your game chat when you arrive at the park. Small feature, but it feels magical when you pull into the parking lot and your phone buzzes with "Brandon has arrived!"
Smart Game Recommendations
The recommendation system considers your sport preferences, favorite parks, skill level, and typical playing times. It's not ML-powered or anything fancy - just good old-fashioned filtering and scoring - but it works surprisingly well.
Live Game Scoring
Every sport has its own scoring system, and Pickup supports them all. Track sets and points in real-time for roundnet, volleyball, pickleball, or any other game. The scoring interface handles team assignments, supports multiple concurrent games, and automatically ends games after a timeout period. It keeps a full match history, making it easy to review past games and track your progress. Way more fun than trying to remember the score mid-game.
Achievements System
Gamification done tastefully. "First Game", "Regular Player" (10 games), "Legend" (100 games). Purely cosmetic, no pay-to-win nonsense, just fun milestones to mark your journey.
Deep Linking and Deferred Deep Linking
Sharing a game works seamlessly across the entire user journey. When someone shares a game, the link takes you directly into that game - even if you don't have the app installed yet. The deferred deep linking flow handles everything: install from the app store, complete onboarding, and land right in the shared game. No copying links, no "now go find the game manually" friction. Just tap, install, onboard, and you're in.
Join Game from PDF
Every game can be exported as a shareable PDF with all the details: time, location, participants, QR code for quick access. Perfect for posting on bulletin boards at gyms or community centers. Scan the code, and you're in the game. To make this work, I built kmpdf, a Kotlin Multiplatform PDF creation library. Because sometimes you need to take digital coordination into the physical world.
Marketing: The Final Boss
Here's the uncomfortable truth: building the app was the easy part. Getting people to actually download and use it? That's the real challenge. And honestly, the marketing strategy is still very much TBD.
I can architect a complex social platform, wrangle real-time data sync across platforms, and build custom PDF libraries - but getting strangers to trust a new social app from an unknown developer? That's a whole different game. One where "just build it and they'll come" is about as effective as showing up to an empty basketball court and hoping for a 5v5.
The app store is a graveyard of beautifully engineered apps that nobody uses. Pickup could have the cleanest architecture and the smoothest UX, but if nobody knows it exists, it's just an expensive side project. The real question isn't "can we build cool features?" - it's "how do we convince the pickup basketball crowd at the local park to switch from their group chat to our app?"
So yes, marketing strategy is TBD. And it's probably going to be harder than everything else combined. If you have ideas - or you're reading this and you actually organize pickup games - please reach out. I can debug a race condition in distributed state management, but viral growth strategies? That's magic I haven't figured out yet.
Lessons Learned
Building a social sports app taught me more than I expected:
Social Features Are a Multiplier
Every social feature you add multiplies complexity. Chat, friends, blocking, reporting - each one introduces edge cases and state management challenges. Don't underestimate this. What seems like "just add chat" is actually "add chat infrastructure, message state, read receipts, typing indicators, push notifications, moderation tools, and 47 edge cases."
Data Models Matter More Than You Think
Get your data model right early or suffer later. Seriously. I refactored the game-park-user relationship three times before finding something that scaled. Each refactor meant touching dozens of files. Design your models with relationships in mind from day one.
Real-Time Adds Delight
The difference between polling for updates and real-time listeners is night and day for user experience. Seeing messages appear instantly, watching game rosters update live - this makes the app feel alive. Worth the added complexity.
Community Is the Product
At the end of the day, Pickup isn't really about the features or the tech stack. It's about getting people to the park. The best technical decision I made was to keep the focus on real-world connection rather than in-app engagement metrics. If someone uses Pickup for 2 minutes and then plays basketball for 2 hours, that's a win.
Future Monetization: City Leagues and Beyond
Here's the thing about sports apps: the users are already spending money on sports. Balls, shoes, court rentals, leagues. The question isn't whether there's money in the market, but how to provide real value worth paying for.
City Leagues
The main monetization plan is organized leagues. Think of it as pickup sports with structure: regular schedules, standings, playoffs, championships. Cities could host basketball leagues, volleyball tournaments, tennis ladders. We provide the platform, handle registration and payment, coordinate schedules, and track standings. Cities get a cut, we get a cut, players get organized competition.
Clubs and Tournaments
Beyond leagues, there's appetite for private clubs and one-off tournaments. A group of pickleball players wants to run a weekend tournament? We can handle the bracket, scheduling, and coordination. A tennis club wants to manage their membership and court bookings? Built-in functionality.
Affiliate Marketing
Sports equipment is a natural fit. When you're in a roundnet chat and someone asks "what ball do you use?", imagine being able to share affiliate links right in the app. No intrusive ads, just helpful recommendations that support the platform. Same for shoes, nets, bags - anything players actually want.
The key is that none of this compromises the core experience. Finding and joining pickup games will always be free. The monetization comes from enhanced features that make sense for people already invested in the community.