Why is Sonarqube telling me await is redundant in async function
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.
Introduction
SonarQube flags await as redundant when an async function's last statement is return await somePromise(). Since async functions automatically wrap their return value in a Promise, return promise and return await promise produce the same result to the caller. The await adds unnecessary overhead — it unwraps the Promise then immediately re-wraps it. However, there is one important exception: inside a try/catch block, return await is necessary to catch rejections. SonarQube rule S4326 (or the ESLint equivalent no-return-await) targets this pattern.
The Redundant Pattern
Both versions return a Promise that resolves to the fetch result. The await in the first version unwraps the Promise, then the async function immediately wraps the result back into a Promise. This round-trip is unnecessary.
Why It Matters
Modern JavaScript engines (V8, SpiderMonkey) optimize away the extra microtask in many cases, so the performance difference is minimal. The main benefit of removing redundant await is cleaner, more intentional code.
When await IS Required: try/catch
Without await, the Promise is returned before it settles — the rejection happens outside the try/catch scope. With await, the function waits for the Promise to resolve or reject, allowing the catch block to handle errors. This is the one case where return await is necessary and correct.
SonarQube Rule Details
SonarQube and ESLint are smart enough to not flag return await inside try/catch blocks, because the await is necessary there.
ESLint Equivalent
The @typescript-eslint/return-await rule with "in-try-catch" is the most precise: it requires await inside try/catch and forbids it elsewhere.
Common Patterns and Fixes
Stack Traces Consideration
In older JavaScript engines, removing await causes the calling function to disappear from error stack traces. Modern engines with async stack traces have largely solved this, but it remains a consideration for some environments.
Common Pitfalls
- Removing
awaitinsidetry/catch: If you havetry { return await promise; } catch (e) { ... }and remove theawait, thecatchblock will never execute for Promise rejections. The rejection propagates to the caller instead of being handled locally. - Removing
awaitinsidetry/finally:finallyblocks execute before the returned value is resolved. Withoutawait, thefinallyblock runs before the Promise settles, which can cause cleanup to happen at the wrong time. - Blindly applying the rule to all files: Auto-fixing
no-return-awaitacross a codebase without checking fortry/catchcontext can introduce bugs. Use@typescript-eslint/return-awaitwith"in-try-catch"mode for safe enforcement. - Confusing with non-async functions:
return awaitin a non-asyncfunction is a syntax error (you cannot useawaitoutsideasync). SonarQube's rule only applies toasyncfunctions. - Assuming performance improvement is significant: The micro-optimization of removing one microtask tick is negligible in real applications. The primary benefit is code clarity and expressing intent — "I'm passing through this Promise" rather than "I'm awaiting and re-returning this value."
Summary
return await promiseis redundant inasyncfunctions — just usereturn promise- The one exception:
return awaitis required insidetry/catchto catch Promise rejections return awaitis also needed intry/finallyfor correct execution order- SonarQube rule S4326 and ESLint's
no-return-awaitflag this pattern - Use
@typescript-eslint/return-awaitwith"in-try-catch"for the best enforcement - The performance difference is negligible — the benefit is cleaner, more intentional code
Related reading
- Why is sorting a string On log n?
- Why is squaring a number faster than multiplying two random numbers?
- Why is stdrotate so fast?
- Why is swap sometimes implemented by passing an array?
- Why is spawning threads in Java EE container discouraged?
- Why is synchronized block better than synchronized method?
- Why is TaskT faster than ValueTaskT?
- Why is Tensorflow 100x slower than convnetjs in this simple NN example?

DSA Fundamentals
Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Data Structures & Algorithms practice on Codemia
Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.