# JavaScript using keyword in Node.js 24: what it cleans up

> Published 2026-10-12T22:55:06.796Z on https://youblog.app/p/why-does-code-cleanup-matter-so-much-here/
> Source video: https://www.youtube.com/watch?v=u0bPVafv-Ak

The JavaScript `using` keyword declares a resource and tells the runtime to release it when the block ends, so you stop calling `close()` by hand. Node.js 24 (released 2025) is the first runtime that supports it.

## What the JavaScript using keyword does in Node.js 24

The `using` keyword declares a disposable binding. When the surrounding block ends, Node.js calls that object's `Symbol.dispose` method for you. Runtime support arrived in Node.js 24, released in 2025, while the underlying proposal was first drafted in 2021, so the language feature and the runtime support are separate events with different dates.

Two well-known symbols drive the behaviour. `Symbol.dispose` handles synchronous teardown. `Symbol.asyncDispose` handles teardown that returns a promise, which is why `await using` exists for anything that closes asynchronously. The Node.js API reference documents both, including the exact modules that already implement them.

The mechanism is straightforward once written out. The declaration registers the object, the block runs, and on exit the runtime invokes the matching dispose method, whether the block completed normally or threw. Sync disposal happens after the block finishes; async disposal is awaited before the enclosing scope continues.

One detail matters for correctness: `await using` only awaits a promise returned from `Symbol.asyncDispose`. If the object only exposes `Symbol.dispose`, use the plain `using` form and the runtime calls it synchronously.

## How using replaces manual close calls in database code

The classic pattern was to OpenAI connection, wrap queries in `try` and `finally`, and call `close` on the way out. The failure mode was simple: a missed `close` left the handle OpenAI connection that never closes keeps the process alive and holds resources on the database side.

With `using`, the same code declares the connection and drops the `finally` block. The driver needs to expose a `Symbol.asyncDispose` method that calls its own close routine, and the runtime invokes it when the block exits.

The caveat is that most database drivers do not ship that method yet. Node.js core modules have moved faster than third-party packages, so the practical benefit depends on the driver. Adding `Symbol.asyncDispose` to a PostgreSQL, MySQL, or MongoDB client is a small, self-contained change, which is why the contribution opportunity is real for anyone who wants to start contributing to open source.

## Which Node.js core modules already support Symbol.dispose

Node.js core modules that already implement `Symbol.dispose` or `Symbol.asyncDispose` include `child_process` (process), `events`, `fs` (file system), `readline`, `sqlite`, `dgram`, `http`, and `http2`, among others. The split between the two symbols follows whether the teardown operation returns a promise.

The official reference lists each module against the symbol it implements, which makes it the fastest way to check support before writing code. Searching for the symbol name inside the Node.js API documentation returns the current list rather than a snapshot from a blog post.

Module support is not the same as full coverage. Not every core module implements either symbol, and whether a given method returns a promise determines which one applies. If a module you rely on is missing, the same documentation page shows where the feature is defined and what a small pull request would look like.

## Timers, streams, and terminal interfaces with using

Timers and terminal interfaces both work with `using`, but they behave differently because of what each resource holds. A repeating timer keeps the event loop alive until it is cleared, so disposal is what lets the process exit.

The `setInterval` case is the clearest demonstration. Without `using`, the process keeps ticking and never exits on its own; with `using`, the block ends, disposal runs, and the process shuts down after a single execution.

`readline` is the more practical daily example. The interface holds stdin open, and calling `close` is easy to forget in a script that also handles errors, and leaving it unclosed means the process simply sits there waiting. Node.js implements `Symbol.asyncDispose` on the `readline` interface, so `await using` covers it because the close method returns a promise.

Streams take the same treatment. A log file opened as a write stream can be declared with `await using`, and when the block ends the stream closes and any rotation or rename logic runs. Timers are still valid, but in day-to-day work `readline`, `child_process`, and database connections come up more often.

### A worked child process example

A `child_process` call looks like this with disposal in place:

```ts
await using proc = spawn('ls', { stdio: ['ignore', 'pipe', 'inherit'] });
for await (const chunk of proc.stdout.toArray()) {
  // consume output
}
```

The `await using` form means the stream and the child process stop themselves when the block ends. You can pipe stdout through the array helpers, such as `toArray`, `asyncIterator`, or the stream iterators, and the runtime closes the pipe for you.

## Which modules should you check before rewriting cleanup code

Before rewriting existing cleanup code, confirm that the specific object you are using implements the symbol, because `using` on a plain object throws a `TypeError` at runtime. The check is short: inspect whether the prototype or instance has `Symbol.dispose` or `Symbol.asyncDispose` defined.

The awkward part is the asymmetrical coverage. Node.js core modules have adopted these symbols widely, while most third-party packages have not, so a codebase mixing driver connections and native streams will use both patterns for a while.

A practical adoption sequence looks like this:

1. Run `process.version` to confirm the runtime is Node.js 24 or later, since earlier versions do not support the syntax at all.

2. Check the target object for `Symbol.asyncDispose` or `Symbol.dispose` before adding `using`, and fall back to `try` and `finally` when neither exists.

3. Convert the smallest, most leak-prone path first, usually a file stream or a readline interface, then measure whether the process now exits without an explicit close.

4. Confirm the change in a short script that logs on disposal, so you know the dispose method fires on both normal and error paths.

For open-source maintainers, adding the symbol to a driver is a contained change that other users can rely on, which is how a language feature reaches the packages that need it most.

## Limits and caveats of the using keyword

