Compare··8 min read

R paste vs paste0: What Actually Differs

The short answer people rarely lead with: paste0() is exactly paste() with sep = "". One default separator is the entire difference. paste() puts a space between the pieces, paste0() puts nothing. Everything else, the recycling, the collapse argument, the way NA becomes the string "NA", is identical, because paste0() literally calls paste() under the hood. This post shows both functions doing real work so you can pick without guessing.

By Deepak Yadav, I write R most days

The short version

  • The only real difference in r paste vs paste0 is the default separator: a space for paste(), nothing for paste0().
  • paste0() is a wrapper, defined as paste(..., sep = ""), so both recycle vectors and both take collapse the same way.
  • Use paste0() for file names, paths, URLs and column names. Use paste() for text meant to read like a sentence.

#The one-line difference

If you remember one thing, remember this: paste0(...) is paste(..., sep = ""). That is the whole story of r paste vs paste0. Both functions turn their arguments into character strings and glue them together. The difference is what sits between the pieces.

# paste puts a single space between arguments
paste("Hello", "world")     #> "Hello world"

# paste0 puts nothing
paste0("Hello", "world")    #> "Helloworld"

That is it. Nothing deeper is hiding. Once you know the default separator flips, every other behavior lines up between the two because they share the same engine.

 paste()paste0()
Default sep" " (one space)"" (empty)
Takes collapseYesYes
Recycles vectorsYesYes
Turns NA into "NA"YesYes
Added inAlways in base RR 2.15.0 (2012)

Both live in base R. No package needed.

#What paste() actually does

The signature is paste(..., sep = " ", collapse = NULL). The dots take any number of vectors. paste() converts each one to character, recycles the shorter vectors up to the length of the longest, joins the pieces position by position using sep, and returns a character vector.

# scalars: one string out
paste("x", 1, "y")                 #> "x 1 y"

# a vector: element-wise, sep between the two inputs
paste(c("a", "b", "c"), 1:3, sep = "_")
#> "a_1" "b_2" "c_3"

# recycling: the short vector repeats
paste("row", 1:3, sep = "-")
#> "row-1" "row-2" "row-3"

The result here is a vector of length three, not one string. That trips people up. paste() joins across the arguments you pass, one output per position. To collapse the whole vector down into a single string, you need the collapse argument, which is a separate job covered below.

#What paste0() actually does

paste0() was added to base R in version 2.15.0 for one reason: people kept typing paste(x, y, sep = "") and it got old. Here is the real definition from base R, lightly trimmed:

paste0 <- function(..., collapse = NULL, recycle0 = FALSE)
    paste(..., sep = "", collapse = collapse, recycle0 = recycle0)

So paste0() is not a separate implementation. It is paste() with the separator hard-set to empty. That is why the two behave identically everywhere except the space. Where paste0() earns its keep is anywhere a space would be wrong:

# numbered file names
paste0("plot_", 1:3, ".png")
#> "plot_1.png" "plot_2.png" "plot_3.png"

# building a URL
paste0("https://api.example.com/v1/users/", 42)
#> "https://api.example.com/v1/users/42"

# prefixing column names
paste0("q", 1:4)
#> "q1" "q2" "q3" "q4"

Every one of those would gain unwanted spaces with plain paste(). That is the daily case paste0() exists for.

#sep versus collapse, the real confusion

Most "why is my paste output weird" questions are not about paste vs paste0 at all. They are about mixing up sep and collapse. The two do different things and you can use both at once.

  • sep goes between the arguments at each position. It does not change the length of the result.
  • collapse runs after that and flattens the whole vector into one string, joining the elements with the collapse string.
x <- c("a", "b", "c")

paste(x, 1:3, sep = "-")
#> "a-1" "b-2" "c-3"        (a vector of 3)

paste(x, 1:3, sep = "-", collapse = ", ")
#> "a-1, b-2, c-3"          (one string)

