Skip to main content

Command Palette

Search for a command to run...

Search Implementation: Build Search Functionality

Learn: Search Implementation: Build Search Functionality

Updated
6 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Search Implementation: Build Search Functionality

Problem

Modern applications need efficient search capabilities that go beyond simple string matching. Users expect:

  • Full-text search across multiple fields
  • Real-time filtering with multiple criteria
  • Relevance ranking for better results
  • Performance even with large datasets
  • Faceted navigation for refined searches

Without proper search implementation, users struggle to find relevant data, leading to poor user experience and reduced engagement.

Solution

A comprehensive search system combines:

  1. Full-text indexing for fast text matching
  2. Filter mechanisms for categorical/range-based queries
  3. Ranking algorithms for relevance scoring
  4. Caching strategies for performance
  5. Pagination for manageable result sets

Code Implementation

1. Basic Search Engine Class

class SearchEngine {
  constructor(documents = []) {
    this.documents = documents;
    this.index = new Map();
    this.buildIndex();
  }

  // Build inverted index for full-text search
  buildIndex() {
    this.index.clear();

    this.documents.forEach((doc, docId) => {
      const tokens = this.tokenize(doc.title + ' ' + doc.content);

      tokens.forEach(token => {
        if (!this.index.has(token)) {
          this.index.set(token, []);
        }

        const postings = this.index.get(token);
        if (!postings.find(p => p.docId === docId)) {
          postings.push({ docId, frequency: 0 });
        }

        postings.find(p => p.docId === docId).frequency++;
      });
    });
  }

  // Tokenize and normalize text
  tokenize(text) {
    return text
      .toLowerCase()
      .replace(/[^\w\s]/g, '')
      .split(/\s+/)
      .filter(token => token.length > 2);
  }

  // Full-text search with TF-IDF ranking
  search(query, options = {}) {
    const {
      limit = 10,
      offset = 0,
      filters = {},
      sortBy = 'relevance'
    } = options;

    const queryTokens = this.tokenize(query);
    const results = new Map();

    // Find documents matching query tokens
    queryTokens.forEach(token => {
      const postings = this.index.get(token) || [];

      postings.forEach(({ docId, frequency }) => {
        if (!results.has(docId)) {
          results.set(docId, {
            docId,
            score: 0,
            matchedTokens: 0
          });
        }

        const result = results.get(docId);
        result.score += this.calculateTFIDF(token, frequency, docId);
        result.matchedTokens++;
      });
    });

    // Apply filters
    let filtered = Array.from(results.values())
      .map(result => ({
        ...result,
        document: this.documents[result.docId]
      }))
      .filter(result => this.applyFilters(result.document, filters));

    // Sort results
    filtered.sort((a, b) => {
      if (sortBy === 'relevance') {
        return b.score - a.score;
      } else if (sortBy === 'date') {
        return new Date(b.document.date) - new Date(a.document.date);
      }
      return 0;
    });

    // Pagination
    const total = filtered.length;
    const paginated = filtered.slice(offset, offset + limit);

    return {
      results: paginated,
      total,
      hasMore: offset + limit < total,
      query,
      executionTime: 0
    };
  }

  // Calculate TF-IDF score
  calculateTFIDF(token, frequency, docId) {
    const tf = frequency / this.documents[docId].wordCount;
    const docsWithToken = (this.index.get(token) || []).length;
    const idf = Math.log(this.documents.length / (docsWithToken + 1));
    return tf * idf;
  }

  // Apply filter criteria
  applyFilters(document, filters) {
    return Object.entries(filters).every(([key, value]) => {
      if (Array.isArray(value)) {
        return value.includes(document[key]);
      } else if (typeof value === 'object' && value.min !== undefined) {
        return document[key] >= value.min && document[key] <= value.max;
      }
      return document[key] === value;
    });
  }
}

2. Advanced Filter System

class FilterManager {
  constructor() {
    this.filters = new Map();
    this.facets = new Map();
  }

  // Register filter type
  registerFilter(name, type, options = {}) {
    this.filters.set(name, {
      type, // 'category', 'range', 'date', 'text'
      options,
      active: false,
      value: null
    });
  }

  // Build facets from documents
  buildFacets(documents, facetFields) {
    facetFields.forEach(field => {
      const facetMap = new Map();

      documents.forEach(doc => {
        const value = doc[field];
        if (value) {
          facetMap.set(value, (facetMap.get(value) || 0) + 1);
        }
      });

      this.facets.set(field, Array.from(facetMap.entries())
        .map(([value, count]) => ({ value, count }))
        .sort((a, b) => b.count - a.count)
      );
    });
  }

  // Apply filters to documents
  applyFilters(documents, activeFilters) {
    return documents.filter(doc => {
      return Object.entries(activeFilters).every(([filterName, filterValue]) => {
        const filter = this.filters.get(filterName);

        if (!filter) return true;

        switch (filter.type) {
          case 'category':
            return Array.isArray(filterValue)
              ? filterValue.includes(doc[filterName])
              : doc[filterName] === filterValue;

          case 'range':
            return doc[filterName] >= filterValue.min && 
                   doc[filterName] <= filterValue.max;

          case 'date':
            const docDate = new Date(doc[filterName]);
            return docDate >= filterValue.start && 
                   docDate <= filterValue.end;

          case 'text':
            return doc[filterName]
              .toLowerCase()
              .includes(filterValue.toLowerCase());

          default:
            return true;
        }
      });
    });
  }