The `using` keyword does not guarantee that every leak disappears, because it only helps when the resource object exposes a disposal method that actually releases the underlying handle. A dispose method that logs and returns does nothing useful.

Runtime availability is the second limit. Support landed in Node.js 24 in 2025, and earlier Node.js releases, most browsers, and many build toolchains did not support the syntax at that time. Checking the current baseline before shipping is necessary rather than optional.

The third limit is scope. Disposal runs when the declared block exits, so a resource declared in a long-lived scope stays open for that whole scope. That gives predictable teardown, not lazy cleanup.

The fourth is ordering. When a function declares more than one `using` binding, disposal runs in reverse order, the last declaration first, which can matter if one resource depends on another.

| Situation | `using` works | Fallback needed |
| --- | --- | --- |
| Node.js core module with `Symbol.dispose` | Yes | No |
| Node.js core module with `Symbol.asyncDispose` | Yes, with `await using` | No |
| PostgreSQL, MySQL, or MongoDB driver without the symbol | No | `try` / `finally` with `close()` |
| Plain object with no dispose method | No, throws `TypeError` | Manual cleanup |
| Resource declared in a long-lived scope | Only at scope exit | Explicit `await` if earlier release is needed |

## Frequently asked questions about JavaScript resource cleanup

### What is the difference between using and await using?

`using` calls `Symbol.dispose`, which is synchronous. `await using` calls `Symbol.asyncDispose` and waits for the returned promise, so anything whose `close` method is asynchronous, such as a database connection or a `readline` interface, needs the await form. Use plain `using` for synchronous cleanup and `await using` when the release returns a promise.

### Do I need Node.js 24 to use the using keyword?

Yes. Support for the syntax and the two symbols landed in Node.js 24, released in 2025. Earlier Node.js versions do not recognise the declaration, and other JavaScript runtimes adopted the feature at different times. If you run an older runtime, you need a different approach for cleanup until you upgrade.

### Does using replace try, catch, and finally entirely?

No. `using` replaces the cleanup block, and it still needs an outer `try` and `catch` if you want to handle the error yourself rather than let it propagate. The pattern is `using` for teardown and `try` and `catch` for error handling. If you want to suppress errors instead of letting them stop the process, you still write the catch Claude.

### What happens if the object has no dispose method?

The runtime throws a `TypeError` when the block attempts disposal. Check for `Symbol.dispose` or `Symbol.asyncDispose` on the object before converting the code, and keep the `try` and `finally` version as a fallback. In a TypeScript codebase you can catch this at compile time instead of runtime by typing the binding correctly.

### Which Node.js core modules support the two symbols today?

The official documentation lists `fs`, `streams`, `child_process`, `events`, `readline`, `sqlite`, `dgram`, `http`, and `http2`, among others. Coverage is not complete across every core module, so the documentation is the source to check.

### How do I know which symbol my module implements?

Open the module's page in the Node.js documentation and search for the symbol name. The page shows whether the module exposes `Symbol.dispose` or `Symbol.asyncDispose`, and the source reference lets you confirm the actual implementation. If neither is present, the module has not adopted the feature.

### Does using help with memory leaks in general?

It helps with cleanup of resources that expose a dispose method, such as file handles, streams, sockets, and database connections. It does not address leaked references that keep objects alive in memory, because that is what the garbage collector and the surrounding code are responsible for. Use it as one part of a broader cleanup strategy.

### Can I use using in a Node.js version before 24?

No. The syntax itself is unrecognised before Node.js 24, so the code fails to parse. The earliest supported version is 24, and you should verify with `process.version` before shipping anything that depends on it.

### What does await using do when the dispose method throws?

The error propagates from the block, and the runtime continues disposing any remaining bindings. If several resources are declared, the earlier failures do not stop the later disposals. You can wrap the block in `try` and `catch` if you want to handle the error explicitly.

## Where the using keyword fits into a wider JavaScript workflow

The `using` keyword joins the small set of language features that change daily habits rather than the shape of a program. Async and await did the same thing: it did not unlock new capabilities, it removed a recurring source of bugs from code that already worked.

That is also why the adoption curve looks uneven. Core modules adopted the symbols quickly because one team controlled them; third-party drivers move at the pace of their maintainers, and each addition is a small, reviewable pull request.

Developers learning this pattern often need a structured path through modern JavaScript and TypeScript rather than isolated feature tutorials. Communities such as Gustavo Dev Doido and the Crazystack Typescript track have built followings around exactly that kind of practical, project-based teaching, and the Bootcamp do Dev Doido runs on similar material. Programs like https://crazystack.com.br gather that style of content in one place for developers who want the surrounding context, not just a single syntax note.

## Turn a YouTube walkthrough into a written article

The same instinct that makes `using` valuable applies to content: cleanup and clarity come from a deliberate handoff rather than from remembering to do it later. A 14-minute walkthrough of a language feature carries real explanation, working examples, and the order in which ideas should land, and most of that structure is lost once the video scrolls past in a feed.

If you have recorded an explanation, a code walkthrough, an interview, or a lesson on video, that material already contains the raw shape of an article. Skala Blog transcribes a YouTube URL and turns it into a structured draft you can review, so the understanding you captured on camera can reach readers who prefer text or who arrive from search.

You already know how to clean up a resource, so clean up the content you have already made: [Skala Blog](https://skalablog.com) takes a YouTube URL, transcribes the video, and turns it into an article.

[Skala Blog](https://skalablog.com)

[Source video](https://www.youtube.com/watch?v=u0bPVafv-Ak)
