Skip to content

race() #515

@jsartisan

Description

@jsartisan

Info

difficulty: easy
title: race()
type: question
template: javascript
tags: javascript

Question

Implement an async function helper race() that executes multiple asynchronous functions concurrently and returns the result of the first function to complete, similar to Promise.race(). Unlike parallel(), it finishes as soon as any function completes or encounters an error.

Requirements

  1. Concurrent execution – Execute all async functions simultaneously
  2. First completion wins – Return the result of the first function to complete (success or error)
  3. Early termination – Stop and ignore remaining functions once first result is received
  4. Callback interface – Each async function follows the Node.js callback pattern (error, data) => void
  5. Return async functionrace() should return a new async function that can be called

Key Behaviors

  • Parallel execution – All functions start simultaneously and run concurrently
  • First-wins semantics – Return the first result (success or error) and ignore the rest
  • Immediate completion – Call final callback as soon as any function completes
  • No result collection – Don't wait for or collect results from other functions
  • Flexible input – Accept any number of async functions in the array

Example

const async1 = (callback) => {
  setTimeout(() => callback(null, 1), 300);
};

const async2 = (callback) => {
  setTimeout(() => callback(null, 2), 100);
};

const async3 = (callback) => {
  setTimeout(() => callback(null, 3), 200);
};

const first = race([async1, async2, async3]);

first((error, data) => {
  console.log(data); // 2, since async2 completes first
});

// Error handling
const asyncFail = (callback) => {
  setTimeout(() => callback(new Error('Something failed')), 50);
};

const raceWithError = race([async1, asyncFail, async3]);

raceWithError((error, data) => {
  console.log(error.message); // "Something failed"
  console.log(data); // undefined
});

Key Challenge

The function must coordinate multiple concurrent async operations and return the first result (success or error) while ignoring all subsequent completions.

Template (JavaScript)

javascript.template.md

export function race(asyncFuncs) {
  // TODO: Implement me
}
import { race } from './index';

describe('race', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  it('should return the first function to complete', (done) => {
    const async1 = (callback) => {
      setTimeout(() => callback(null, 1), 300);
    };

    const async2 = (callback) => {
      setTimeout(() => callback(null, 2), 100);
    };

    const async3 = (callback) => {
      setTimeout(() => callback(null, 3), 200);
    };

    const first = race([async1, async2, async3]);

    first((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(2); // async2 completes first
      done();
    });

    jest.advanceTimersByTime(150);
  });

  it('should handle empty array', (done) => {
    const emptyRace = race([]);

    emptyRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBeUndefined();
      done();
    });
  });

  it('should handle single async function', (done) => {
    const asyncSingle = (callback) => {
      setTimeout(() => callback(null, 'single'), 100);
    };

    const singleRace = race([asyncSingle]);

    singleRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('single');
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should return first error and ignore subsequent results', (done) => {
    const async1 = (callback) => {
      setTimeout(() => callback(null, 1), 100);
    };

    const asyncFail = (callback) => {
      setTimeout(() => callback(new Error('First error')), 50);
    };

    const asyncSuccess = (callback) => {
      setTimeout(() => callback(null, 3), 75);
    };

    const raceWithError = race([async1, asyncFail, asyncSuccess]);

    raceWithError((error, data) => {
      expect(error.message).toBe('First error');
      expect(data).toBeUndefined();
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should handle functions with same completion time', (done) => {
    const async1 = (callback) => {
      setTimeout(() => callback(null, 1), 100);
    };

    const async2 = (callback) => {
      setTimeout(() => callback(null, 2), 100);
    };

    const async3 = (callback) => {
      setTimeout(() => callback(null, 3), 100);
    };

    const sameTimeRace = race([async1, async2, async3]);

    sameTimeRace((error, data) => {
      expect(error).toBeNull();
      // Any of the three values could be returned
      expect([1, 2, 3]).toContain(data);
      done();
    });

    jest.advanceTimersByTime(150);
  });

  it('should handle functions with different completion times', (done) => {
    const fastAsync = (callback) => {
      setTimeout(() => callback(null, 'fast'), 10);
    };

    const slowAsync = (callback) => {
      setTimeout(() => callback(null, 'slow'), 200);
    };

    const mediumAsync = (callback) => {
      setTimeout(() => callback(null, 'medium'), 100);
    };

    const mixedRace = race([fastAsync, slowAsync, mediumAsync]);

    mixedRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('fast'); // Fastest completes first
      done();
    });

    jest.advanceTimersByTime(50);
  });

  it('should handle mixed data types', (done) => {
    const asyncString = (callback) => {
      setTimeout(() => callback(null, 'hello'), 50);
    };

    const asyncNumber = (callback) => {
      setTimeout(() => callback(null, 42), 100);
    };

    const asyncBoolean = (callback) => {
      setTimeout(() => callback(null, true), 150);
    };

    const mixedTypesRace = race([asyncString, asyncNumber, asyncBoolean]);

    mixedTypesRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('hello'); // String completes first
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should handle immediate error', (done) => {
    const asyncFail = (callback) => {
      setTimeout(() => callback(new Error('Immediate error')), 10);
    };

    const asyncSuccess = (callback) => {
      setTimeout(() => callback(null, 'success'), 50);
    };

    const immediateErrorRace = race([asyncFail, asyncSuccess]);

    immediateErrorRace((error, data) => {
      expect(error.message).toBe('Immediate error');
      expect(data).toBeUndefined();
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should ignore results from slower functions', (done) => {
    const fastAsync = jest.fn((callback) => {
      setTimeout(() => callback(null, 'fast'), 50);
    });

    const slowAsync = jest.fn((callback) => {
      setTimeout(() => callback(null, 'slow'), 200);
    });

    const raceTest = race([fastAsync, slowAsync]);

    raceTest((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('fast');
      // Verify that slow function was called but its result is ignored
      expect(fastAsync).toHaveBeenCalled();
      expect(slowAsync).toHaveBeenCalled();
      done();
    });

    jest.advanceTimersByTime(100);
  });
});

