Problem: Decorate a Retryable Operation with Metrics

Hard
40 min
Implement a retry mechanism for unreliable async operations, then enhance it with logging and timing decorators.

Problem statement

You’re responsible for stabilizing a service call that occasionally fails due to random network errors. Your task is twofold: first, implement a retry wrapper that automatically retries failed async operations. Then, use decorators to add observability—logging and timing—without changing the retry logic.

This simulates real-world resilience patterns where we layer monitoring on top of recovery mechanisms.

Goal

  1. Implement a retry(fn) function that:

    1. Calls fn() up to 3 times.

    2. Retries only when an error is thrown.

    3. Returns the first successful result.

    4. Throws the last error after 3 failed attempts.

  2. Implement two decorators:

    1. withLogging(fn): Logs when the operation starts and whether it succeeded or failed.

    2. withTiming(fn): Measures the time it takes for the operation (including retries).

  3. Compose them together:

const wrappedOperation = withTiming(withLogging(retry(flakyOperation)));

Constraints

  • Do not modify flakyOperation.

  • The retrywithLogging, and withTiming functions must be independent.

  • Handle errors gracefully and log retries.

  • Use only console.log for all output.

Sample output

The examples below illustrate what the output should look like:

(async () => {
const wrappedOperation = withTiming(withLogging(retry(flakyOperation)));
try {
const result = await wrappedOperation();
console.log('Final Result:', result);
} catch (err) {
console.log('Operation failed after retries');
}
})();
/* Expected output (might vary):
[LOG] Starting operation...
[RETRY] Attempt 1 failed
[RETRY] Attempt 2 failed
[LOG] Operation succeeded
[TIMER] retry took 312ms
Final Result: Success! */

Good luck trying the problem! If you’re unsure how to proceed, check the “Solution” tab above.

Problem: Decorate a Retryable Operation with Metrics

Hard
40 min
Implement a retry mechanism for unreliable async operations, then enhance it with logging and timing decorators.

Problem statement

You’re responsible for stabilizing a service call that occasionally fails due to random network errors. Your task is twofold: first, implement a retry wrapper that automatically retries failed async operations. Then, use decorators to add observability—logging and timing—without changing the retry logic.

This simulates real-world resilience patterns where we layer monitoring on top of recovery mechanisms.

Goal

  1. Implement a retry(fn) function that:

    1. Calls fn() up to 3 times.

    2. Retries only when an error is thrown.

    3. Returns the first successful result.

    4. Throws the last error after 3 failed attempts.

  2. Implement two decorators:

    1. withLogging(fn): Logs when the operation starts and whether it succeeded or failed.

    2. withTiming(fn): Measures the time it takes for the operation (including retries).

  3. Compose them together:

const wrappedOperation = withTiming(withLogging(retry(flakyOperation)));

Constraints

  • Do not modify flakyOperation.

  • The retrywithLogging, and withTiming functions must be independent.

  • Handle errors gracefully and log retries.

  • Use only console.log for all output.

Sample output

The examples below illustrate what the output should look like:

(async () => {
const wrappedOperation = withTiming(withLogging(retry(flakyOperation)));
try {
const result = await wrappedOperation();
console.log('Final Result:', result);
} catch (err) {
console.log('Operation failed after retries');
}
})();
/* Expected output (might vary):
[LOG] Starting operation...
[RETRY] Attempt 1 failed
[RETRY] Attempt 2 failed
[LOG] Operation succeeded
[TIMER] retry took 312ms
Final Result: Success! */

Good luck trying the problem! If you’re unsure how to proceed, check the “Solution” tab above.

Node.js
// Simulated flaky async function
async function flakyOperation() {
if (Math.random() < 0.75) throw new Error('Random failure');
return 'Success!';
}
// Your code here
// Example usage
(async () => {
const wrappedOperation = withTiming(withLogging(retry(flakyOperation)));
try {
const result = await wrappedOperation();
console.log('Final Result:', result);
} catch (err) {
console.log('Operation failed after retries');
}
})();
/* Expected output (might vary):
[LOG] Starting operation...
[RETRY] Attempt 1 failed
[RETRY] Attempt 2 failed
[LOG] Operation succeeded
[TIMER] retry took 312ms
Final Result: Success! */