Asynchronous programming becomes easier once you stop thinking of it as:
code running magically
in the background
and instead think:
start work
if it must wait:
suspend this operation
let something else progress
resume when the result
is available
The motivation is especially clear for I/O.
A network request may spend:
2 ms preparing request
200 ms waiting for network
3 ms parsing result
Keeping an important execution thread blocked during the entire:
205 ms
may be unnecessary.
Async does not automatically mean another thread
An asynchronous operation may use an event loop, operating-system I/O completion, a scheduler, a thread pool, a worker process, or a combination of mechanisms. The important abstraction is that the caller can wait without necessarily blocking the execution resource that could be doing other useful work.
1. Understand why asynchronous programming exists
Consider a server handling one request.
receive request
↓
query database
↓
wait 80 ms
↓
call payment API
↓
wait 300 ms
↓
send response
Much of the request lifetime is:
waiting
rather than:
computing
Synchronous blocking model
call database
execution thread waits
database returns
continue
Asynchronous model
start database operation
suspend current task
scheduler runs other work
database completes
resume suspended task
Async is especially useful for I/O-bound work
Examples:
- HTTP requests.
- Database calls.
- Timers.
- File operations.
- Message queues.
- Socket communication.
- User-interface events.
CPU-bound work is different
Suppose:
calculatePrimeNumbers()
consumes:
5 seconds of CPU
adding:
async
does not remove those five seconds of computation.
CPU-heavy work may instead need:
worker thread
thread pool
process pool
parallel algorithm
separate service
depending on the runtime and workload.
2. Separate blocking, non-blocking, concurrency, and parallelism
These terms are related but not interchangeable.
Blocking
call operation
current execution context
cannot continue useful work
wait until operation finishes
Non-blocking
start operation
execution resource remains
available for other work
Concurrency
Multiple tasks make progress during overlapping periods.
Task A:
work → wait → work
Task B:
work → wait → work
Parallelism
Multiple operations execute at the same instant.
CPU core 1:
Task A
CPU core 2:
Task B
Concurrency does not require parallel execution
A single-threaded event loop can manage:
thousands of waiting sockets
while executing one piece of application code at a time.
Async and multithreading are not synonyms
You can have:
async + single thread
async + thread pool
threads without async APIs
parallel workers + async I/O
depending on architecture.
3. Understand event loops and schedulers
Async execution model (diagram)
A simplified event-driven runtime can be imagined as:
ready tasks
↓
event loop
↓
run task
task reaches await
↓
operation not complete
↓
suspend task
run another task
Later
I/O completes
↓
runtime marks task ready
↓
scheduler selects task
↓
task resumes after await
Suspension is not the same as blocking
Suspended:
this logical task is waiting
Blocked:
the execution resource
itself may be unable
to perform other work
Schedulers choose what runs next
Depending on the runtime, scheduling may involve:
- An event loop.
- A ready queue.
- Worker threads.
- Executor services.
- Dispatch queues.
Do not depend on exact thread identity unless guaranteed
Some runtimes may resume a continuation:
on the same thread
while others may resume:
on a scheduler-selected thread
or on a specific UI executor.
Follow your platform's execution rules instead of assuming:
after await
=
same thread
4. Think of promises as future outcomes
A promise represents an operation whose final outcome may not exist yet.
Conceptual states
Pending
↓
Fulfilled
with value
or
Rejected
with error
Example idea
promise = fetchUser()
At this instant:
user may not exist yet
as a completed result.
Instead:
promise represents
the eventual outcome
Continuation style
fetchUser()
.then(useUser)
.catch(handleError)
Conceptually:
when result exists:
run next operation
if failure occurs:
run error handler
Promises can be chained
fetchUser()
↓
fetchOrders(user)
↓
calculateSummary(orders)
↓
render(summary)
Each step can produce another future outcome.
Do not confuse promise creation with completion
result = fetchUser()
may give:
Promise<User>
rather than:
User
5. Understand futures and tasks
A future also represents:
a value or failure
that may arrive later
Different ecosystems use names such as:
Future
Task
Deferred
Promise
CompletableFuture
with different exact APIs.
Do not over-focus on terminology
For beginners, ask:
Can this object complete later?
Can I await it?
Can it fail?
Can it be cancelled?
Can I combine it
with other operations?
Some APIs distinguish producer and consumer sides
Conceptually:
Promise:
can complete value
Future:
can observe eventual value
while another language may use:
Future
for the entire abstraction.
Task abstractions may represent active execution
A task can combine:
future result
+
execution lifecycle
+
cancellation
+
scheduler integration
depending on the platform.
6. Understand coroutine suspension and resumption
A coroutine is a computation that can:
run
suspend
resume
suspend again
finish
Conceptual example
coroutine loadDashboard():
user = await loadUser()
orders = await loadOrders(user)
return buildDashboard(user, orders)
At the first await
loadUser not finished
↓
coroutine suspends
The runtime stores continuation state
It needs enough information to know:
where to resume
which local state matters
how to continue control flow
When the operation completes
user result becomes ready
↓
coroutine scheduled again
↓
execution resumes
after await
A suspended coroutine usually does not require a dedicated blocked thread
This is one reason coroutine-based systems can efficiently represent many waiting operations.
Coroutines are not automatically concurrent
This:
user = await loadUser()
orders = await loadOrders()
may still run sequentially:
load user completely
then
load orders completely
7. Read async and await as control flow
Promises, futures, and coroutines comparison (diagram)
The easiest mental model for:
await operation()
is:
start or observe operation
if result ready:
continue immediately
if not:
suspend this async flow
resume later with
value or error
Sequential-looking does not mean synchronous blocking
user = await fetchUser()
print(user.name)
looks sequential because:
print
logically depends on:
user
but the runtime may execute other work while:
fetchUser()
is waiting.
Await usually propagates failure
Conceptually:
result = await future
means either:
result receives value
or:
failure is raised
at await point
This allows ordinary error-handling structure
try:
user = await fetchUser()
orders = await fetchOrders(user)
catch error:
handle(error)
Exact syntax differs by language, but the idea is common.
8. Run independent operations concurrently
Consider:
profile = await loadProfile()
messages = await loadMessages()
If:
loadMessages()
does not depend on:
profile
this may unnecessarily serialize two independent operations.
Sequential
load profile:
300 ms
then
load messages:
400 ms
total:
about 700 ms
Concurrent
start profile
start messages
wait for both
idealized total:
about 400 ms
ignoring overhead and resource contention.
Fan-out
start A
start B
start C
Fan-in
wait until required
results are available
But concurrency is not free
This is dangerous:
for 1,000,000 items:
start async request
because it can exhaust:
- Memory.
- Connections.
- Sockets.
- File handles.
- Remote API quotas.
- Database connection pools.
Use bounded concurrency
Conceptually:
1,000 jobs
maximum:
10 active at once
through:
semaphore
worker pool
bounded queue
concurrency limiter
Backpressure matters
If producers generate:
10,000 events / second
and consumers process:
2,000 events / second
an unbounded queue eventually becomes a memory problem.
9. Handle errors without losing them
One of the most dangerous async patterns is:
start operation
forget about it
Awaited work
Usually has clear ownership:
caller
↓
await child
↓
receive result
or error
Detached work
Can become:
fire
and
forget
which raises questions:
Who observes failure?
Who cancels it?
How long may it run?
What happens when owner exits?
Do not silently discard promise or task failures
This can create:
unhandled rejection
unobserved task failure
background exception
silent partial operation
Aggregate operations have failure semantics too
If:
A succeeds
B fails
C succeeds
decide whether the parent operation should:
fail immediately
wait for all
return partial results
collect all failures
according to product semantics.
Preserve original errors
Weak:
catch:
throw "request failed"
Better conceptually:
throw higher-level context
while preserving original cause
so debugging retains:
operation context
+
technical cause
10. Design cancellation and timeouts deliberately
Suppose a user starts:
search for "laptop"
then immediately searches:
search for "monitor"
The first request may no longer be useful.
Cancellation allows obsolete work to stop
request A starts
request B starts
A becomes irrelevant
cancel A
Cancellation is often cooperative
The runtime may signal:
please stop
but application code and called APIs must reach cancellation-aware points.
Clean up in finally / defer-style blocks
open resource
try:
await work
finally:
close resource
so cancellation does not leak:
- Connections.
- Locks.
- Temporary files.
- Subscriptions.
Timeout is a policy
if operation takes
more than 5 seconds:
stop waiting
but ask:
Did we only stop waiting?
or
did underlying work
actually stop?
Timeouts should match operation semantics
A:
100 ms
timeout may be reasonable for one local cache but disastrous for a remote operation over a mobile network.
Propagate cancellation where ownership requires it
parent request cancelled
↓
database query
HTTP request
child transformation
↓
cancel where possible
11. Keep task lifetimes structured
Unstructured async code can create work that outlives the operation that created it.
Example
handleRequest():
start backgroundTask()
return response
What owns:
backgroundTask
after:
handleRequest()
has finished?
Structured concurrency ties child work to a scope
parent scope
|
+-- child A
|
+-- child B
|
+-- child C
Before the scope finishes:
children finish
or
children are cancelled
Benefits
- Clear ownership.
- Predictable cancellation.
- Better error propagation.
- Fewer orphaned tasks.
- Easier reasoning about lifetime.
Long-lived background work still needs an owner
Some tasks genuinely outlive a request or screen:
queue consumer
scheduled worker
application-level telemetry
background synchronization
Give them explicit:
service lifetime
supervisor
shutdown behavior
error policy
rather than launching them accidentally.
12. Avoid common async programming mistakes
Forgetting to await
saveUser()
return success
may return before:
saveUser()
finishes.
Accidental serialization
a = await loadA()
b = await loadB()
when A and B are independent.
Unbounded fan-out
start one task
for every item
without limit
can overwhelm both your application and downstream systems.
Blocking inside async code
An event-loop or UI thread can still be blocked by:
CPU-heavy loop
blocking file call
blocking database API
sleep
synchronous network call
Assuming async means parallel
A single-threaded event loop may execute callbacks one at a time.
Assuming no race conditions because one thread is used
Interleaving across await points can still create logic races.
Example:
balance = readBalance()
await externalCheck()
writeBalance(balance - 10)
Another task may change the balance while the first task is suspended.
Holding locks across await
This can increase:
contention
deadlock risk
latency
depending on the synchronization primitive.
Ignoring cancellation
An obsolete operation can continue consuming:
CPU
network
database connections
memory
Swallowing cancellation as ordinary error
Some runtimes represent cancellation through special exceptions or result states.
Treat:
user cancelled
differently from:
server failed
where the runtime's model requires it.
Fire-and-forget without supervision
Detached work needs explicit:
ownership
logging
error handling
shutdown policy
13. Debug asynchronous code systematically
Async execution and debugging flow (diagram)
Async bugs often involve:
time
ordering
ownership
shared state
cancellation
rather than one obviously incorrect line.
Record operation identity
Useful metadata:
request ID
task ID
job ID
operation name
resource ID
attempt number
while avoiding sensitive information.
Record lifecycle events
task created
task started
waiting for database
database completed
task resumed
task cancelled
task failed
task finished
Find the suspension boundary
Ask:
What was this task awaiting
when progress stopped?
Inspect dependency state
A task waiting forever may actually be waiting on:
connection pool
lock
message
remote API
queue slot
child task
Check concurrency limits
Example:
database pool:
10 connections
10 tasks:
holding connection
while awaiting another operation
new queries:
waiting forever
Use controlled delays to reproduce races
Tests can intentionally delay:
operation A
before write
operation B
after read
to expose ordering bugs reliably.
Test failures explicitly
success
timeout
cancellation
dependency failure
partial failure
retry
duplicate completion
Read async stack traces carefully
Runtimes may include:
await boundary
continuation
scheduler
promise callback
coroutine resume
in ways that differ from ordinary synchronous stacks.
Trace the logical operation rather than expecting one continuous physical stack.
Use one hypothesis at a time
Good:
The request hangs because
all database connections
are held by tasks awaiting
a downstream API.
Then verify:
pool usage
task stacks
connection lifetime
downstream latency
14. Copy/paste async programming checklist
Async programming checklist
Fundamentals
- Identify whether work is I/O-bound or CPU-bound.
- Do not use async syntax as a performance spell.
- Understand what waits.
- Understand what continues running.
- Understand what scheduler or runtime is involved.
- Distinguish suspension from blocking.
- Distinguish concurrency from parallelism.
Blocking
- Identify synchronous network calls.
- Identify blocking database calls.
- Identify blocking file I/O.
- Identify sleep calls.
- Identify CPU-heavy loops.
- Avoid blocking important event-loop threads.
- Avoid blocking UI threads.
- Move CPU-heavy work to appropriate workers where needed.
Non-blocking I/O
- Use runtime-supported async APIs.
- Await operation completion.
- Propagate errors.
- Propagate cancellation.
- Close resources correctly.
- Set appropriate timeouts.
Promises
- Understand pending state.
- Understand fulfilled state.
- Understand rejected state.
- Chain dependent operations.
- Handle rejection.
- Do not forget returned promises.
- Avoid deeply nested callback-style chains where better syntax exists.
Futures
- Understand future result.
- Understand completion semantics.
- Understand failure semantics.
- Understand cancellation support.
- Avoid blocking get / wait on critical async thread.
- Combine futures intentionally.
- Observe every failure.
Tasks
- Give tasks clear ownership.
- Know when task starts.
- Know when task completes.
- Know whether task is lazy or eager.
- Know cancellation semantics.
- Know how errors propagate.
- Avoid orphaned tasks.
Coroutines
- Understand suspension points.
- Understand resumption.
- Do not assume suspension blocks a thread.
- Know which dispatcher / executor is used where relevant.
- Keep blocking code out of inappropriate coroutine contexts.
- Propagate cancellation.
- Use structured scopes where available.
Async / await
- Await operations whose result is required.
- Do not await too early when independent work can overlap.
- Do not forget await accidentally.
- Handle errors around meaningful operation boundaries.
- Do not assume same thread after await unless guaranteed.
- Keep sequential-looking logic understandable.
Sequential dependencies
- Operation B depends on A.
- Await A.
- Validate A result.
- Start B.
- Await B.
- Keep dependency explicit.
- Avoid fake concurrency when ordering is required.
Independent operations
- Start independent work before awaiting.
- Await together where appropriate.
- Define failure semantics.
- Define cancellation semantics.
- Limit concurrency.
- Avoid overwhelming downstream systems.
Fan-out
- Define task count.
- Define maximum active concurrency.
- Use semaphore or worker pool where useful.
- Avoid one unbounded task per item.
- Handle per-item failures.
- Support cancellation.
- Track progress.
Fan-in
- Decide whether all results are required.
- Decide whether first result is sufficient.
- Decide whether first failure stops group.
- Decide whether partial success is allowed.
- Collect errors where appropriate.
- Cancel unnecessary remaining work.
Bounded concurrency
- Set maximum active tasks.
- Size limit to downstream capacity.
- Consider database pool size.
- Consider API quotas.
- Consider memory.
- Consider sockets.
- Monitor queue depth.
- Add backpressure.
Backpressure
- Measure producer rate.
- Measure consumer rate.
- Bound queues.
- Reject, delay, batch, or shed work when overloaded.
- Avoid infinite buffers.
- Monitor backlog.
- Define overload behavior.
CPU-bound work
- Do not assume await makes CPU work non-blocking.
- Use worker thread where appropriate.
- Use process pool where appropriate.
- Use parallel algorithm where appropriate.
- Limit worker count.
- Measure context-switch overhead.
- Keep event loop responsive.
Thread pools
- Avoid blocking every pool thread.
- Monitor pool saturation.
- Avoid nested blocking waits.
- Size carefully.
- Distinguish I/O tasks from CPU tasks where runtime supports it.
- Avoid creating unlimited threads.
Event loop
- Keep callbacks short.
- Avoid blocking I/O.
- Avoid CPU-heavy work.
- Understand ready queue.
- Understand timers.
- Understand I/O completion.
- Monitor event-loop lag where relevant.
Schedulers
- Know default scheduler.
- Know UI scheduler if applicable.
- Know worker scheduler.
- Do not assume execution order beyond guarantees.
- Avoid relying on accidental thread affinity.
Shared mutable state
- Identify values accessed by multiple tasks.
- Avoid read-modify-write races.
- Use appropriate synchronization.
- Prefer immutable data where practical.
- Prefer message passing where useful.
- Keep critical sections small.
Race conditions
- Test different completion orders.
- Delay operation A.
- Delay operation B.
- Cancel during transition.
- Retry during transition.
- Verify state invariants.
- Avoid relying on timing.
Locks
- Avoid holding ordinary blocking locks across await unless explicitly safe.
- Understand async-aware synchronization primitives.
- Keep lock scope small.
- Avoid network I/O while holding lock.
- Avoid database I/O while holding unrelated lock.
- Define consistent lock ordering.
Deadlocks
- Avoid sync-over-async waits.
- Inspect held locks.
- Inspect waiting tasks.
- Inspect thread-pool exhaustion.
- Inspect callback dependencies.
- Capture task and thread states.
- Remove circular wait.
Promises / futures and errors
- Await or observe every operation.
- Preserve original causes.
- Add useful operation context.
- Avoid generic error wrapping.
- Distinguish timeout from cancellation.
- Distinguish user cancellation from failure.
- Avoid silent background errors.
Unhandled failures
- Configure runtime reporting.
- Monitor rejected promises.
- Monitor failed tasks.
- Log supervised background failures.
- Do not rely on garbage collection to surface errors.
- Test failure path.
Fire-and-forget
- Use only when intentional.
- Give task an owner.
- Handle failure.
- Handle shutdown.
- Handle cancellation.
- Add logging / monitoring.
- Avoid fire-and-forget for critical writes.
Cancellation
- Accept cancellation signal / token where appropriate.
- Propagate to child work.
- Check cancellation at useful boundaries.
- Clean up resources.
- Avoid swallowing cancellation.
- Make cancellation idempotent where possible.
- Define user-visible behavior.
Cancellation cleanup
- Close files.
- Release locks.
- Return database connections.
- Unsubscribe listeners.
- Remove temporary resources.
- Avoid committing partial state unintentionally.
- Use finally / defer patterns.
Timeouts
- Set deliberate timeout.
- Avoid one universal timeout for everything.
- Understand whether timeout cancels underlying work.
- Cancel underlying operation when appropriate.
- Distinguish connect timeout.
- Distinguish read timeout.
- Distinguish overall operation timeout.
Retries
- Retry only appropriate failures.
- Add backoff.
- Add jitter where distributed systems need it.
- Limit attempts.
- Respect cancellation.
- Respect overall deadline.
- Ensure operation is safe to retry.
- Avoid retry storms.
Idempotency
- Use stable operation ID where needed.
- Make duplicate requests safe.
- Avoid double payment.
- Avoid duplicate message processing.
- Test retry after timeout.
- Test ambiguous completion.
Structured concurrency
- Keep child tasks inside parent scope.
- Wait for child completion.
- Cancel children when parent fails where semantics require it.
- Propagate errors.
- Avoid orphan work.
- Use supervisor semantics only deliberately.
Background services
- Give service explicit lifetime.
- Start intentionally.
- Stop gracefully.
- Handle failure.
- Restart only according to policy.
- Drain queues where appropriate.
- Monitor health.
UI applications
- Keep UI thread responsive.
- Cancel obsolete screen requests.
- Avoid applying stale responses.
- Verify screen still exists before update.
- Handle navigation during request.
- Handle repeated taps.
- Disable duplicate submissions where needed.
Search requests
- Cancel previous query when obsolete.
- Debounce where useful.
- Ignore stale result if cancellation cannot stop it.
- Tag result with request identity.
- Avoid older response replacing newer response.
Network requests
- Set timeout.
- Propagate cancellation.
- Handle offline.
- Handle DNS failure.
- Handle TLS failure.
- Handle server error.
- Handle malformed response.
- Avoid uncontrolled retry.
Database
- Match concurrency to connection pool.
- Keep transaction scope short.
- Avoid holding connection while waiting on unrelated remote service.
- Propagate timeout.
- Propagate cancellation.
- Handle deadlocks and retries appropriately.
- Close / return connections.
File I/O
- Use asynchronous API where beneficial.
- Avoid reading huge files at once.
- Stream large content.
- Handle cancellation.
- Handle partial reads.
- Close resource.
- Handle disk-full errors.
Streams
- Define producer.
- Define consumer.
- Bound buffers.
- Handle completion.
- Handle errors.
- Handle cancellation.
- Avoid leaking subscriptions.
Callbacks
- Avoid callback nesting where higher-level abstraction helps.
- Ensure callback fires once where contract says once.
- Handle error callback.
- Handle cancellation.
- Preserve operation context.
- Avoid calling completion twice.
Promise chains
- Return nested promise / future correctly.
- Avoid forgetting return in chain.
- Handle rejection once at meaningful boundary.
- Avoid duplicate error handling.
- Keep chain readable.
Coroutine scopes
- Choose scope matching owner lifetime.
- Do not use global scope casually.
- Cancel scope when owner disappears.
- Keep child lifetime visible.
- Avoid leaking screen / request tasks.
Resource lifetime
- Open resource as late as practical.
- Release as early as practical.
- Avoid holding scarce resource across unrelated await.
- Use structured cleanup.
- Test cancellation during resource use.
Testing
- Test immediate success.
- Test delayed success.
- Test dependency failure.
- Test timeout.
- Test cancellation.
- Test concurrent completion order.
- Test duplicate completion.
- Test retry.
Deterministic tests
- Use fake clock where possible.
- Use controllable scheduler where available.
- Use fake network.
- Use controlled futures / promises.
- Avoid real sleep.
- Trigger completion explicitly.
- Keep task ordering intentional.
Async test mistakes
- Do not finish test before async work completes.
- Await tested operation.
- Observe background failures.
- Avoid arbitrary sleeps.
- Use eventual assertions with bounded timeout where necessary.
- Reset global state.
Debugging
- Capture task identity.
- Capture operation identity.
- Capture timestamp.
- Capture dependency being awaited.
- Capture cancellation state.
- Capture timeout state.
- Capture concurrency limit.
- Capture queue depth.
Async stack traces
- Identify suspension boundaries.
- Identify scheduler frames.
- Identify callback frames.
- Identify coroutine continuation.
- Identify promise chain.
- Follow logical operation across boundaries.
- Use correlation IDs.
Hanging tasks
- What is task awaiting?
- Is dependency alive?
- Is lock held?
- Is queue full?
- Is connection pool exhausted?
- Did callback never fire?
- Did completion signal get lost?
- Is task waiting on itself indirectly?
Slow async operations
- Measure total duration.
- Measure active CPU duration.
- Measure wait duration.
- Measure queue delay.
- Measure downstream latency.
- Measure pool wait.
- Separate scheduling delay from I/O delay.
Logging
- Log operation start.
- Log important suspension boundary where useful.
- Log completion.
- Log cancellation.
- Log failure.
- Avoid excessive per-await logging.
- Keep correlation context.
- Avoid secrets.
Observability
- Track in-flight operations.
- Track queue depth.
- Track timeout rate.
- Track cancellation rate.
- Track dependency latency.
- Track pool utilization.
- Track event-loop lag where relevant.
- Track retry volume.
Performance
- Avoid creating unnecessary tasks.
- Batch small operations where useful.
- Limit fan-out.
- Reuse connections.
- Stream large data.
- Measure before optimizing.
- Remember concurrency can increase contention.
Common mistakes
- Forgetting await.
- Blocking event loop.
- Blocking UI thread.
- Sequentially awaiting independent work.
- Starting unlimited tasks.
- Losing background exceptions.
- Ignoring cancellation.
- Using timeout without stopping underlying work.
- Holding scarce resource across unrelated await.
- Assuming same thread after await.
- Updating shared mutable state unsafely.
- Using async for CPU work without appropriate executor.
Code review
- What can suspend?
- Who owns each task?
- Who observes errors?
- Who cancels work?
- What happens on timeout?
- What happens if parent exits?
- Is concurrency bounded?
- Can operations finish in a different order?
- Is shared state safe?
- Are resources released?
Final review
- Is the workload I/O-bound or CPU-bound?
- Does async actually avoid blocking useful execution?
- Are dependent operations awaited sequentially?
- Are independent operations allowed to overlap where beneficial?
- Is concurrency bounded?
- Are all task failures observed?
- Is cancellation propagated?
- Do timeouts cancel underlying work where appropriate?
- Are child lifetimes structured?
- Are shared-state races handled?
- Are blocking calls kept off critical async threads?
- Are retries safe and bounded?
- Are scarce resources released before unrelated waits?
- Can tests control timing without arbitrary sleeps?
- Can logs connect events across async boundaries?
- Does the design remain understandable when operations fail, cancel, or complete in an unexpected order?
15. FAQ
What is asynchronous programming?
It is a programming model where an operation can begin, wait for something such as I/O, and later continue without necessarily blocking the execution resource that could be performing other useful work.
What is the difference between a promise and a future?
Both generally represent an outcome that will become available later. The terminology differs between languages and libraries. Some APIs distinguish a writable promise from a read-only future, while others use a single abstraction.
What is a coroutine?
A coroutine is a computation that can suspend at defined points and resume later. This makes it possible to express asynchronous control flow in a style that often resembles ordinary sequential code.
Does await block a thread?
Not necessarily. For genuinely asynchronous operations, await commonly suspends the logical task and lets the runtime use the execution resource for something else. Exact behavior depends on the language, runtime, and API being awaited.
Is async the same as multithreading?
No. Async describes how operations can suspend and resume around waiting. Multithreading describes the use of multiple threads. They can be combined, but an event-loop application can handle substantial asynchronous concurrency on one thread.
When should independent async operations run concurrently?
When they do not depend on one another and overlapping their waiting time provides a benefit. Still apply concurrency limits so you do not overload memory, connection pools, remote services, or other shared resources.
Why is structured concurrency useful?
It keeps child operations tied to a parent scope, making completion, cancellation, failure propagation, and task ownership easier to reason about.
Key terms (quick glossary)
- Asynchronous programming
- A programming model that allows operations to suspend while waiting and later resume when their result or next event becomes available.
- Blocking
- Preventing an execution resource from performing other useful work while an operation waits.
- Non-blocking
- Allowing an execution resource to remain available while an operation is pending.
- Concurrency
- Multiple tasks making progress during overlapping periods of time.
- Parallelism
- Multiple operations executing at the same instant on different processing resources.
- Promise
- An abstraction representing a future completion that can usually succeed with a value or fail with an error.
- Future
- An object representing a result that may become available later.
- Task
- A runtime abstraction that may represent active asynchronous execution, its eventual result, cancellation, and scheduling state.
- Coroutine
- A suspendable computation that can pause at defined suspension points and resume later.
- await
- A language construct or operation that waits for an asynchronous result by suspending the current async flow when necessary.
- Event loop
- A runtime loop that processes runnable work and events such as completed I/O, timers, or callbacks.
- Scheduler
- A runtime component that decides when and where runnable tasks or continuations execute.
- Suspension point
- A location where a coroutine or async task may pause and later resume.
- Cancellation
- A request for an in-progress operation to stop because its result is no longer needed or the owning operation is ending.
- Timeout
- A time limit after which an operation or wait is considered too slow and an alternative failure or cancellation path is taken.
- Structured concurrency
- An approach where child asynchronous operations remain tied to a parent scope so lifetime, errors, and cancellation have clear ownership.
- Backpressure
- A mechanism for slowing, limiting, buffering, rejecting, or otherwise controlling incoming work when consumers cannot keep up.
- Race condition
- A defect where program correctness depends on the relative timing or ordering of concurrent operations.
Worth reading
Recommended guides from the category.