How to Build a Personalized Real Estate Feed: Location, History & Smart Fallbacks cover

Nov 4, 2025

How to Build a Personalized Real Estate Feed: Location, History & Smart Fallbacks

Author

Abhishek Krishnan RathaurSoftware Engineer - II

Subject Matter Expert

Konakanchi Venkata Suresh BabuPrincipal Technical Consultant.

Hey folks! Recently, I have been working on an exciting real estate application where sellers can easily list their properties, and buyers can discover homes tailored just for them. One of the key challenges we tackled was personalizing the userโ€™s feedโ€Šโ€”โ€Šensuring that buyers see the most relevant listings based on their location, past searches, and preferences, while also keeping the experience fresh with smart fallbacks.

In this post, I will walk you through our approach to building a dynamic, user-centric feed that balances personalization with discovery. Whether youโ€™re a developer, product manager, or just curious about recommendation systems, this breakdown will give you practical insights!

Approach to Building a Dynamic Real Estate Feed

The Challenge: Making Property Discovery Smarter

In todayโ€™s hyper-competitive real estate market, relevance isnโ€™t just importantโ€Šโ€”โ€Šitโ€™s the difference between keeping users engaged or losing them forever. With countless property platforms available, buyers expect instant access to listings that perfectly match their needs, while sellers demand that their properties are seen by the right audiences. A generic, one-size-fits-all approach leads to frustrated users who waste hours scrolling through irrelevant options, while valuable properties get buried in the noise. This challenge is compounded by the fact that 78% of buyers abandon a platform after just three irrelevant recommendations (2023 PropTech Survey).


Why Personalization Matters

Imagine two users searching for properties in Mumbai:


  1. First-time buyer Priya wants a 2BHK apartment under โ‚น1.5Cr near schools.
  2. Investor Rohan seeks commercial spaces in Bandra with high rental yields

A generic feed showing random listings would frustrate both. Too broad, and users scroll endlessly. Too restrictive, and they miss hidden gems.

The Core Problem

Most platforms fail at:

1. Cold-start personalization (What to show new users?)

2. Over-reliance on manual filters (Forcing constant tweaking)

3 . Static recommendations (Ignoring evolving preferences)


Our Solution

We built an adaptive recommendation engine that:

1. Learns from explicit preferences (onboarding choices)

2. Adapts to implicit behavior (searches, clicks, dwell time)

3. Works seamlessly for logged-in and anonymous users

4. Respects privacy while maximizing relevance

Technical Foundations

Tech Stack

Backend: NestJS 

Database: PostgreSQL 

SearchPrisma: Raw SQL (Haversine formula)

Geolocation: Browser GPS + ipinfo.io API (fallback)

Example Scenario

When Priya (our first-time buyer) signs up:


  1. Onboarding: Selects โ€œFamily homeโ€, budget range, school proximity
  2. First search: Filters for โ€œ2BHK, near international schoolsโ€

System response:

  • Prioritizes 2BHKs in her budget
  • Boosts listings near top-rated schools
  • Gradually learns she prefers gated communities

Meanwhile, anonymous users get location-aware defaults based on:


1. Browser GPS (if permitted) โ†’ 500m precision

2. IP geolocation โ†’ Neighborhood-level

3. City-level trending listings โ†’ Fallback


1. Personalized Feeds Need Dataโ€Šโ€”โ€ŠBut Where Do We Get It?

To customize a userโ€™s feed, we need search metadataโ€Šโ€”โ€Šlocation, property type, budget range, and preferences. But this data comes from different sources depending on whether the user is logged in or not.


For Authenticated User: Leveraging Past Preferences


For authenticated users, our personalization system combines explicit onboarding preferences with implicit behavioral learning to deliver hyper-relevant property recommendations. During onboarding, users provide explicit baseline preferencesโ€Šโ€”โ€Šincluding locations (like hometown or work city), property type, budget ranges, and property categoryโ€Šโ€”โ€Šwhich we store as their primary preference profile. Beyond these initial inputs, our system automatically captures and analyzes every search interaction, converting filters like location, budget ranges, or property type selections into evolving implicit preferences.


Location history (array of {lat, long} coordinates)

Property category (residential/commercial)

Property type (apartment, villa, office, etc.)

Budget range (budget_min, budget_max)


This helps us prioritize listings that match their past behavior.


