Async Programming Explained: Promises, Futures, and Coroutines for Beginners

Last updated: ⏱ Reading time: ~20 minutes

AI-assisted guide Curated by Norbert Sowinski

Share this guide:

Diagram-style illustration of asynchronous programming showing synchronous execution, event loops, schedulers, promises, futures, coroutines, async await, background I/O, task suspension, resumption, cancellation, timeouts, and error propagation

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:

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)

Asynchronous programming execution model showing application task, async I/O request, suspension, event loop or scheduler, other runnable tasks, operating system or external service completion, ready queue, task resumption, and final result

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:

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)

Comparison of promises, futures, tasks, and coroutines showing eventual result states, completion, chaining, awaiting, suspension, resumption, error propagation, cancellation, and how async await provides sequential-looking syntax over asynchronous operations

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:

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:

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

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 programming debugging flow showing async call, promise future or coroutine creation, suspension point, scheduler, dependency operation, completion or failure, resumption, cancellation and timeout checks, error propagation, caller handling, logging, and root-cause investigation

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.

Found this useful? Share this guide: