# How absl::Cord Achieves Better Performance Than std::string for Large Strings

> Discover how absl::Cord outperforms std::string for large strings with its chunked, reference-counted tree structure, enabling O(1) operations and avoiding costly reallocations.

- Repository: [Abseil/abseil-cpp](https://github.com/abseil/abseil-cpp)
- Tags: performance
- Published: 2026-07-13

---

**absl::Cord uses a chunked, reference-counted tree structure to provide O(1) append, prepend, and copy operations for large strings, avoiding the reallocations and memory copies required by std::string's contiguous buffer.**

For applications handling large, mutable text in the `abseil/abseil-cpp` library, `absl::Cord` delivers significant performance advantages over `std::string` by abandoning the single contiguous buffer model. Instead, it stores data in a tree of chunks that enables constant-time mutations at the ends and cheap sharing between instances. This design fundamentally changes the complexity characteristics of string operations for large datasets.

## Chunked Architecture: The Foundation of absl::Cord Performance

While `std::string` maintains a single contiguous memory block, `absl::Cord` organizes data as a tree of immutable chunks (often called "flats" or nodes). According to the source comments in [`absl/strings/cord.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/cord.h) (lines 31-41), this structure specifically optimizes three scenarios: efficient insertions at the beginning or end, zero-copy external memory references, and cheap copy-on-write sharing.

### O(1) Append and Prepend Operations

Appending to a large `std::string` requires O(N) time when reallocation occurs, as the entire buffer must be copied to new memory. In contrast, `absl::Cord` achieves O(1) amortized complexity by adding new chunks to the tree or filling spare capacity in existing nodes without disturbing existing data.

The implementation in `absl/strings/cord.cc` demonstrates this through `AppendPrecise` (lines 9-18), which writes directly into an existing inline buffer or appends a new flat chunk without touching the rest of the data. Similarly, `PrependPrecise` (lines 21-32) mirrors this logic for front insertions, making both operations constant-time regardless of the Cord's total size.

### Copy-on-Write and Cheap Substring Operations

Copying a large `std::string` always allocates new memory and copies every byte (O(N)). `absl::Cord` copies are O(1) because they simply increment reference counts on the underlying chunk tree. As documented in [`absl/strings/cord.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/cord.h) (lines 40-44), this copy-on-write semantics means multiple Cords can share the same underlying data until a mutation actually requires a copy.

Substrings (created via `Subcord`) are equally efficient, representing views over existing chunks rather than new allocations. This makes extracting portions of multi-gigabyte strings nearly instantaneous.

## Performance Comparison: absl::Cord vs std::string

The trade-offs between these containers become clear when examining operation complexity:

| Feature | absl::Cord | std::string |
|---------|--------------|---------------|
| **Appending / Prepending** | O(1) amortized – adds new chunks without copying existing data | O(N) – requires reallocation and full buffer copy when capacity exceeded |
| **Copy / Assignment** | O(1) – reference count increment only | O(N) – allocates new buffer and copies all data |
| **Substring** | O(1) – tree view over existing chunks | O(N) – allocates new buffer for substring |
| **External Memory** | Zero-copy via `MakeCordFromExternal` | Always copies data into owned buffer |
| **Random Access** | O(log N) – traverses chunk tree | O(1) – direct pointer arithmetic |
| **Memory Overhead** | Higher (tree metadata, reference counts) | Lower (single pointer + size/capacity) |

## Zero-Copy External Memory Integration

A unique advantage of `absl::Cord` is the ability to reference externally allocated memory without copying. The `MakeCordFromExternal` function wraps existing buffers in a Cord interface with a custom deletion callback. This is impossible with `std::string`, which always assumes ownership and copies data into its own allocation.

This capability is particularly valuable when interfacing with memory-mapped files, network buffers, or C APIs where the data lifecycle is managed externally.

## Implementation Details in Abseil Source Code

The performance characteristics stem from specific implementation choices across several key files:

- **[`absl/strings/cord.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/cord.h)** (lines 19-27): Defines `CopyCordToString`, which efficiently converts Cord data to `std::string` by reusing the destination's capacity when possible, avoiding redundant allocations.
- **`absl/strings/cord.cc`**: Contains the core mutation logic including `AppendPrecise` and `PrependPrecise` that implement the O(1) end operations.
- **[`absl/strings/internal/cord_rep_flat.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/internal/cord_rep_flat.h)**: Defines flat chunk objects used when appending large amounts of data without creating deep tree structures.
- **[`absl/strings/internal/cord_rep_btree.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/internal/cord_rep_btree.h)**: Implements the B-tree representation that enables O(log N) navigation while maintaining O(1) end operations.
- **[`absl/strings/internal/cord_internal.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/internal/cord_internal.h)**: Provides low-level helpers like `RemoveCrcNode` and `SkipCrcNode` that keep the chunk tree efficient during mutations.

## Practical Examples: Using absl::Cord for Large Strings

```cpp
#include "absl/strings/cord.h"
#include <string>
#include <iostream>

int main() {
    // 1. Build a huge cord by repeatedly appending without reallocations.
    absl::Cord big;
    for (int i = 0; i < 1'000'000; ++i) {
        big.Append("x");               // O(1) per iteration
    }
    std::cout << "size: " << big.size() << '\n';

    // 2. Zero-copy external memory.
    const char* data = "already-allocated payload";
    absl::Cord external = absl::MakeCordFromExternal(
        absl::string_view(data, strlen(data)),
        [](absl::string_view) { /* no-op releaser */ });
    // No copy of `data` happened.

    // 3. Cheap copy / sharing.
    absl::Cord copy = big;             // Only ref-count increments (O(1))
    std::cout << "copy size: " << copy.size() << '\n';

    // 4. Fast sub-cord (view) without copying.
    absl::Cord view = big.Subcord(100, 200);  // O(1) view over the same chunks
    std::cout << "view size: " << view.size() << '\n';

    // 5. Convert to std::string efficiently if we already have capacity.
    std::string dst;
    dst.reserve(big.size());           // allocate once
    absl::CopyCordToString(big, &dst); // reuses capacity, no extra allocs
    std::cout << "dst length: " << dst.size() << '\n';
}

```

Appending, prepending, and sub-cord operations remain cheap because they manipulate the underlying chunk tree rather than moving large blocks of memory. The external-memory example demonstrates avoiding any copy at all, something a plain `std::string` cannot achieve.

## Summary

- **Chunked storage** eliminates O(N) reallocations during growth, making `absl::Cord` ideal for incrementally building large strings.
- **O(1) copy and assignment** via reference counting allows efficient passing and sharing of large immutable datasets.
- **Zero-copy external memory** integration enables Cords to reference existing buffers without allocation overhead.
- **Fast end modifications** (append/prepend) operate in constant time regardless of string size, unlike `std::string`'s linear complexity.
- **Trade-off**: Random access is O(log N) rather than O(1), and memory overhead is higher due to tree metadata.

## Frequently Asked Questions

### When should I choose absl::Cord over std::string?

Use `absl::Cord` when working with strings larger than a few kilobytes that undergo frequent appending, prepending, or copying. It is also superior when you need to share large immutable data between multiple objects or reference external memory without copying. For small strings or heavy random-access workloads, `std::string` remains the better choice due to cache locality and O(1) indexing.

### How does absl::Cord handle memory management?

`absl::Cord` uses an intrusive reference-counting system on its internal nodes (chunks). When you copy a Cord, the implementation increments atomic reference counts on the underlying tree nodes rather than allocating new memory. When the last reference is destroyed, the nodes are deallocated. This copy-on-write mechanism ensures that mutations only copy the specific chunks being modified, not the entire data structure.

### Is random access slower with absl::Cord?

Yes. Because data is distributed across multiple chunks, accessing character `i` requires traversing the tree to locate the correct chunk, resulting in O(log N) complexity compared to `std::string`'s O(1) pointer arithmetic. For applications requiring intensive character-by-character iteration or frequent arbitrary indexing, `std::string` provides better cache performance and lower latency.

### Can I convert absl::Cord to std::string without copying?

You cannot avoid copying the actual character data when converting to `std::string` because `std::string` requires contiguous storage. However, you can avoid *extra* allocations by using `absl::CopyCordToString`, defined in [`absl/strings/cord.h`](https://github.com/abseil/abseil-cpp/blob/main/absl/strings/cord.h) (lines 19-27). If the destination `std::string` already has sufficient capacity reserved, this function writes directly into that buffer, preventing the double-allocation that would occur with a naive copy.