A full-stack web application that allocates hostel rooms to students using the Gale-Shapley stable matching algorithm — the same class of algorithm behind the National Resident Matching Program (which matches medical residents to hospitals in the US) and Nobel Prize–winning work in market design.
Built solo as a portfolio project to demonstrate algorithmic thinking, full-stack engineering, and the ability to design, debug, and ship a real deployed system end-to-end.
🔗 Live app: hostel-seat-allocation.vercel.app 🔗 API: hostel-seat-allocation.onrender.com 🔗 Repo: github.com/Anuj8506/hostel-seat-allocation
⚠️ Cold start note: The backend is hosted on Render's free tier, which spins down after periods of inactivity. The first request after idle time can take 30–50 seconds to wake the server up. Subsequent requests are fast. If the app seems stuck on first load, give it a moment — it's not broken, just waking up.
| Select Hostels | Rank Hostels |
|---|---|
![]() |
![]() |
| Allocation Result |
|---|
![]() |
| Admin Room Management | Admin Allocation Results |
|---|---|
![]() |
![]() |
Every year, colleges face the same problem: N students, M hostel rooms, everyone has preferences, capacity is limited, and someone has to decide who gets what — fairly. Most systems solve this with first-come-first-served or manual admin decisions, both of which are easy to game or simply unfair.
This project treats it as what it actually is: a two-sided matching problem, and solves it with an algorithm that's mathematically guaranteed to produce a stable matching — no student and hostel room would both prefer each other over their current assignment.
| Layer | Technology |
|---|---|
| Frontend | React (Vite), React Router, Axios |
| Backend | Node.js, Express |
| Database | MongoDB Atlas |
| Auth | Stateless JWT |
| Deployment | Vercel (frontend) + Render (backend) |
Gale-Shapley solves the stable marriage problem: given two groups where each member ranks the other group by preference, find a matching where no two participants would rather be matched to each other than to their current match. That property is called stability, and it's what makes the algorithm fair in a way naive approaches (like first-come-first-served) aren't.
- Students have preferences over hostel rooms/blocks.
- Rooms (via the admin/institution) have an implicit ranking of students, based on merit — for this system, 12th percentage for first-years and CGPA for seniors (years 2–4).
- Both sides "propose" and "reject" until every match is stable — nobody is left wishing they'd swapped.
This project runs two independent Gale-Shapley pools:
- First-year pool — students ranked by 12th percentage (0–100)
- Senior pool (years 2–4) — students ranked by CGPA × 10 (normalized to a 0–100 scale)
The pools never compete with each other for rooms — each category of room only matches within its own pool, so a strong senior can't crowd out a first-year for a first-year room, and vice versa.
- Every unmatched student proposes to their top remaining choice of room.
- Each room compares all students currently proposing to it (plus anyone it's already provisionally holding) and keeps its highest-ranked students up to capacity, rejecting the rest.
- Rejected students cross that room off their list and propose to their next choice in the following round.
- Repeat until every student is either matched or has been rejected by every room on their preference list.
Worked example:
Say Room A (capacity 1) is preferred by Students X and Y. X has a 95% (12th), Y has an 88%.
- Round 1: Both X and Y propose to Room A (their top choice).
- Room A can only hold 1 student, so it provisionally holds X (higher score) and rejects Y.
- Round 2: Y proposes to their next-choice room (say Room B), which has space, so Y is provisionally held there.
- No further rejections occur — the matching is now stable: X can't do better than Room A, and Y isn't rejected by anyone with room left to accept them over Y.
Complexity: O(n²) in the worst case (n students, n rooms) — every student may propose to every room once before the algorithm terminates. For a hostel-scale problem (hundreds of students, tens of rooms), this runs essentially instantly.
Implementation: backend/src/algorithms/galeShapley.js — returns { studentMatch, hostelHoldings, unmatchedStudents }. Tested against hand-traced cases (4-student/2-hostel, 5-student unmatched scenario, and 3 additional edge cases) in test.js and edgeTestCase.js.
Not everyone gets matched in Round 1 (rooms run out, or preferences don't align). So:
- Round 1: Full pool of students vs. full room capacity.
- Round 2: Any unmatched students from Round 1 get a fresh, scoped preference form (only showing rooms with remaining capacity) and are matched again via a second, independent Gale-Shapley run.
- Manual review fallback: Anyone still unmatched after Round 2 is flagged for manual admin review rather than silently left in limbo.
A few design decisions worth calling out (the kind of thing that comes up in an interview):
- Single
Roommodel with acategoryfield ('first-year'/'senior') instead of two separate models — same shape of data, differentiated by a field rather than duplicated schemas. - Preference history is preserved — a new
Preferencedocument is created for each round rather than overwriting the previous one, so the full decision trail is auditable. isAdminflag on theStudentmodel rather than a separateAdminmodel — simpler for a system with only one role distinction, with admin bootstrap handled by registering normally and flipping the flag directly in MongoDB Atlas (documented as an intentional, interview-defensible tradeoff, not an oversight).- Sentinel document pattern for Round 2's "already ran" guard — rather than tracking round completion with a separate flag that could go stale, a sentinel
Allocationdocument (status: 'round2-complete') is written when Round 2 finishes, including when it produces zero new allocations. This closed a real bug: a naive "did any allocations get created?" check silently let Round 2 be re-triggered when it happened to match nobody. - Stateless JWT auth — no server-side session store, which keeps the backend easy to scale horizontally (not that a portfolio project needs to scale, but the pattern is production-realistic).
git clone https://github.com/Anuj8506/hostel-seat-allocation.git
cd hostel-seat-allocation
# Backend
cd backend
npm install
# create a .env file (see .env.example) with MONGO_URI, JWT_SECRET, CLIENT_URL
npm run dev
# Frontend (in a new terminal)
cd frontend
npm install
# create a .env file with VITE_API_URL (no /api suffix)
npm run devThere's no public "make me an admin" endpoint (intentionally — see Architecture Highlights above). To create an admin account:
- Register a normal student account through the app.
- In MongoDB Atlas, open the
hostel-dbdatabase →studentscollection. - Find your user document and set
isAdmin: true. - Log out and back in to pick up the new token.
- Student registration & login (JWT auth)
- Branch validation against a configurable whitelist
- Two-step preference selection UI (select relevant hostels, then rank them — not forced to rank every option)
- Two-round Gale-Shapley allocation with pool separation by year group
- Manual review queue for students unmatched after both rounds
- Admin dashboard: room CRUD, trigger allocation rounds, view results, view unmatched students
(Full list maintained in a local, gitignored IMPROVEMENTS.md as a deliberate scope-control parking lot. Highlights:)
- Dedicated admin-promotion endpoint (currently handled via direct DB edit — functional, but a proper endpoint would be the natural next step for a multi-admin institution)
- Branch-level CGPA normalization (accounting for grading differences across departments)
- Distance/location as an additional room-scoring factor (deferred since all hostels are on the same campus, and student preference ranking already implicitly captures proximity preference)
Built by Anuj as a solo portfolio project — full-stack implementation plus algorithmic design, testing, and cloud deployment, done independently from first commit to production.




