Race condition is a programming error that occurs when multiple asynchronous operations try to access the same data simultaneously, and the result depends on the order of their execution. To avoid race conditions, synchronization methods are used: locks, semaphores, queues, cancellation of previous operations, and proper state management.
Main prevention methods:
Race condition is a critical problem in asynchronous programming where program behavior depends on the timing of competing operations. This is especially relevant in web applications where multiple asynchronous operations can run in parallel.
Race conditions occur when:
Most common approach when working with APIs:
// Cancel previous request on new search
let controller = new AbortController();
function search(query) {
// Cancel previous request
controller.abort();
controller = new AbortController();
return fetch(`/api/search?q=${query}`, {
signal: controller.signal
});
}Process operations sequentially:
// Queue for sequential execution
const queue = Promise.resolve();
function addToQueue(operation) {
return queue.then(() => operation());
}class SearchService {
constructor() {
this.controller = new AbortController();
}
async search(query) {
// Cancel previous search
this.controller.abort();
this.controller = new AbortController();
try {
const response = await fetch(`/api/search?q=${query}`, {
signal: this.controller.signal
});
return await response.json();
} catch (error) {
if (error.name !== 'AbortError') {
throw error;
}
}
}
}// Use mutex for synchronization
class Mutex {
constructor() {
this.queue = Promise.resolve();
}
lock() {
let unlock;
const lock = new Promise(resolve => {
unlock = resolve;
});
this.queue = this.queue.then(() => lock);
return unlock;
}
}When quickly editing data:
// Correct approach - cancel previous save
function autoSave(data) {
if (this.saveController) {
this.saveController.abort();
}
this.saveController = new AbortController();
return saveData(data, this.saveController.signal);
}When multiple operations update the same element:
// Use sequence for correct order
async function updateUI() {
const results = await Promise.all([
fetch('/api/data1'),
fetch('/api/data2')
]);
// Update UI only after all data
renderResults(results);
}Race prevention methods work in all modern browsers but require proper understanding of asynchronicity.
Preventing race conditions is an important aspect of creating reliable asynchronous applications. Proper synchronization ensures stable operation and predictable behavior.
What’s the problem with this code and how to fix it?
let userData = null;
async function updateProfile(newData) {
const response = await fetch('/api/profile', {
method: 'PUT',
body: JSON.stringify(newData)
});
userData = await response.json();
renderProfile(userData);
}
// User quickly changes data
updateProfile({ name: 'John' });
updateProfile({ name: 'Peter' });
updateProfile({ name: 'Sid' });Answer: The problem is a race condition. Since requests execute asynchronously, the order of completion is unpredictable. It may happen that the last request sent completes first, and outdated data is displayed in the interface.
Fixed version:
let userData = null;
let controller = new AbortController();
async function updateProfile(newData) {
// Cancel previous request
controller.abort();
controller = new AbortController();
try {
const response = await fetch('/api/profile', {
method: 'PUT',
body: JSON.stringify(newData),
signal: controller.signal
});
userData = await response.json();
renderProfile(userData);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Profile update error:', error);
}
}
}Explanation:
This is a standard pattern for preventing races in web applications. It ensures the interface displays current state after the last user action.
Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