Guide · Backend
Bun Check Explained: Type-Check TypeScript Before You Run, Test or Build
20 min readUpdated By Saket Bhatnagar
Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.
In short
- Bun check is a type checker built into Bun since version 1.4.3. A type checker reads your TypeScript and reports type mistakes, like a string where a number belongs, without running the code.
- It reads your tsconfig.json, the file that holds your TypeScript settings. On our samples it printed the same error lines as tsc, TypeScript's own compiler, and it writes no output files.
- Plain bun runs TypeScript without checking types. Adding --check to bun run, bun test or bun build first checks the files that command loads, and nothing runs if there is an error.
- Bun says it is 3x to 6.4x faster than tsc 7.0.2 on its test machine. Treat those as Bun's own numbers until you time your own project.
- Beginners can stop after the section on reading the output. Engineers get the traps we found by running 1.4.3 and 1.4.4: test-only checks, computed imports and check scripts.
What goes wrong when Bun runs TypeScript with type errors?
Nothing stops it. Bun removes the type annotations and runs the JavaScript that is left, so a type mistake turns into a wrong answer at runtime instead of an error. That gap is what bun check closes.
Picture a common setup. A small food-delivery backend stores each order amount in paise, where 100 paise make one rupee. One order arrives from a form as the text "19900" instead of the number 19900. The setup is made up, but the bug is real and we ran it.
The total function adds 49900 and "19900". In JavaScript, adding a number to a string joins them as text. So the code divides "4990019900" by 100 and prints 49900199 rupees, instead of 698. Here is src/order.ts, followed by src/index.ts, where line 5 holds the mistake.
export interface Order {
id: string;
amountPaise: number;
}
export function totalRupees(orders: Order[]): number {
return orders.reduce((sum, o) => sum + o.amountPaise, 0) / 100;
}import { totalRupees, type Order } from "./order.js";
const orders: Order[] = [
{ id: "A1", amountPaise: 49900 },
{ id: "A2", amountPaise: "19900" },
];
const label: string = totalRupees(orders);
console.log(label);TypeScript knows amountPaise must be a number, so it can catch this before anyone sees the bill. There is a second, smaller mistake too: label is declared as a string, but totalRupees returns a number.
Yet running bun src/index.ts on Bun 1.4.4 printed 49900199 and exited with code 0, which means success. Before Bun 1.4.3, teams ran tsc as a separate step to catch this. Now one command, bun check, finds both mistakes and stops with exit code 1. You will see its exact output in the section on running it.
Which words do you need before using bun check?
You need seven terms: type checker, tsc, tsconfig.json, emit, error code, exit code and pipe. Each gets a plain meaning below, tied to the order example.
- Type checker: a program that reads code and checks that values match their declared types. It flags "19900" because amountPaise is declared as a number.
- tsc: the TypeScript compiler from the typescript npm package. Running tsc --noEmit only checks types, which is the job bun check copies.
- tsconfig.json: the settings file for a TypeScript project. It says which files to check and how strict to be.
- Emit: writing output files, such as compiled JavaScript or .d.ts type declaration files. Bun check never emits anything.
- Error code: a stable number for each kind of TypeScript error. TS2322 means a value's type is not assignable to the expected type.
- Exit code: the number a command returns when it ends. Zero means success, and anything else means failure. CI, short for continuous integration, is the server that tests every code change and reads this number to pass or fail it.
- Pipe: sending one command's output into another program or a file instead of the screen. Standard output and standard error are a command's two output channels, one for results and one for messages.
What is bun check and how does it work?
Bun check is a TypeScript type checker inside the Bun binary. The Bun 1.4.3 release post calls it a port of typescript-go, the native TypeScript 7 compiler. It says bun check passes all 51,210 of TypeScript 7.0.2's conformance tests, the compiler's own tests of how it must behave.
It finds the nearest tsconfig.json, loads every file that config includes, and checks them on all CPU cores. You do not need the typescript package installed. In our test project, node_modules had no typescript folder and bun check still worked.
Think of it like a ticket checker at a railway gate. Every passenger, which is every file, shows a ticket, which is its types, before boarding the train, which is running the code. Where the picture breaks: a real checker looks at one ticket at a time, while bun check compares files against each other through their imports.
What one bun check run does
Drag to exploreNotice the two early exits. Config and syntax errors are printed and stop the run before any types are checked, so fix those first.
Diagram connections:
- Find tsconfig.json to Valid options?
- Valid options? to Load and parse files (Yes)
- Valid options? to Print errors and summary (No: stop early)
- Load and parse files to Syntax errors?
- Syntax errors? to Check types on all cores (No)
- Syntax errors? to Print errors and summary (Yes: stop early)
- Check types on all cores to Print errors and summary
- Print errors and summary to Exit code
- Find tsconfig.json: Nearest one, or -p PATH, or built-in defaults
- Valid options?: Removed options like baseUrl fail here
- Load and parse files: Included files plus everything they import
- Syntax errors?: Example: TS1109 Expression expected
- Check types on all cores: One thread per core unless --threads N
- Print errors and summary: Errors to standard output, summary to standard error
- Exit code: 0 when clean, 1 for any error
We confirmed both early exits on 1.4.4. A tsconfig.json with baseUrl failed with TS5102 and the note "Stopped before type checking 1 file". A file with a broken expression failed with TS1109, and no type errors were reported until it was fixed.
How do you run bun check on a TypeScript project?
Install Bun's type declarations, list them in tsconfig.json, then run bun check in the project folder. The steps below are what we ran on Bun 1.4.4.
Pin your Bun version
Run npm i -D --save-exact [email protected], then run it as npx bun check or from a package.json script. Plain bun uses whichever Bun is first on your PATH.
Add Bun's types
Run bun add -d @types/bun, or npm i -D @types/bun if you use npm. Without it, names like Bun and bun:test are unknown. In our test without the package, bun check also printed a note telling us to install it.
List the types in tsconfig.json
Set compilerOptions.types to ["bun"]. TypeScript 7 loads no global type packages unless they are listed there.
Run the checker
Run bun check in the folder with tsconfig.json. Use bun check -p packages/api to check another project folder.
Read the exit code
Run echo $? straight after, or echo $LASTEXITCODE in PowerShell. You should see 0 for a clean project and 1 when any error was found.
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"types": ["bun"]
},
"include": ["src"]
}On the order example, bun check printed exactly the same two error lines as tsc 7.0.2 in pipe mode, which --no-pretty forces. Only the last line is extra, and the timing in brackets will differ on your machine.
$ bun check --no-pretty
src/index.ts(5,15): error TS2322: Type 'string' is not assignable to type 'number'.
src/index.ts(8,7): error TS2322: Type 'number' is not assignable to type 'string'.
Found 2 errors in 1 file, checked 2 files [89.68ms]
$ echo $?
1Each line reads as file, then line and column in brackets, then the error code and message. Line 5, column 15 is the amountPaise property that received the "19900" string, and line 8 is label. We changed "19900" to 19900 and wrote totalRupees(orders).toFixed(2), which returns a string. Bun check then printed "No type errors in 2 files" and exited 0.
How do you read bun check output in a terminal, a pipe and CI?
Bun check picks its output style from where it is running. In a terminal it shows source code around each error. In a pipe it prints one line per error, like tsc --pretty false.
| Where it runs | What you see | How to force it |
|---|---|---|
| Terminal | Code excerpt, a caret under the mistake, the error code, and a note pointing at the declaration | --pretty |
| Pipe or file | One line per error in tsc format, then a summary line | --no-pretty |
| GitHub Actions | The plain lines plus ::error annotations that show inline on the pull request | Set when GITHUB_ACTIONS is true |
| Coding agent | Tagged blocks per error with source lines and no colours | Set by agent variables such as AGENT=1 |
The streams matter in scripts. Bun check sends error lines to standard output and the summary line to standard error. We checked this by hiding each stream in turn. A grep on the error output will not see the summary. When only standard output is piped, the summary can appear on screen before the errors.
In terminal mode, more than 50 identical errors are grouped. Our file with 60 copies of the same mistake showed one excerpt and the line "60 times in this file". Add --all to see every one, and add --timing to print load and check times.
Bun check vs tsc: what is the same and what differs?
For checking, bun check aims to be a drop-in for tsc --noEmit with the same TypeScript 7 rules, with two documented exceptions listed under limits below. The differences are in what else each tool does, and we ran every row below on Bun 1.4.4 and tsc 7.0.2.
| Question | tsc 7.0.2 | bun check (Bun 1.4.4) |
|---|---|---|
| Same error lines on our samples? | Reference output | Yes, line for line in pipe mode |
| Writes files? | Yes without --noEmit: .js, .d.ts and .tsbuildinfo | Never, even with outDir and declaration set |
| Exit code on errors | 1, or 2 when it still emitted output | Always 1 |
| Needs the typescript package? | Yes, it is the package | No, rules are built in |
| Project references with -b | Builds referenced projects and writes files | Reads referenced projects from source, writes nothing |
| Compiler flags with -b | Rejected with TS5094 | Accepted |
| Editor support | Yes, through the TypeScript language server | None, your editor keeps using TypeScript |
Keep tsc when you publish a library and need .d.ts files. Bun's docs also tell you to install the TypeScript version Bun reports. On both 1.4.3 and 1.4.4, bun -e "console.log(process.versions.typescript)" printed 7.0.2.
TypeScript 7 defaults catch people coming from TypeScript 5
Bun check follows TypeScript 7, so its defaults apply. A tsconfig.json without a strict setting still reported TS7006, an implicit any parameter, in both tools. Strict mode is on unless you turn it off.
Old options now fail outright. On our sample, baseUrl gave TS5102 and moduleResolution node gave TS5108, in both bun check and tsc. Bun's docs list target es5 and some module values as removed too.
What does the --check flag do for bun run, bun test and bun build?
It type checks first and only continues when there are no errors. On an error it prints the errors, exits with 1 and runs nothing, so the wrong bill from the order example never prints.
bun --check src/index.ts on the order example
Drag to exploreThe check covers the entry file and its static imports only. Look at which arrow leads to running code.
Diagram connections:
- bun --check src/index.ts to Collect files to check
- Collect files to check to Any type errors?
- Any type errors? to Print errors, exit 1 (Yes)
- Any type errors? to Run the program (No)
- bun --check src/index.ts: Developer or CI runs the command
- Collect files to check: index.ts plus order.ts, which it imports
- Any type errors?: Same rules as bun check
- Print errors, exit 1: Two TS2322 errors, code never runs
- Run the program: Prints 698.00 after the fix
We ran all four forms on Bun 1.4.4 and got the same stop-on-error result each time:
- bun --check src/index.ts and bun run --check src/index.ts: errors printed, exit 1, nothing ran.
- bun build --check src/index.ts --outdir out: errors printed, exit 1, and no out folder was created.
- Bun.build with check: true in a build script: by default it threw an error listing both TS2322 errors and wrote nothing. With throw: false it returned success: false, with the errors in its logs.
- bun --watch --check src/index.ts: after we broke the file during the watch, it re-checked and printed the error instead of restarting the program.
Trap one: bun test --check only checks what the tests import
Our order.test.ts imported order.ts but not index.ts, which still held both errors. Running bun test --check passed and ran the test, while bun check on the whole project failed. The docs describe this scope, but it is easy to miss.
Trap two: computed imports are not followed
A file that loaded a module with import(name), where name was built from strings, passed bun --check and ran. The loaded module held a type error that only the full bun check caught. Workers and require calls are also outside the check, per Bun's docs.
Why does bun check run your package.json check script instead?
Because Bun treats bun check like a script name when package.json has a script called check. That script runs instead of the type checker, on both Bun 1.4.3 and 1.4.4.
We added a check script that only printed a message. Both versions printed the message and exited 0, without checking any types. That is a silent pass in CI, because the job looks green while the order bug is still there.
Bun 1.4.4 added --no-script, which forces the type checker. On 1.4.4, bun check --no-script reported the two errors and exited 1. On 1.4.3 the flag does not exist, and it was passed to the script as a plain argument.
How does bun check handle monorepos and missing tsconfig files?
It follows project references, which are links in tsconfig.json from one project to the projects it depends on. It reads referenced projects straight from source, so nothing needs building first.
We built a two-package monorepo where api imports a function from shared. Running bun check -b found the error in api and wrote no files. Running tsc -b found the same error but wrote dist output and two .tsbuildinfo files, which are tsc's build cache.
- Root config with only references: plain bun check followed them anyway and said so in a note.
- No tsconfig.json at or above the folder: it listed the nested configs, suggested bun check -p or bun check ., and exited 1.
- No tsconfig.json anywhere: it used built-in defaults with strict on, and still reported an implicit any.
- An empty folder: it printed "Nothing to check" and exited 1, so a wrong working directory fails loudly.
Checking one file is narrower than it looks. Running bun check src/order.ts passed, because order.ts imports nothing broken. The errors lived in index.ts, which was outside that run.
How fast is bun check in Bun 1.4.3 and 1.4.4?
Bun reports it as several times faster than tsc 7.0.2, and 1.4.4 as faster again on large projects. These are Bun's own benchmarks, so treat them as claims to test on your code.
| Claim | Bun's number | Setup Bun gave |
|---|---|---|
| Speed vs tsc 7.0.2 (1.4.3 post) | 3.0x to 6.4x faster across eight projects | 16-core Apple silicon Mac |
| VS Code src, 9,795 files (1.4.3 post) | 1.24 s vs 5.98 s; 2.14 GB vs 7.97 GB memory | Same machine |
| Memory vs tsc 7.0.2 (1.4.3 post) | 2.2x to 4.9x less | Same eight projects |
| MUI docs, 1.4.4 vs 1.4.3 | 1.31 s vs 10.28 s; peak memory 1.3 GB vs 11.2 GB | Linux x64, 8 threads, best of 3 |
| 4,000 small projects with -b, 1.4.4 vs 1.4.3 | 0.87 s vs 13.65 s | 16-core M4 Max, best of 6 |
We also timed our own small run, labelled as ours. The machine was a Linux x86_64 container with 2 virtual CPUs at 2.8 GHz and 7 GB of memory. The project was 2,000 generated files with simple types.
Over five rounds, Bun 1.4.4 took 0.27 to 0.44 seconds, Bun 1.4.3 took 0.32 to 0.41 seconds, and tsc 7.0.2 took 0.97 to 1.50 seconds. That is roughly three times faster on this synthetic project, with only two cores to share.
The 1.4.4 post also lists costs. Bun's own src/js folder got slower, from 0.11 to 0.20 seconds. Monorepos of large projects can use more memory, because -b now checks up to 16 small projects at once instead of 4.
What are the limits of bun check as of October 2026?
It only checks types, and it can still disagree with tsc in rare cases. These limits come from Bun's docs and release posts, and we ran the ones we could.
- No emit: no JavaScript, .d.ts files, source maps or .tsbuildinfo, so there is no incremental cache between runs either.
- No language server: editors still need the typescript package for red squiggles and hover types.
- Known mismatches: the 1.4.3 post reports 1,102 of 1,103 configs matching tsc across 72 deliberately misconfigured repositories. Under 11 other option sets, 751 of 752 runs matched, and the one that differed had strict turned off.
- Extra errors: Bun's docs say tsc can drop an error that a later file raises in an earlier one, while bun check reports it.
- Ignored setups: Bun's docs say it ignores tsconfig plugins and does not support Yarn Plug'n'Play, so Yarn projects need nodeLinker node-modules.
- Behaviour changes: 1.4.4 stopped reporting TS2321, a deep-comparison error, following TypeScript 7.1, so the same code can pass on 1.4.4 and fail on 1.4.3 or on tsc 7.0.2.
Bun's checker is young and changes fast between releases. Pin the Bun version in package.json and CI, and treat a Bun upgrade like a TypeScript upgrade. Bun asks for mismatches with tsc to be reported on its GitHub issues.
How do you explain bun check in a system design interview?
Frame it as a build-pipeline question: where does type safety get enforced, and what does it cost in time? Use the order-total bug as your one example.
State the gap
Bun and similar runtimes strip types and run the code, so a type mistake becomes a wrong result, like a 49900199 rupee bill.
Name the gate
A type checker such as bun check or tsc --noEmit checks types before code runs, and its exit code fails the pipeline.
Place it in the pipeline
Run a full project check in CI, and a fast --check guard before local runs, tests and builds.
Call out the scope traps
Test-only checks and computed imports skip files, so the full check is the source of truth.
Talk about trust
Speed numbers are vendor claims, so measure on your own repository. Pin versions, because checker rules change between releases.
Say what you keep
Keep the typescript package for .d.ts output and the editor, and use bun check for fast checking.
Questions people ask
- Does bun check replace the typescript package?
- Only for checking from the command line. Bun check has the TypeScript 7 rules built in, so CI does not need the typescript package to check types. Your editor still needs it for errors and hover types, and tsc is still needed to write .d.ts files. Bun's docs suggest installing the TypeScript version that process.versions.typescript reports, which was 7.0.2 on Bun 1.4.3 and 1.4.4.
- Does Bun check types when I run a .ts file?
- No, not by default. Plain bun file.ts strips the types and runs the code, which is why our order example printed a wrong total and exited 0. Add the --check flag, as in bun --check file.ts, to type check the file and its static imports first. On any error it exits 1 and the code does not run.
- What exit code does bun check return?
- Zero when there are no type errors and 1 when there are any. It also exits 1 when there is nothing to check, such as an empty folder or no tsconfig.json at or above a folder with nested configs. By contrast, tsc 7.0.2 returned 2 in our test when it reported errors but still wrote output files.
- Why does bun check say my tests pass when the project has errors?
- You are probably running bun test --check, which only checks test files and what they import. In our test, index.ts held two errors but no test imported it, so the tests ran and passed. Run plain bun check on the whole project as a separate CI step to catch everything.
- Can I use bun check with a monorepo?
- Yes. With -b, bun check follows project references in tsconfig.json and reads each referenced project from source, so nothing needs building first. In our two-package test it found the same error as tsc -b without writing any files. Bun 1.4.4 also made -b much faster on monorepos with thousands of small projects, according to its release post.
- Is bun check faster than tsc?
- Bun says yes: 3x to 6.4x faster than tsc 7.0.2 on eight open-source projects, measured on a 16-core Apple silicon Mac. On our small 2-core container with 2,000 generated files, it was about three times faster. Speed depends on your code and machine, so time both tools on your own repository before deciding.
- Why does bun check print a message from my own script?
- Your package.json has a script named check, and Bun runs that script instead of the type checker. Use bun check --no-script on Bun 1.4.4 or later to force the type checker. On Bun 1.4.3 that flag is not available, so rename your script to typecheck.
Sources and further reading
- Bun v1.4.3 release post (10 October 2026): bun check introduced
- Bun v1.4.4 release post (22 October 2026): bun check speed-ups and --no-script
- Bun docs: Type checking with bun check
- oven-sh/bun pull request 44361: wip type checker (draft)
- npm registry metadata for the bun package: 1.4.3 and 1.4.4 publish dates
- npm registry metadata for the typescript package: 7.0.2 latest, 7.1 beta
- Microsoft Learn: about_Automatic_Variables ($LASTEXITCODE in PowerShell)