Template (TypeScript)

typescript.template.md

type Callback = (error: Error | null, data: any) => void;
type AsyncFunc = (callback: Callback, data?: any) => void;

export function race(asyncFuncs: AsyncFunc[]): AsyncFunc {
  // TODO: Implement me
}
import { race } from './index';

describe('race', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.clearAllTimers();
    jest.useRealTimers();
  });

  it('should return the first function to complete', (done) => {
    const async1 = (callback: any) => {
      setTimeout(() => callback(null, 1), 300);
    };

    const async2 = (callback: any) => {
      setTimeout(() => callback(null, 2), 100);
    };

    const async3 = (callback: any) => {
      setTimeout(() => callback(null, 3), 200);
    };

    const first = race([async1, async2, async3]);

    first((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe(2); // async2 completes first
      done();
    });

    jest.advanceTimersByTime(150);
  });

  it('should handle empty array', (done) => {
    const emptyRace = race([]);

    emptyRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBeUndefined();
      done();
    });
  });

  it('should handle single async function', (done) => {
    const asyncSingle = (callback: any) => {
      setTimeout(() => callback(null, 'single'), 100);
    };

    const singleRace = race([asyncSingle]);

    singleRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('single');
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should return first error and ignore subsequent results', (done) => {
    const async1 = (callback: any) => {
      setTimeout(() => callback(null, 1), 100);
    };

    const asyncFail = (callback: any) => {
      setTimeout(() => callback(new Error('First error')), 50);
    };

    const asyncSuccess = (callback: any) => {
      setTimeout(() => callback(null, 3), 75);
    };

    const raceWithError = race([async1, asyncFail, asyncSuccess]);

    raceWithError((error, data) => {
      expect(error.message).toBe('First error');
      expect(data).toBeUndefined();
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should handle functions with same completion time', (done) => {
    const async1 = (callback: any) => {
      setTimeout(() => callback(null, 1), 100);
    };

    const async2 = (callback: any) => {
      setTimeout(() => callback(null, 2), 100);
    };

    const async3 = (callback: any) => {
      setTimeout(() => callback(null, 3), 100);
    };

    const sameTimeRace = race([async1, async2, async3]);

    sameTimeRace((error, data) => {
      expect(error).toBeNull();
      // Any of the three values could be returned
      expect([1, 2, 3]).toContain(data);
      done();
    });

    jest.advanceTimersByTime(150);
  });

  it('should handle functions with different completion times', (done) => {
    const fastAsync = (callback: any) => {
      setTimeout(() => callback(null, 'fast'), 10);
    };

    const slowAsync = (callback: any) => {
      setTimeout(() => callback(null, 'slow'), 200);
    };

    const mediumAsync = (callback: any) => {
      setTimeout(() => callback(null, 'medium'), 100);
    };

    const mixedRace = race([fastAsync, slowAsync, mediumAsync]);

    mixedRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('fast'); // Fastest completes first
      done();
    });

    jest.advanceTimersByTime(50);
  });

  it('should handle mixed data types', (done) => {
    const asyncString = (callback: any) => {
      setTimeout(() => callback(null, 'hello'), 50);
    };

    const asyncNumber = (callback: any) => {
      setTimeout(() => callback(null, 42), 100);
    };

    const asyncBoolean = (callback: any) => {
      setTimeout(() => callback(null, true), 150);
    };

    const mixedTypesRace = race([asyncString, asyncNumber, asyncBoolean]);

    mixedTypesRace((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('hello'); // String completes first
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should handle immediate error', (done) => {
    const asyncFail = (callback: any) => {
      setTimeout(() => callback(new Error('Immediate error')), 10);
    };

    const asyncSuccess = (callback: any) => {
      setTimeout(() => callback(null, 'success'), 50);
    };

    const immediateErrorRace = race([asyncFail, asyncSuccess]);

    immediateErrorRace((error, data) => {
      expect(error.message).toBe('Immediate error');
      expect(data).toBeUndefined();
      done();
    });

    jest.advanceTimersByTime(100);
  });

  it('should ignore results from slower functions', (done) => {
    const fastAsync = jest.fn((callback: any) => {
      setTimeout(() => callback(null, 'fast'), 50);
    });

    const slowAsync = jest.fn((callback: any) => {
      setTimeout(() => callback(null, 'slow'), 200);
    });

    const raceTest = race([fastAsync, slowAsync]);

    raceTest((error, data) => {
      expect(error).toBeNull();
      expect(data).toBe('fast');
      // Verify that slow function was called but its result is ignored
      expect(fastAsync).toHaveBeenCalled();
      expect(slowAsync).toHaveBeenCalled();
      done();
    });

    jest.advanceTimersByTime(100);
  });
});

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions