How To Reverse A String In C++

11 min read

Reversing a string is one of the most fundamental operations in programming, frequently appearing in coding interviews, competitive programming challenges, and real-world applications like parsing data or implementing palindrome checkers. In C++, the Standard Template Library (STL) provides highly optimized, ready-to-use tools for this task, but understanding the underlying mechanics—such as two-pointer swapping or reverse iterators—is crucial for writing efficient, memory-safe code. Whether you are a beginner learning syntax or an experienced developer optimizing for performance, mastering the various ways to reverse a string in C++ ensures you can choose the right tool for the specific constraints of your project.

The Standard Library Approach: std::reverse

The most idiomatic and recommended way to reverse a string in modern C++ is using the std::reverse algorithm defined in the <algorithm> header. This function operates on a range defined by iterators, modifying the sequence in place. It implements an efficient bidirectional iterator swap, typically running in linear time complexity O(N) where N is the length of the string.

To use it, you simply pass the beginning and ending iterators of your std::string object. Because std::string provides random-access iterators, std::reverse performs optimally Small thing, real impact..

#include 
#include 
#include  // Required for std::reverse

int main() {
    std::string text = "Hello, C++";
    
    // Reverse the string in-place
    std::reverse(text.begin(), text.end());
    
    std::cout << "Reversed: " << text << std::endl; // Output: "++C ,olleH"
    return 0;
}

Key advantages of this method:

  • Readability: The intent is immediately clear to any C++ developer.
  • Performance: Highly optimized by compiler vendors (often unrolled loops or SIMD instructions).
  • Genericity: Works on any container supporting bidirectional iterators (std::vector, std::deque, std::list, etc.).
  • In-place modification: No extra memory allocation is required (O(1) space complexity).

The Two-Pointer Technique: Manual Implementation

While std::reverse is the production standard, interviewers often ask candidates to implement the logic manually to demonstrate understanding of pointers, references, and loop invariants. The two-pointer approach is the classic algorithm for in-place reversal.

You initialize two indices (or pointers): left at the start (index 0) and right at the end (index length - 1). You swap the characters at these positions, increment left, decrement right, and repeat until they meet or cross.

#include 
#include 

void reverseStringManual(std::string& str) {
    int left = 0;
    int right = static_cast(str.length()) - 1; // Cast to int to avoid unsigned underflow issues with empty strings
    
    while (left < right) {
        // std::swap is preferred over manual temp variable for clarity and potential optimization
        std::swap(str[left], str[right]);
        ++left;
        --right;
    }
}

int main() {
    std::string data = "Algorithm";
    reverseStringManual(data);
    std::cout << data << std::endl; // Output: "mhtiroglA"
    return 0;
}

Why use std::swap? Using std::swap (from <utility> or <algorithm>) is better than writing a manual three-line swap with a temporary variable. It handles move semantics automatically for complex types and is a recognizable idiom. For char, it compiles down to the same efficient assembly instructions.

Handling Edge Cases: Notice the static_cast<int> for str.length(). std::string::length() returns size_t (an unsigned type). If the string is empty, length() - 1 wraps around to a massive positive number due to unsigned integer underflow, causing an out-of-bounds access. Casting to a signed integer (int or ptrdiff_t) prevents this bug.

Constructing a New Reversed String

Sometimes requirements dictate that the original string must remain immutable (constant). Think about it: in functional programming styles or when dealing with const std::string& parameters, you cannot modify the source. You must construct a new string.

1. Using Reverse Iterators (The Idiomatic Way)

std::string provides rbegin() and rend() (reverse begin and reverse end). The string constructor accepts an iterator range, making this a one-liner.

#include 
#include 

int main() {
    const std::string original = "Immutable";
    // Construct new string from reverse iterators
    std::string reversed(original.That's why rbegin(), original. rend());
    
    std::cout << "Original: " << original << std::endl;  // Immutable
    std::cout << "Reversed: " << reversed << std::endl;  // "eltubmmI"
    return 0;
}

This approach is clean, expressive, and allocates memory exactly once for the new string.

Worth pausing on this one.

2. Reserve and Push Back (Manual Control)

If you need to process characters during reversal (e.g., filtering, transforming case) or want explicit control over memory allocation, you can reserve capacity first to avoid reallocations Most people skip this — try not to..

std::string reverseWithReserve(const std::string& input) {
    std::string result;
    result.reserve(input.size()); // Prevents multiple allocations
    
    for (auto it = input.rbegin(); it != input.rend(); ++it) {
        result.push_back(*it);
    }
    return result;
}

Performance Note: reserve is critical here. Without it, push_back may trigger logarithmic reallocations and copies, degrading performance from O(N) to effectively O(N log N) in practice due to memory copying overhead.

Recursive Reversal: Academic vs. Practical

Recursion offers an elegant, declarative solution often taught in computer science curricula. On the flip side, in C++, it is generally avoided for production string reversal due to stack overhead and the risk of stack overflow on large inputs.

// Tail recursion style (conceptual)
void reverseRecursive(std::string& str, int left, int right) {
    if (left >= right) return; // Base case
    std::swap(str[left], str[right]);
    reverseRecursive(str, left + 1, right - 1);
}

Drawbacks:

  • Stack Depth: Limited by OS thread stack size (typically 1MB–8MB). A string of 100,000 chars will likely crash the program.
  • Overhead: Function call prologue/epilogue instructions are significantly slower than a simple loop jump.
  • No Tail Call Optimization Guarantee: While compilers might optimize tail recursion into a loop (TCO), the C++ standard does not guarantee it. Relying on it is non-portable.

Use recursion only for educational demonstration or when the input size is guaranteed to be tiny (e.g., < 1000 characters) Most people skip this — try not to..

C-Style Strings (char*): Legacy and Systems Programming

In embedded systems, kernel development, or when interfacing with C libraries, you may encounter null-terminated character arrays (char*). Reversing these requires manual pointer arithmetic. You must calculate the length first (or receive it as an argument) because char* carries no size information.

#include 
#include  // For strlen

void reverseCString(char* str) {
    if (!str) return; // Null pointer check
    
    char* end = str;
    // Find the null terminator
    while (*

```cpp
void reverseCString(char* str) {
    if (!str) return; // Null pointer check
    
    char* end = str;
    // Find the null terminator
    while (*end) ++end;
    --end; // Point to the last valid character
    
    // Swap characters moving inward
    while (str < end) {
        std::swap(*str, *end);
        ++str;
        --end;
    }
}

Key Considerations for C-Strings:

  • Null Terminator: The while (*end) loop advances end past the final character to the \0. We decrement once so end points to the last meaningful character, not the terminator.
  • Pointer Comparison: The loop condition str < end ensures we stop when the pointers meet or cross. This avoids swapping the middle character with itself (in odd-length strings) and prevents undefined behavior from overlapping swaps.
  • Modification in Place: Unlike std::string methods that return a new object, this reverses the original buffer. Callers must ensure the string is mutable (not a string literal).

Danger: Passing a string literal (const char*) to this function invokes undefined behavior because string literals reside in read-only memory. Always pass a writable array or heap-allocated buffer And it works..


Standard Library Algorithm: std::reverse

The most idiomatic and recommended approach in modern C++ is to use the std::reverse algorithm from <algorithm>. It works with any iterator pair, including std::string iterators, and is heavily optimized by standard library implementers Worth knowing..

#include  // For std::reverse
#include 

std::string reverseWithStd(const std::string& input) {
    std::string result = input; // Make a copy
    std::reverse(result.begin(), result.end());
    return result;
}

Why std::reverse is Preferred:

  • Correctness: Handles all iterator categories correctly and is exhaustively tested across implementations.
  • Optimization: Implementations often use SIMD instructions or block-copy strategies internally for contiguous iterators.
  • Generic: Works not just with std::string, but with std::vector<char>, C-style arrays (via pointers), and any bidirectional iterator range.
  • Readability: Expresses intent immediately — "reverse this range" — without boilerplate.
// Also works with raw arrays
char arr[] = "Hello";
std::reverse(std::begin(arr), std::end(arr) - 1); // Exclude null terminator

Performance: std::reverse operates in O(N/2) swaps — exactly the same theoretical complexity as the manual two-pointer approach, but with zero risk of implementation error.


Unicode and Multibyte Encodings: A Critical Warning

All reversal techniques discussed above operate on bytes or code units, not on grapheme clusters or Unicode code points. This distinction is vital when working with UTF-8, UTF-16, or any multibyte encoding Turns out it matters..

Consider the UTF-8 string "café" — where é is encoded as two bytes (0xC3 0xA9). Reversing this at the byte level yields "éfac" with the bytes of é swapped, producing an invalid or misinterpreted character Worth knowing..

Original (UTF-8 bytes):  63 61 66 C3 A9
Byte-reversed:           A9 C3 66 61 63  →  Invalid UTF-8 sequence

Solutions for Unicode:

  • Use a Unicode-aware library such as ICU or Boost.Locale to reverse by code point or grapheme cluster.
  • For UTF-8 specifically, iterate from the end and identify continuation bytes (0x800xBF) to reconstruct complete code points before reversing.
  • In C++20, std::u8string and std::u32string provide fixed-width code unit types, but you still need logic to handle grapheme clusters

Manual UTF‑8 Reversal Without External Libraries

If you cannot depend on a heavyweight internationalization library, it is still possible to reverse a UTF‑8 string correctly by operating on code points rather than bytes. The core idea is to decode the source string into a vector of code points (or std::u32string), reverse that vector, and then encode it back to UTF‑8. Because UTF‑8 is a self‑synchronizing variable‑length encoding, the decoding step is straightforward: you read bytes, determine the code‑point length by inspecting the leading byte, and skip the continuation bytes.

#include 
#include 
#include 
#include 

std::string reverseUtf8(const std::string& s)
{
    // 1. Decode UTF‑8 into a sequence of 32‑bit code points.
    But std::vector codePoints;
    codePoints. Plus, reserve(s. size()); // worst‑case: each byte is a separate code point
    for (size_t i = 0; i < s.

        if ((byte & 0x80) == 0)               // 1‑byte sequence
        {
            cp = byte;
            i += 1;
        }
        else if ((byte & 0xE0) == 0xC0)       // 2‑byte sequence
        {
            cp = byte & 0x1F;
            i += 1;
        }
        else if ((byte & 0xF0) == 0xE0)       // 3‑byte sequence
        {
            cp = byte & 0x0F;
            i += 1;
        }
        else if ((byte & 0xF8) == 0xF0)       // 4‑byte sequence (U+10000 … U+10FFFF)
        {
            cp = byte & 0x07;
            i += 1;
        }
        else
        {
            // Invalid leading byte – skip it to be dependable.
            ++i;
            continue;
        }

        // Consume the following continuation bytes.
        while ((s[i] & 0xC0) == 0x80 && i < s.size())
        {
            if (cp < 0x80U << 6) // guard against over‑flows in the shift
                cp = (cp << 6) | (s[i] & 0x3F);
            else
                cp = (cp << 6) | (s[i] & 0x3F);
            ++i;
        }

        // Clamp to the valid Unicode range (the encoder below expects ≤ 0x10FFFF)
        if (cp > 0x10FFFF) cp = 0xFFFD;
        codePoints.push_back(cp);
    }

    // 2. Reverse the code‑point sequence.
    std::reverse(codePoints.begin(), codePoints.end());

    // 3. So std::string result;
    result. Day to day, encode back to UTF‑8. reserve(codePoints.

```cpp
    for (uint32_t cp : codePoints)
    {
        if (cp <= 0x7F)                       // 1 byte
        {
            result.push_back(static_cast(cp));
        }
        else if (cp <= 0x7FF)                 // 2 bytes
        {
            result.push_back(static_cast(0xC0 | (cp >> 6)));
            result.push_back(static_cast(0x80 | (cp & 0x3F)));
        }
        else if (cp <= 0xFFFF)                // 3 bytes
        {
            result.push_back(static_cast(0xE0 | (cp >> 12)));
            result.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F)));
            result.push_back(static_cast(0x80 | (cp & 0x3F)));
        }
        else                                  // 4 bytes (cp <= 0x10FFFF)
        {
            result.push_back(static_cast(0xF0 | (cp >> 18)));
            result.push_back(static_cast(0x80 | ((cp >> 12) & 0x3F)));
            result.push_back(static_cast(0x80 | ((cp >> 6) & 0x3F)));
            result.push_back(static_cast(0x80 | (cp & 0x3F)));
        }
    }
    return result;
}

