# fmt::format vs fmt::print: The Core Difference in C++ String Formatting

> Understand the core difference between fmt::format and fmt::print in C++ string formatting. Learn when to use fmt::format to return a string and fmt::print to output directly.

- Repository: [Hello World Foundation/fmt](https://github.com/fmtlib/fmt)
- Tags: deep-dive
- Published: 2026-09-06

---

**`fmt::format` returns a formatted `std::string` for further use, while `fmt::print` writes formatted output directly to a stream or `stdout` without returning anything.**

Both functions are part of the **{fmt}** library's public API, but they solve different problems in C++ string formatting. Understanding when to use each can eliminate unnecessary string allocations and improve your application's I/O performance. This guide breaks down their differences using the actual implementation in `fmtlib/fmt`.

---

## Return Type and Basic Behavior

The most immediate distinction is what each function returns:

| Function | Return Type | Primary Action |
|----------|-------------|--------------|
| `fmt::format` | `std::string` | Constructs and returns a string object |
| `fmt::print` | `void` | Performs side-effect output, returns nothing |

This difference shapes every other characteristic of these two APIs.

---

## Implementation Details from the Source Code

### fmt::format Implementation

The `fmt::format` function template is defined in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) at lines 4600–4603:

```cpp
template <typename... T>
FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)
    -> std::string {
  return vformat(fmt.str, vargs<T...>{{args...}});
}

```

This implementation:
- Calls `vformat` internally to produce the final string
- Uses a temporary `detail::counting_buffer` to determine the required size before allocating the result
- Returns a newly constructed `std::string` containing the formatted output

### fmt::print Implementation

The `fmt::print` function has two primary overloads. The convenience version that defaults to `stdout` lives in [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h) at lines 2890–2892:

```cpp
template <typename... T>
FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
  vprint(stdout, fmt.str, vargs<T...>{{args...}});
}

```

The generic overload accepting any `FILE*` is in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) at lines 1084–1088:

```cpp
inline void print(FILE* f, format_string<T...> fmt, T&&... args) {
  if (detail::use_utf8) vprint(f, fmt.str, vargs<T...>{{args...}});
  else vprint(f, fmt.str, vargs<T...>{{args...}});
}

```

Both overloads delegate to `vprint` or `detail::print`, which write directly to the output destination without creating an intermediate string object.

---

## When to Use fmt::format

Use `fmt::format` when you need the formatted text for subsequent operations:

```cpp
#include <fmt/format.h>

int main() {
    // Build a message for logging system
    std::string msg = fmt::format("The answer is {}.", 42);
    
    // msg can be stored, modified, or passed to other APIs
    logger.write(msg);
    network.send(msg);
    
    // String concatenation before final output
    std::string header = fmt::format("[{}] ", timestamp());
    std::string full_msg = header + msg;
}

```

Common use cases include:
- Preparing messages for logging frameworks
- Constructing JSON or XML payloads
- Building SQL queries with escaped parameters
- Formatting values for unit test assertions
- Creating strings that will be stored or transmitted

---

## When to Use fmt::print

Use `fmt::print` when you want immediate output without retaining the text:

```cpp
#include <fmt/print.h>

int main() {
    // Direct console output—no string allocation
    fmt::print("The answer is {}.\n", 42);
}

```

File output using `fmt::output_file` (from [`include/fmt/os.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h)):

```cpp
#include <fmt/os.h>

int main() {
    auto out = fmt::output_file("results.txt");
    out.print("Processing complete: {} items\n", item_count);
    // Writes directly, no intermediate std::string
}

```

Common use cases include:
- Console applications and CLI tools
- Writing progress indicators or status messages
- Log files where you don't need the string later
- Real-time data streams
- Any "fire-and-forget" output scenario

---

## Performance Comparison

The performance difference stems from **memory allocation behavior**:

- **`fmt::format`** — Always allocates memory for the returned `std::string` (unless you use the overload with `fmt::basic_memory_buffer`). This allocation and the resulting copy can be expensive in hot loops.

- **`fmt::print`** — Avoids the string allocation entirely when writing to an already-opened stream. The formatted data flows directly from the internal buffer to the output destination.

For high-frequency output (logging millions of lines, progress updates), prefer `fmt::print`. For batched or conditional output where you may not print at all, `fmt::format` gives you flexibility to decide later.

---

## Thread Safety Considerations

Both functions are thread-safe in their internal operations, but the surrounding context differs:

- **`fmt::format`** — The returned string is a self-contained value. Once constructed, it can be passed safely between threads with no synchronization concerns.

- **`fmt::print`** — Thread safety depends on the output destination. `stdout` is typically synchronized by the C runtime library, but `FILE*` streams and `std::ostream` objects may require external synchronization if shared across threads.

---

## C API Equivalents

The {fmt} library exposes C-compatible versions of both functions:

| C++ Function | C Equivalent | Behavior |
|--------------|--------------|----------|
| `fmt::format` | `fmt_format` | Returns a newly allocated C string (caller must free) |
| `fmt::print` | `fmt_print` | Writes directly to a `FILE*` |

These are useful when integrating {fmt} into C codebases or writing language bindings.

---

## Summary

- **`fmt::format`** constructs and returns a `std::string`—use it when you need the formatted text for storage, transmission, or further processing
- **`fmt::print`** writes directly to output destinations with `void` return—use it for immediate display or file I/O without allocation overhead
- The implementations in [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) and [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h) share formatting logic but diverge at the final step: string construction vs. direct output
- Choose `fmt::print` for performance-critical output loops; choose `fmt::format` when you need the string value itself

---

## Frequently Asked Questions

### Can I use fmt::print with std::ostream instead of FILE*?

Yes. Include [`include/fmt/ostream.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h) to get `fmt::print` overloads that accept `std::ostream&`. This allows integration with C++ streams while maintaining the same direct-output semantics.

### Is there a way to use fmt::format without allocating memory?

Yes. Use the overload that takes `fmt::basic_memory_buffer` as an output parameter. This lets you provide your own buffer and avoid dynamic allocation for known-size formatting.

### Why does fmt::print have two separate implementations in base.h and format.h?

The [`include/fmt/base.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/base.h) version provides the minimal `stdout`-only functionality for lightweight use cases. The [`include/fmt/format.h`](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) version adds the full `FILE*` overload and integrates with the complete formatting system. This split reduces compile times when you only need basic functionality.

### Can I mix fmt::format and fmt::print in the same program?

Absolutely. They are designed to work together. A common pattern is using `fmt::format` to prepare complex messages, then `fmt::print` to output them, as shown in the source analysis examples.