For anonymous users, we prioritize immediacy while respecting privacy. If location permissions are granted, we use real-time browser GPS coordinates for precise matching. When denied, we fall back to IP-based geolocation (via services like ipinfo.io) to approximate the userโ€™s city/region, supplemented by popular local listings. This ensures even first-time visitors receive contextually relevant options without friction, while logged-in users benefit from a feed that grows smarter with every interaction.

async function fetchLocationFromIP(ip) {
 const response = await fetch(
   `https://ipinfo.io/${ip}?token=${process.env.IPINFO_TOKEN}`
 );
 const { loc } = await response.json();
 const [lat, long] = loc.split(',');
 return { lat, long };
}

This gives us an approximate location to start with.


2. Building the Search Metadata

Hereโ€™s the logic we use to construct the feed criteria:

async function buildSearchMetadata(user ,ip ,currentLocation) {
 // Case 1: User is authenticated & has a search history
 const searchParams = {
   property_category: undefined,
   property_type: undefined,
   budget_min: undefined,
   budget_max: undefined,
   listing_type: undefined,
   locations: undefined,
   area_type: undefined,
   area_min: undefined,
   area_max: undefined,
   car_parking_capacity: undefined,
 };

 if (user?.searchPreference) {
   searchParams.locations = user.searchPreference.location;
   searchParams.property_category = user.searchPreference.propertyCategory || undefined;
   searchParams.property_type = user.searchPreference.propertyType || undefined;
   searchParams.budget_min = user.searchPreference.budget_min || undefined;
   searchParams.budget_max = user.searchPreference.budget_max || undefined;
   searchParams.listing_type = user.searchPreference.listing_type || undefined;
   searchParams.area_type = user.searchPreference.area_type || undefined;
   searchParams.car_parking_capacity = user.searchPreference.car_parking_capacity || undefined;
   searchParams.area_min = user.searchPreference.area_min || undefined;
   searchParams.area_max = user.searchPreference.area_max || undefined;
 }
 // case 2: For anonymous user location provided
 if(currentLocation){
   const {lat, long} = currentLocation;
   searchParams.locations = [{ lat, long }];
 }

 // Case 3: Fallback for ip-based location if location denied
 if (!searchParams.locations?.length) {
   const { lat, long } = await fetchLocationFromIP(ip);
   searchParams.locations = [{ lat, long }];
 }

 return searchParams;
}

1.Privacy-firstโ€Šโ€”โ€ŠWe donโ€™t store precise location for anonymous users.

2.Progressive personalizationโ€Šโ€”โ€ŠThe feed improves as users engage more.

3.Smart defaultsโ€Šโ€”โ€ŠIf no budget is set, we show mid-range properties.


3. Location-Based Filtering: Fast & Accurate

We use a two-phase geo-filter approach:


A. Phase 1: Bounding Box Filter (Fast Approximate Filtering)

Before calculating exact distances, the query first applies a bounding box to quickly eliminate distant listings:

loc.latitude BETWEEN ${latitude - latDiff} AND ${latitude + latDiff}
AND
loc.longitude BETWEEN ${longitude - lonDiff} AND ${longitude + lonDiff}

B. Phase 2: Precise Distance Calculation (Haversine Formula)

After the bounding box filter, the query applies an exact distance check:

${earthRadius} * acos(
 cos(radians(${latitude})) *
 cos(radians(loc.latitude)) *
 cos(radians(loc.longitude) - radians(${longitude})) +
 sin(radians(${latitude})) *
 sin(radians(loc.latitude))
) <= ${proximityRadius}

4. Dynamic Relevance Scoring

The query ranks listings based on how well they match search criteria:

const {property_type, listing_type, area_min, area_max, budget_min , budget_max, car_parking_capacity} = searchParams;
const scoreConditions: Prisma.Sql[] = [];

A. Property Type Matching

if (propertyType?.length > 0 && !propertyType.includes(PropertyType.ALL)) {
   scoreConditions.push(
     Prisma.sql`CASE WHEN l.property_type IN (${Prisma.join(
       propertyType.map((type) => Prisma.raw(`'${type}'::property_type`)),
     )}) THEN 1 ELSE 0 END`,
   );
 }

B. Listing Type Matching

if (listingType) {
   scoreConditions.push(
     Prisma.sql`CASE WHEN l.listing_type = ${Prisma.raw(`'${listingType}'::listing_type`)} THEN 1 ELSE 0 END`,
   );
 }