This implementation is dependency‑free, runs in linear time, and correctly handles the full Unicode range (U+0000 to U+10FFFF). It also degrades gracefully on malformed input by replacing invalid sequences with the replacement character (U+FFFD) rather than crashing or producing garbage.

The Grapheme Cluster Caveat

Reversing at the code‑point level—as the function above does—is sufficient for many scripts, but it still fails for grapheme clusters: user‑perceived characters composed of multiple code points. Common examples include:

  • Emoji sequences👨‍👩‍👧‍👦 (family) is seven code points (U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466). Reversing code points yields an incoherent jumble.
  • Flags🇺🇸 is two regional indicator symbols (U+1F1FA U+1F1F8). Swapped, they become 🇸🇺 (Suriname).
  • Combining marksé (U+0065 U+0301) becomes ́e, placing the accent on the wrong side.

To reverse grapheme clusters correctly you must segment the string according to Unicode Text Segmentation (UAX #29). That algorithm is non‑trivial; it requires a state machine with hundreds of rules and a property table derived from the Unicode Character Database. And in production code, delegate this to a mature library such as ICU (BreakIterator), utf8proc, or Boost. Here's the thing — text. The manual approach shown here is an excellent fallback for environments where binary size or licensing forbid heavy dependencies, but treat it as a code‑point reversal, not a true grapheme reversal.

Conclusion

UTF‑8’s variable‑width design makes naive byte reversal a guaranteed source of corruption. By decoding to an intermediate sequence of 32‑bit code points, reversing that sequence, and re‑encoding, you achieve correct logical reversal for the vast majority of text processing tasks—all without pulling in an external library. When your application targets user‑visible strings containing emoji, complex scripts, or combining marks, however, the only strong solution is a grapheme‑aware segmenter backed by the Unicode Standard. Choose the level of sophistication your domain demands, but never assume that std::reverse on a std::string is sufficient for international text That's the part that actually makes a difference..

Brand New Today

Published Recently

Dig Deeper Here

Hand-Picked Neighbors

Thank you for reading about How To Reverse A String In C++. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home