skip to content

When is using the plain + operator for String concatenation perfectly acceptable, and how do you decide between +, StringBuilder, and String.format on a given line of code?

level: juniorimportance: should knowfreq 45%

answer

  1. Myth: 'never use + for Strings' — false
  2. Fixed single expression → + (compiler fuses it)
  3. Accumulate across loop iterations → StringBuilder (else O(n^2))
  4. String.format = readable template, but parses at runtime → slower, avoid in hot loops
  5. Joining a collection → String.join / Collectors.joining

basics

~20 s

Plain + is fine whenever you join a fixed, small number of strings in one expression and not inside a loop — like building a log message or a greeting. Use StringBuilder when you build up a string across loop iterations. String.format is for readable templated text when speed isn't critical.

solid answer

~40 s

Plain + is the right default for a single, fixed concatenation expression — `"User " + id + " logged in"` — because the compiler fuses the whole expression into one efficient operation (one StringBuilder pre-Java-9, an invokedynamic/StringConcatFactory call since Java 9), so there's no repeated copying. It's only dangerous when you *accumulate across loop iterations* (`s += x` n times), which is O(n^2); there you must use StringBuilder. String.format (and similar templating) is for readability when you have a fixed template with several inserts and performance isn't critical — it's slower than + because it parses a format string at runtime, so don't use it on hot paths or in tight loops. Rule of thumb: fixed expression → +; build-up in a loop → StringBuilder; readable template, cold path → String.format.

go deeper

for a junior

Knows + is fine for simple fixed concatenations and not to use it in loops; can pick StringBuilder for loop build-up.

for a middle

Explains that the compiler fuses a single expression so + is efficient, and that String.format trades speed for readability (avoid in hot loops).

for a senior

Articulates the expression-vs-loop distinction precisely, knows the Java 9 invokedynamic detail, and picks String.join/Collectors.joining for delimiter joins.

for a principal

Sets readability-first conventions (default to +, reserve StringBuilder for measured loop hotspots, restrict String.format on hot paths) and educates the team against the 'never use +' cargo cult.

## The myth to dispel A common over-correction is 'never use + for Strings, always use StringBuilder'. That's wrong and makes code noisier. The truth is narrower: **+ is only a problem when you accumulate across many loop iterations.** Everywhere else, + is fine and usually clearest. ## Why plain + is fine for a single expression String is **immutable** (can't be changed in place), and `+` is the concatenation operator. The cost concern with immutability is *repeated re-copying*. But a single concatenation **expression** is compiled as one fused operation: - **Java 8 and earlier:** `javac` lowers `a + b + c` into a *single* StringBuilder with appends and one `toString()`. - **Java 9+:** it emits one `invokedynamic` instruction bootstrapped by `StringConcatFactory`, which the runtime turns into an optimized, often single-allocation concatenation. Either way the *whole expression* is handled together — no quadratic re-copy. So: ```java String msg = "User " + userId + " logged in at " + time; // perfectly fine ``` is efficient and readable. Wrapping that in a manual StringBuilder gains nothing and reads worse. ## When + becomes the O(n^2) trap The danger is *accumulation across iterations*: ```java String s = ""; for (String part : parts) { s += part; // separate statement each loop → O(n^2) } ``` Here each iteration is its own statement reading the previous result, so the compiler can't fuse them, and immutability forces a full re-copy each time. **This** is where you switch to StringBuilder (O(n)). ## Where String.format fits `String.format("...", args)` (and `printf`-style templating) builds a string from a **format string** with placeholders, e.g. `String.format("User %s logged in at %s", id, time)`. Its advantage is **readability** for templated text — the layout is visible in one literal, with locale/number/date formatting built in. The cost: at runtime it **parses the format string** and uses reflection-ish machinery, so it's noticeably **slower** than + or StringBuilder. That's fine for cold paths (startup messages, error text, occasional logging) but a bad idea in **tight loops or hot paths** — there, prefer + (fixed expression) or StringBuilder (accumulation). ## Decision guide for a single line - **A fixed, small number of pieces, evaluated once (not in a loop):** use **+**. Clearest, and compiler-optimized. - **Building a string up across loop iterations:** use **StringBuilder** (pre-size if you can). - **A readable template with several inserts, on a non-hot path, especially needing locale/number/date formatting:** use **String.format**. - **Joining a collection with a delimiter:** prefer **String.join** / **Collectors.joining** over a manual loop. The through-line: optimize for **readability by default**, and only reach for StringBuilder when the *loop-accumulation* pattern would actually make it O(n^2).

  • Why is String.format usually slower than + for the same output?
    It parses the format string and dispatches per-placeholder formatting at runtime, which adds overhead. Plain + on a fixed expression is fused by the compiler into one efficient concatenation, so it skips that parsing cost.
  • Is `"a" + b + c` outside a loop worth converting to a StringBuilder?
    No. A single concatenation expression is already compiled into one fused/optimized operation, so a manual StringBuilder adds verbosity with no performance gain.

saying these in an interview costs you the question

  • Insisting on StringBuilder for every concatenation, including single fixed expressions — needless noise with no benefit.
  • Using String.format inside a tight loop or hot path — it parses the format string each call and is much slower.
  • Thinking + in a single expression creates many intermediate Strings — the compiler fuses it into one operation.
  • Believing String.format is 'just as fast' as + — it isn't; it trades speed for readability.

context