paste(x, collapse = "")
#> "abc"                    (one string, no sep needed)

A clean way to hold it in your head: sep works sideways across arguments, collapse works downward across the vector. If your output has the wrong number of strings, the fix is almost always collapse, not sep.

#Which one should you use?

Pick by the default you want, so you type fewer arguments. A working rule:

  • Use paste0() when the pieces should touch: file names, paths, URLs, SQL fragments, generated column or object names, HTML strings, any glue where a space is a bug.
  • Use paste() when the output is meant to read like language: log messages, labels, sentences printed for a human. The space you get for free is the space you want.
  • Use paste(..., sep = something) when the separator is neither empty nor a space, for example an underscore or a tab. paste0() cannot help you here since its separator is fixed.

One tip that outranks both for file paths: reach for file.path() instead of paste0(). It inserts the correct path separator for the operating system, so your code does not break when it moves between machines.

# fragile
paste0("data", "/", "raw", "/", "file.csv")

# portable
file.path("data", "raw", "file.csv")
#> "data/raw/file.csv"

#The gotchas both share

Because paste0() is paste(), these bite you either way.

NA becomes the string "NA". Neither function keeps a missing value missing. It gets coerced to the two characters N and A, which can silently pollute a column.

paste("value:", NA)         #> "value: NA"
paste0("id", NA)            #> "idNA"

Numbers get R's default formatting. If you paste a double, you get however R chooses to print it, which may not be the decimals you want. For money or fixed precision, format the number first with sprintf() or format().

paste0("Total: $", 5)              #> "Total: $5"
sprintf("Total: $%.2f", 5)        #> "Total: $5.00"

Empty input is not nothing. paste() with a zero-length vector and no collapse returns character(0), not "". If a downstream function expects a single string, that can throw an error you did not see coming. Setting collapse gives you back an empty string when that is what you meant.

For the common single-line case, sprintf() is often clearer than either paste function because the template shows the shape of the output at a glance: sprintf("user %s has %d posts", name, n).

#Pasting R code and output around

A small aside, since I do most of this on a Mac. The other kind of paste in an R workflow is the one where you copy a snippet out of the console and drop it into a script, or lift a data frame's output into a message to a colleague. If you have ever lost a chunk of code because the next copy overwrote it, a clipboard history helps more than it sounds like it would. I wrote up the basics in how to copy on Mac, and compared the dedicated tools in the Paste clipboard manager review and the Maccy write-up. None of that changes what paste() does in R, but it changes how much friction there is in getting code and results in and out of your editor.

#Frequently asked questions

What is the difference between paste and paste0 in R?

The only difference is the default separator. paste() joins its arguments with a single space between them, because its default is sep = " ". paste0() joins them with nothing, because it calls paste() with sep = "". Recycling, the collapse argument and NA handling are identical, because paste0() is a thin wrapper around paste().

What is paste0 in R?

paste0() is a shortcut for paste(..., sep = ""). It concatenates its arguments into character strings with no separator between them. It was added in R 2.15.0 so you would not have to type sep = "" every time you wanted glued-together strings, which is the common case for file names, URLs and column names.

How do I use the paste() function in R programming?

Pass paste() one or more vectors. It converts each to character, recycles the shorter ones to a common length, and joins the pieces with sep (a space by default), returning a vector the length of the longest input. Add collapse = "," to then flatten that vector into a single string joined by commas.

Is paste0 faster than paste in R?

Marginally, and never enough to matter. paste0() skips inserting a separator, so on very large vectors it can be a hair quicker, but the gap is negligible in real code. Choose between them by which default separator you want, not by speed.

This is written from years of writing R, not from a product. If you spot something off in an example, email hello@thedeepflux.com and I will fix it.
Deepak YadavCrafting beautiful digital consumer products.

Product designer and indie hacker. Founder of Ossian Design Lab. Builds and ships business and consumer digital products in public.

Follow on X

Read next.

Look up.
It's all right there.