C. Area Matching

if (area_min && area_max) {
 scoreConditions.push(
   Prisma.sql`CASE WHEN (
     l.area_total BETWEEN ${area_min} AND ${area_max})
   THEN 1 ELSE 0 END`
 );
}

D. Car Parking Capacity Matching

if (car_parking_capacity) {
 scoreConditions.push(
   Prisma.sql`CASE WHEN (
     l.car_parking_capacity >= ${car_parking_capacity}
   ) THEN 1 ELSE 0 END` 
 );
}

E. Budget Matching

CASE WHEN (l.price BETWEEN ${budgetMin} AND ${budgetMax}) THEN 1 ELSE 0 END

Final Score Calculation

Final score calculation is based on four to five key matching criteria, with additional optional factors that can be incorporated for more granular personalization.

(property_type_match + listing_type_match + area_match + budget_match + car_parking) AS match_score

5. Smart Sorting & Prioritization

Listings are sorted using a three-tier ranking system:

match_score DESC (Best matches first)
l.priority DESC (Boosted listings)
la.impression_count DESC (Popular listings)

This ensures:

1. Relevance (Userโ€™s search preferences)

2. Business Needs (Manually prioritized listings)

3. Engagement (Trending properties)


Final Summary

Thank you for joining this exploration of how we built a smarter property recommendation system! By combining user preferences, location data, and search history, we created feeds that adapt to each personโ€™s needsโ€Šโ€”โ€Šwhether theyโ€™re a first-time visitor or a returning user. For logged-in users, the system learns from past behavior, while guests still get relevant results using approximate locations. This balance ensures everyone finds what theyโ€™re looking for quickly.


To make searches lightning-fast, we optimized our database with smart indexing on key filters like price, property category, and property type. By pre-filtering results and caching popular listings, we cut query times by over 80%. These tweaks ensure smooth performance, even with millions of properties in our system.

CREATE INDEX idx_listing_filters ON listings (property_type, listing_type, price, property_category,area_total); 
CREATE INDEX idx_geo_search ON locations (latitude, longitude);

Looking ahead, AI will take personalization even further. Imagine the system automatically suggesting filters based on your search queries or predicting preferences you havenโ€™t even stated yet. Weโ€™re excited to explore these innovationsโ€Šโ€”โ€Šand weโ€™d love to hear your ideas too! Thanks for reading, and happy house hunting!

Subscribe to Our Newsletter

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
Insight
Sep 1, 2026

Building Interactive Cards from Design JSON Without Killing Your Feed: Overlays, Video, Mute/Unmute, and Lag-Free Lists

Learn how to turn design JSON into interactive, video-enabled cards using overlays, smart media controls, caching, and virtualization without slowing down high-cardinality feeds.

Insight
Aug 31, 2026

What Is the GeekyAnts Agentic Development Life Cycle? How ADLC Changes Conventional Product Engineering

This blog explains GeekyAnts ADLC and how it brings AI agents into product engineering while keeping human oversight.

Insight
Aug 27, 2026

Your AI Model Is Now a Supply Chain Risk: Why FinTech Products Need Resilient, Compliant AI Architecture

Understand how AI in FinTech creates new supply-chain risks and how resilient architecture, governance, fallbacks, and observability can help teams build secure, compliant AI products.

Insight
Aug 27, 2026

Building AI Lending Products for Production: Credit Risk, Compliance, and Operational Control

Learn how to build production-ready AI lending products with credit risk, compliance, core banking integration, human review, and audit-ready architecture.

Insight
Aug 25, 2026

Building an AI-Ready ACH Payment Product: Features, Compliance, Costs, and Scale

This guide covers building AI-ready ACH payment software: core features, NACHA compliance, cost, and scaling for enterprise volume.

AI
Aug 21, 2026

Can You Get Sued for an AI-Built App? Legal Risks Founders Should Know

A practical legal risk guide for founders and engineering leaders building AI built apps, covering liability, copyright, data privacy, and what it takes to survive enterprise due diligence.

Insight
Aug 21, 2026

Clinical Trial Management Software Development: Features, AI Use Cases, Cost, and Timeline

A practical guide to developing clinical trial management software, including features, AI use cases, architecture, integrations, development process, and CTMS strategy decisions that shape trial cost, compliance, and delivery.

The Right Conversation Can

Save You Six Months.

Book a call