Async / Lambda / Threading Reference
Language-by-language reference for async/await, lambdas, multithreading, and error handling. Real examples in JS, TS, Python, Go, Rust, Java, and C#.
Showing 4 examples for JavaScript / Lambda / Arrow
Arrow Functions
Concise function syntax that lexically binds `this`.
Array Methods with Lambdas
map, filter, reduce — the core functional trio.
Closures
Functions that capture variables from their outer scope.
Currying & Partial Application
Transform multi-arg functions into chains of single-arg functions.
How Async/Await Works Under the Hood
Despite different syntax across languages, async/await in JavaScript, Python, Rust, C#, and Java all share the same fundamental mechanism: a state machine. When the compiler encounters an async function, it transforms it into a state machine object that can be suspended at each await point and resumed when the awaited future/promise/task completes. The calling thread is freed to do other work while the suspension is active — this is why async/await is non-blocking without creating new threads.
In JavaScript, this state machine runs on the single-threaded event loop — there is no parallelism, only concurrency. In C# and Java, the continuation (code after await) may resume on a different thread pool thread. In Rust, async functions return impl Future which is inert until polled by a runtime like Tokio — the runtime decides when and where to poll each future.
Lambdas vs. Closures: What's the Difference?
A lambda is an anonymous function — a function without a name, defined inline. A closure is a function that captures variables from its surrounding scope. In most languages, lambdas are implemented as closures, but the terms have different emphasis. JavaScript arrow functions, Python lambda expressions, Go anonymous functions, Rust closures, Java lambdas, and C# lambdas are all both — anonymous functions that can capture their enclosing scope.
The key difference between languages is capture semantics. JavaScript captures variables by reference (shared binding). Python captures variables by reference but only the binding, not the value — a classic gotcha in loops. Go closures capture by reference too, requiring the url := url loop-variable capture pattern. Rust closures are explicit: the compiler tells you whether you are capturing by reference (Fn), mutable reference (FnMut), or by value (move).
Choosing the Right Concurrency Primitive
Use async/await for I/O-bound work: network requests, database queries, file reads. It is efficient (no thread creation overhead) and composable (Promise.all, Task.WhenAll, asyncio.gather). Avoid async for CPU-bound work — an async function that does heavy computation without yielding blocks the event loop or thread for its entire duration.
Use threads/goroutines for CPU-bound work that needs actual parallelism. In Go, goroutines are so cheap that you can spawn one per task without a pool. In Java and C#, prefer thread pool abstractions (ExecutorService, Task, Parallel.ForEach) over raw threads. In Python, use multiprocessing (not threading) for CPU parallelism due to the GIL. In Rust, both std::thread and Rayon are excellent options — Rayon's par_iter() is the simplest choice for data parallelism.
Frequently Asked Questions
What is the difference between concurrency and parallelism?
Concurrency means managing multiple tasks that can overlap in time — they may not literally run simultaneously but are interleaved (e.g., async/await in JavaScript on a single thread). Parallelism means tasks literally execute at the same instant on multiple CPU cores (e.g., Go goroutines on multiple OS threads, Rust's Rayon, Java's ForkJoinPool). JavaScript and Python's asyncio are concurrent but not parallel for CPU-bound work. Go, Rust threads, Java threads, and C# Tasks are both concurrent and potentially parallel.
Why does Python's multithreading not speed up CPU-bound code?
Python's CPython interpreter has a Global Interpreter Lock (GIL) — a mutex that allows only one thread to execute Python bytecode at a time. This means Python threads cannot run in parallel on multiple CPU cores for CPU-bound work, even on a multi-core machine. For I/O-bound work (network requests, file reads), threads still improve throughput because the GIL is released during I/O waits. For CPU parallelism in Python, use multiprocessing.Pool (separate processes, each with their own GIL) or a C extension like NumPy that releases the GIL internally.
When should I use goroutines vs. a thread pool in Go?
Go's goroutines are extremely lightweight (starting at ~2KB stack, multiplexed onto OS threads by the Go runtime). You can safely spawn thousands or even millions of goroutines. Unlike Java or C# thread pools, you rarely need to limit goroutine creation manually. Use channels and sync.WaitGroup for coordination. The Go runtime's scheduler (GOMAXPROCS controls parallelism, defaulting to the number of CPU cores) handles efficient scheduling automatically. Thread pools are a Java/C# pattern that compensates for the high cost of OS threads — not necessary in Go.
What is the difference between Rust's Fn, FnMut, and FnOnce?
These are Rust's three closure traits that describe how a closure captures its environment. FnOnce can be called at most once — it moves captured variables into itself (must take ownership). FnMut can be called multiple times and captures by mutable reference — it can mutate captured variables. Fn can be called multiple times and only captures by shared reference — it cannot mutate captured variables. Every closure implements at least FnOnce; closures that don't move captured values also implement FnMut; and closures that only read captured values also implement Fn. When writing a function that accepts a closure, prefer the most general bound (Fn > FnMut > FnOnce).
How does C#'s CancellationToken work with async tasks?
CancellationToken implements cooperative cancellation — the calling code creates a CancellationTokenSource, passes its Token to async methods, and calls Cancel() when it wants to stop. The async method is responsible for periodically checking ct.ThrowIfCancellationRequested() or passing the token to awaitable operations (HttpClient.GetAsync, Task.Delay, etc.) which will throw OperationCanceledException if cancelled. This is cooperative — the method decides when to check for cancellation, which prevents abrupt resource leaks. A common pattern is to link CancellationTokenSource.CancelAfter(timeout) for deadline-based cancellation.