Inspiration
Language learning apps like Duolingo gamify vocabulary drills, but they rarely push you into the most effective practice of all - real conversation. We've all experienced the gap between knowing a language on paper and actually speaking it under pressure. The moment someone responds to you in a foreign language, everything changes. We wanted to build something that closes that gap: no scheduling, no tutors, no waiting. Just click, get matched, and start talking.
What it does
LinguaLink matches language learners in real-time for live video conversations. You sign up, complete a short onboarding where you select your native language, the language you're learning, and your proficiency level (via CEFR or Duolingo score), and then you can drop into a matchmaking queue. Within seconds you're connected with another learner studying the same language at a similar skill level. You practise together (with assisted conversation prompts), skip to a new partner whenever you're ready, and build fluency through genuine back-and-forth conversation.
How we built it
We built LinguaLink as a full-stack Next.js 15 app using the App Router and React Server Components. The stack:
- Frontend: Next.js, TypeScript, React.js, custom CSS variables for a dark Apple-inspired design system
- Stats microservice — a separate Python FastAPI service handles all conversation analytics. The Next.js app POSTs user and conversation data to
/api/get-statsand receives a stats payload back. The service calculates new vocabulary acquired this week, most-used words via frequency counting, a 168-day activity heatmap, popular topics, average words per session, new words per minute, and weekly conversation counts over 8 weeks. - Video: VDO.Ninja for peer-to-peer WebRTC video without a media server
- Matchmaking: MongoDB (via Mongoose) with a
WaitingRoomcollection. Users join a queue with theirlearningLangandskillLevel(0–160). The matching algorithm finds the longest-waiting compatible partner who shares the same target language and a similar skill level (±15 points) - Database: MongoDB Atlas storing users, conversations, vocabulary, and topics
- AI: Gemini 2.5 Flash for topic extraction from conversation transcripts with the Gemini API. Gemini is also called to generate contextual conversation prompts tailored to the users' target language, native language, and difficulty level. ElevenLabs Scribe (
scribe_v2) handles speech-to-text transcription of each call. Audio is captured, sent to the ElevenLabs API, and the returned transcript is parsed word-by-word with timestamps, enabling both real-time captions and post-call vocabulary analysis - Polling: The waiting page polls
/api/matchevery 3 seconds; the call page polls/api/call/[id]/statusto detect when a partner leaves and automatically re-queues the remaining user
Challenges we ran into
- Matchmaking race conditions: when two users find each other simultaneously, both polls could attempt to create separate sessions. We solved this with an atomic MongoDB
findOneAndUpdatewith asessionId: nullguard, ensuring only one process claims the match. - Partner departure detection: when one user ends a call or skips, the other was left alone with no way to re-queue. We added a
/api/call/[id]/leaveendpoint and a 3-second status poll on the call page that detects when participant count drops below 2 and automatically returns the remaining user to/waiting. - Client-side state consistency: maintaining consistent state across a multi-page app without a global state manager required careful discipline — we standardised all sessionStorage keys and ensured writes happen before any async database calls that could fail.
- MongoDB connection buffering: server actions were timing out because the Mongoose connection file exported
connectDB()but action files were only importing the module without calling it. Every action now explicitly callsawait connectDB()as its first line.
Accomplishments that we're proud of
- A fully working real-time matchmaking system backed by MongoDB with race-condition protection
- Seamless WebRTC video calls using VDO.Ninja with zero media server infrastructure
- An end-to-end user journey from sign-up → onboarding → matchmaking (based on skill level and learning language) → live call → skip → rematch, all working in production
- A clean, accessible dark-mode UI that feels intuitive
- AI-powered conversation analysis that extracts vocabulary and topics from each session
- A Python FastAPI microservice that computes vocabulary growth, activity heatmaps, topic frequency, and CEFR progression independently of the JS frontend
What we learned
- Building the matchmaking algorithm taught us a lot about thinking through concurrency edge cases even in a small app. Two users finding each other at exactly the same moment is a real problem, and MongoDB's conditional update pattern turned out to be an elegant way to handle it without any locking infrastructure.
- Real conversation is a fundamentally different problem to vocabulary drilling. Building a product around live human interaction forced us to think about latency, awkward silences, and user anxiety in ways that a flashcard app never would.
- Designing for the unhappy path is as important as designing for the happy one. What happens when your match leaves mid-call? What happens when nobody is in the queue? What happens when the database call fails before the session is written? Each of those edge cases required its own considered solution.
What's next for LinguaLink
- AI conversation feedback: post-call analysis using Gemini to highlight grammar mistakes, suggest better phrasing, and track vocabulary growth over time
- Topic-based matching: let users choose conversation topics before joining the queue so matches share interests as well as skill level
- Mobile app: a React Native version optimised for mobile video calls
- Group calls: expand beyond 1-on-1 to small group conversations for more dynamic practice
Built With
- elevenlabs
- fastapi
- gemeniapi
- mongodb
- node.js
- python
- react.js
- typescript
Log in or sign up for Devpost to join the conversation.