Search Implementation: Build Search Functionality
Learn: Search Implementation: Build Search Functionality
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:
- Full-text indexing for fast text matching
- Filter mechanisms for categorical/range-based queries
- Ranking algorithms for relevance scoring
- Caching strategies for performance
- 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.