vroom indexes your CSV instead of reading it, and that changes where the cost lands
Fast reading of delimited files
At a glance
- What is it?
- vroom is a delimited file reader for R that indexes where each record lives and loads columns lazily through the Altrep framework, then multithreads the work. It is the fastest reader in its own benchmark, and the laziness has costs you should know about.
- Who is it for?
- Adopt vroom if you read large delimited files in R and want the fastest path available, particularly if you only touch a few columns of a wide file or filter before materialising. Do not adopt it when you need every column fully in memory quickly, when you rely on embedded newlines in headers or fields, or when a single thread is required, because that case needs num_threads = 1.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 32 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
It indexes records rather than parsing the file up front
vroom is a delimited file reader for R from the tidyverse organisation, and the README is upfront about how it achieves the speed it claims. It does not stop to actually read all of your data. It indexes where each record is located so it can be read later.
That single design choice is the whole architecture. The vectors returned use the Altrep framework to lazily load data on demand when it is accessed, so you only pay for what you use. The README stresses that this lazy access happens automatically, so no changes to your data manipulation code are needed.
The consequence is that the cost of reading a file is split into two moments. Indexing is up front and proportional to file size, because you have to find the record boundaries. Materialisation is deferred and proportional to the columns you actually touch. For a wide file where you need three of forty columns, that is a large saving. For a narrow file where you need everything, you have paid the indexing cost and then paid the parsing cost, which is work a plain reader would have done once in a single pass.
The package is written in C++ and exposes an interface matching readr, so column type specification is the same shape of call. The README states that vroom has nearly all of the parsing features of readr for delimited and fixed width files.
The quoted throughput is 1.23 GB/sec, and the README is clear that this comes from a real 1.53G dataset with 14,388,451 rows and 11 columns, with the code in the bench/ directory of the repository.
Indexing, materialising and writing all use multiple threads
vroom also uses multiple threads for indexing, for materialising non-character columns, and when writing, according to the README. Three separate places, which tells you the threading is woven through the implementation rather than bolted onto one function.
The distinction between the two phases matters when you think about the thread count. Indexing is parallelised over the file, and materialisation is parallelised over the columns, but character columns are excluded from the latter because they are handled differently. That exclusion is stated in passing, as a parenthetical about non-character columns, and it is worth remembering if you are profiling a file that is mostly text.
There is one important constraint on all of this. The README notes that embedded newlines in headers and fields require num_threads = 1. That is a real limitation for anyone working with quoted fields that contain line breaks, which is exactly the kind of data that shows up when a file has been exported from a system that allows multi-line cells.
So the threading story has a cliff. Most files get the fast path. Files with embedded newlines in the header or in a field fall back to single threaded operation, and the README gives no throughput figure for that case.
Features readr has, and the ones vroom adds on top
The README lists nearly all of the parsing features of readr for delimited and fixed width files: delimiter guessing, specification of column types including type guessing, skipping of headers, comments and blank lines, quoted fields, double and backslashed escapes, whitespace trimming, and windows newlines.
Then it marks with an asterisk the features that are additions rather than parity. Those are custom delimiters including multi-byte and Unicode ones, column selection in the style of dplyr::select, support for big integer numeric types, reading from multiple files or connections, embedded newlines in headers and fields, and resistance to invalid inputs, which the README says comes from extensive testing with the afl fuzz tester.
Two of those additions carry real weight for an adopter.
Column selection in the dplyr::select style means you can ask for a subset of columns and skip the parsing work for the rest, which is the same laziness idea as the Altrep columns but applied at read time.
Reading from multiple files or connections is a different kind of feature. The README describes it as native support, and shows a workflow where a set of files is passed directly to vroom, with an id argument that adds a column revealing which filename each row came from. The output shows the delimiter detected as a tab and the column specification, so the type guessing works across the whole set rather than per file.
The fuzz testing claim deserves attention because it addresses a category of failure that readers of untrusted data hit: a malformed file should produce an error, not a crash.
Installing from CRAN and reading your first file
The installation path is standard for an R package on CRAN.
install.packages("vroom")If you need the development version from GitHub, the README suggests pak, with the package install line commented out as the prerequisite.
# install.packages("pak")
pak::pak("tidyverse/vroom")The usage example shows the readr compatible interface with an explicit col_types list, which is the shape you should copy for a first run because it removes type guessing from the picture.
vroom::vroom(
"mtcars.tsv",
col_types = list(
cyl = "i",
gear = "f",
hp = "i",
disp = "_",
drat = "_",
vs = "l",
am = "l",
carb = "i"
)
)The column type codes are compact: i for integer, f for factor, l for logical, and a value of _ for a column that is skipped entirely. That last one is worth pausing on, because it is the clearest expression of the lazy design. A column marked that way is not read at all, which is faster than reading it and never using it.
The result is a tibble, and the printed output shows the column types that were applied, so a mismatch between your spec and the file shows up immediately rather than later.
Reading many files in one call, with the id column
The multi-file support is worth a close look because it changes how you structure a pipeline over sharded data. The README generates a set of files by splitting the nycflights dataset by airline, writing each shard with vroom_write, then reads them all into one tibble.
files <- fs::dir_ls(glob = "flights*tsv")
files
vroom::vroom(files, id = "source")The id argument is the part that earns its place. Rather than looping over files and binding the results, you pass the filenames directly and get back a single tibble with a source column recording the origin of each row. For a directory of daily exports or per-tenant shards, that removes an entire class of glue code.
The same section shows the writing side. vroom_write takes a data frame, an output path and a delim argument, and the feature list describes writing delimited files with as-needed quoting, meaning fields are quoted only where the delimiter or quoting rules require it rather than always.
The one caveat visible in the example is that the column specification is reported for the combined set, with four character columns, fourteen doubles, one datetime and the source column. Type guessing ran across all the files as one, which is the right behaviour if your shards are consistent and a trap if they are not.
Where the benchmark numbers come from, and what they leave out
The README's headline table compares four readers on the same file.
| package | version | time (sec) | speedup | throughput |
|:-----------|--------:|-----------:|--------:|--------------:|
| vroom | 1.5.1 | 1.36 | 53.30 | 1.23 GB/sec |
| data.table | 1.14.0 | 5.83 | 12.40 | 281.65 MB/sec |
| readr | 1.4.0 | 37.30 | 1.94 | 44.02 MB/sec |
| read.delim | 4.1.0 | 72.31 | 1.00 | 22.71 MB/sec |So on that dataset vroom is ahead of data.table by a factor of roughly four, and ahead of read.delim by a factor of fifty. The benchmark is described as coming from a real 1.53G dataset with 14,388,451 rows and 11 columns, with the full details in the benchmark article on the package site and the code in the bench/ directory of the repository.
Two things to keep in mind when you read such a table. The versions are old relative to the current package versions, so a rerun today would use different code in all four rows. And the comparison is a full materialisation benchmark, which is the case where vroom's lazy design has to work hardest to pay off, since the point of laziness is to avoid reading columns you do not use.
For a workload that filters first and selects two columns of a fifty column file, the gap would look different, and probably more favourable to vroom. The table tells you the floor, not the ceiling.
Building from source, and the benchmark targets in the Makefile
The package is a standard R package with compiled C++ in src/, and the Makefile in the repository gives you the developer loop.
all:
@Rscript -e 'pkgload::load_all(quiet = FALSE)'
test:
@Rscript -e 'devtools::test()'The all target loads the package with pkgload so the compiled code is available without a full install, and the test target runs devtools::test against the tests/testthat directory. There is also a clean target that calls pkgbuild::clean_dll, which is the thing to run when you change the C++ and want to force a rebuild.
The bench target is more interesting. The Makefile defines a million row and twenty five column benchmark shape, plus variables for a taxi trip fare dataset and a PUMS fixed width file, and the pattern rules run the benchmark scripts under R with the row and column counts passed as arguments. The bench target is marked .NOTPARALLEL, so the benchmark runs are serialised on purpose, which is the correct choice when the output is being timed.
The install target is a plain R CMD INSTALL, which is what you want for a quick local check of a change.
This is a package with a real development infrastructure around it: revdep/ for reverse dependency checks, pkgdown/ for the documentation site, standalone/ and tests/ directories, and a .covrignore plus codecov.yml for coverage.
The licence is custom, and the project carries a stable lifecycle badge
The repository is recorded under a custom licence that GitHub cannot classify, with the terms in the LICENSE file and a LICENSE.md at the top level. That is worth checking before you vendor the package into a commercial codebase, and it is a difference from most of the tidyverse, where the MIT licence is the norm.
On maintenance, the repository is not archived and the last push was on 2026-09-21. The recent releases are v1.7.1 on 2026-03-31, v1.7.0 on 2026-01-27 and v1.6.7 on 2025-11-29, so releases land regularly and the version numbers move in patch increments within a minor line.
The README carries a lifecycle badge marking the package as stable, which is the tidyverse's own signal that the API is not going to be reshaped. That is the most useful maintenance fact here, because for a data reading package the risk is not that the project dies but that a major version changes your call sites.
The README is generated from README.Rmd, so the documented examples are executed as part of the build, and there is a NEWS.md for the change log. The package is on CRAN, which is where most R users will get it, and the development version is available through pak.
Editorial conclusion
Adopt vroom if you read large delimited files in R and want the fastest path available, particularly if you only touch a few columns of a wide file or filter before materialising. Do not adopt it when you need every column fully in memory quickly, when you rely on embedded newlines in headers or fields, or when a single thread is required, because that case needs num_threads = 1. Before you switch, read the benchmarks article with the real dataset size, check the licence file yourself since the repository licence is custom, and confirm the column types you rely on are in the col_types list rather than guessed.
Frequently asked questions
How do I install vroom?
Install it from CRAN with install.packages("vroom"). For the development version from GitHub the README suggests pak, with pak::pak("tidyverse/vroom") and install.packages("pak") as the prerequisite.
Why is vroom faster than readr?
The README says vroom does not stop to read all of the data. It indexes where each record is located and the returned vectors use the Altrep framework to load data lazily on demand, so you only pay for what you use, and no changes to your R data manipulation code are needed.
Does vroom support reading multiple files at once?
Yes. The README describes native support for reading from multiple files or connections, and you can pass the filenames directly and use the id argument to add a column recording which file each row came from.
What is the difference between vroom and readr?
The README says vroom has nearly all of the parsing features of readr for delimited and fixed width files, and marks the additions with an asterisk. Those include custom multi-byte and Unicode delimiters, column selection like dplyr::select, big integer types, reading multiple files, embedded newlines in headers and fields, and resistance to invalid inputs, tested with afl.
Are there cases where vroom cannot use multiple threads?
Yes. The README notes that embedded newlines in headers and fields require num_threads = 1, so that case falls back to single threaded operation.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tidyverse-vroom)Was this useful?