  // Get available facet values
  getFacets(fieldName) {
    return this.facets.get(fieldName) || [];
  }

  // Set active filter
  setFilter(name, value) {
    const filter = this.filters.get(name);
    if (filter) {
      filter.active = true;
      filter.value = value;
    }
  }

  // Clear filter
  clearFilter(name) {
    const filter = this.filters.get(name);
    if (filter) {
      filter.active = false;
      filter.value = null;
    }
  }

  // Get active filters
  getActiveFilters() {
    const active = {};
    this.filters.forEach((filter, name) => {
      if (filter.active) {
        active[name] = filter.value;
      }
    });
    return active;
  }
}

3. Search with Caching

class CachedSearchEngine extends SearchEngine {
  constructor(documents = []) {
    super(documents);
    this.cache = new Map();
    this.cacheSize = 100;
  }

  // Generate cache key
  generateCacheKey(query, filters, options) {
    return JSON.stringify({ query, filters, options });
  }

  // Search with caching
  search(query, options = {}) {
    const cacheKey = this.generateCacheKey(query, options.filters, options);

    if (this.cache.has(cacheKey)) {
      return {
        ...this.cache.get(cacheKey),
        cached: true
      };
    }

    const startTime = performance.now();
    const results = super.search(query, options);
    const executionTime = performance.now() - startTime;

    results.executionTime = executionTime;

    // Store in cache
    if (this.cache.size >= this.cacheSize) {
      const firstKey = this.cache.keys().next().value;
      this.cache.delete(firstKey);
    }
    this.cache.set(cacheKey, results);

    return results;
  }

  // Clear cache
  clearCache() {
    this.cache.clear();
  }

  // Get cache stats
  getCacheStats() {
    return {
      size: this.cache.size,
      maxSize: this.cacheSize
    };
  }
}

4. Complete Example with UI

// Sample data
const sampleDocuments = [
  {
    id: 1,
    title: 'JavaScript Fundamentals',
    content: 'Learn the basics of JavaScript programming',
    category: 'Programming',
    difficulty: 'Beginner',
    rating: 4.5,
    date: '2024-01-15',
    wordCount: 150
  },
  {
    id: 2,
    title: 'Advanced React Patterns',
    content: 'Master advanced React design patterns and best practices',
    category: 'Web Development',
    difficulty: 'Advanced',
    rating: 4.8,
    date: '2024-02-20',
    wordCount: 200
  },
  {
    id: 3,
    title: 'Node.js Backend Development',
    content: 'Build scalable backend applications with Node.js',
    category: 'Backend',
    difficulty: 'Intermediate',
    rating: 4.6,
    date: '2024-01-10',
    wordCount: 180
  }
];

// Initialize search engine
const searchEngine = new CachedSearchEngine(sampleDocuments);

// Initialize filter manager
const filterManager = new FilterManager();
filterManager.registerFilter('category', 'category');
filterManager.registerFilter('difficulty', 'category');
filterManager.registerFilter('rating', 'range', { min: 0, max: 5 });
filterManager.buildFacets(sampleDocuments, ['category', 'difficulty']);

// Perform search
const results = searchEngine.search('JavaScript React', {
  limit: 10,
  offset: 0,
  filters: {
    category: ['Programming', 'Web Development'],
    rating: { min: 4.5, max: 5 }
  },
  sortBy: 'relevance'
});

console.log('Search Results:', results);
console.log('Available Categories:', filterManager.getFacets('category'));

Tips & Best Practices

1. Indexing Strategy

// Use appropriate data structures
// - Inverted index for full-text search
// - B-tree for range queries
// - Hash tables for exact matches
// - Trie for prefix searches

2. Performance Optimization

// Implement lazy loading
const lazySearch = async (query) => {
  const batchSize = 100;
  let offset = 0;

  while (true) {
    const batch = searchEngine.search(query, {
      limit: batchSize,
      offset
    });

    yield batch.results;

    if (!batch.hasMore) break;
    offset += batchSize;
  }
};

3. Relevance Ranking

  • Use TF-IDF for basic relevance
  • Implement BM25 for better results
  • Consider field boosting (title > content)
  • Apply recency bias for time-sensitive data

4. Filter Best Practices

  • Faceted search for discoverability
  • Filter suggestions based on results
  • Applied filters display for clarity
  • Clear all option for easy reset

5. Caching Strategy

  • Cache frequent queries
  • Implement LRU eviction policy
  • Set appropriate TTL for cache entries
  • Monitor cache hit rates

6. Scalability Considerations

  • Use Elasticsearch or Solr for large datasets
  • Implement distributed indexing
  • Consider database-level full-text search
  • Use search-as-you-type with debouncing

7. User Experience

// Debounce search input
const debounceSearch = (fn, delay = 300) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
};

const handleSearch = debounceSearch((query) => {
  const results = searchEngine.search(query);
  updateUI(results);
}, 300);

8. Testing

// Test search accuracy
const testSearch = () => {
  const query = 'JavaScript';
  const results = searchEngine.search(query);

  console.assert(results.results.length > 0, 'Should find results');
  console.assert(results.results[0].score > 0, 'Should have relevance score');
};

Summary

A robust search implementation requires:

  • Efficient indexing for fast retrieval
  • Flexible filtering for refined results
  • Smart ranking for relevance
  • Performance optimization through caching
  • Scalable architecture for growth

This foundation enables users to quickly find exactly what they need, improving engagement and satisfaction.