parikshan

reverse-string

Difficulty: Easy  ·  Topic: Two Pointers  ·  Practice it on parikshan: /practice/reverse-string/start

The story

Cloudflare's edge cache assigns each cached object a key derived from the request URL. When the team rolled out a new "consistent suffix routing" feature, the routing layer needed the reversed form of the key to compute the routing prefix. The key strings are at most 100 kilobytes long and the operation runs on every hot-path request, so the function had to be tight, no allocations, no unnecessary copies. The on-call engineer wrote the four-line two-pointer reverse and shipped it.

That is reverse-string. It is the purest form of the converging two-pointer pattern, the cleanest place to learn the skeleton that powers valid-palindrome, reverse-words-in-string, and a dozen variants.

What's actually being asked

Given a string s, print the string with its characters in reverse order, followed by a newline. The input may be empty.

The naive approach

Slice with step -1:

print(s[::-1])

O(n) time, O(n) memory for the new string. In Python this is the right answer; the slice idiom is what every Pythonista uses in real code. The skill the problem trains is the two-pointer alternative, which generalises to languages without slicing (C, Go, Rust) and to in-place buffer reversal.

The key insight

A string is symmetric only if every pair (s[i], s[n - 1 - i]) matches. Reversing just enforces that symmetry by swapping every such pair. Two pointers at the ends, walking inward, swapping as they go, do exactly this in one pass.

The complete pattern:

  • l = 0, r = n - 1.
  • While l < r: swap s[l] and s[r]; increment l; decrement r.

In Python, strings are immutable, so you cannot swap a character in place. You convert to a list, swap, and "".join at the end, O(n) space anyway. In C or Rust on a mutable byte buffer, the swap is in-place and the whole reverse is O(1) extra memory.

Step-by-step approach

The Pythonic one-liner:

  1. Read the input line, strip the trailing newline.
  2. Print s[::-1].

The two-pointer version (language-agnostic skeleton):

  1. Read the input line, strip the trailing newline, convert to a list of characters chars.
  2. Set l = 0, r = len(chars) - 1.
  3. While l < r:
    • Swap chars[l] and chars[r].
    • Increment l. Decrement r.
  4. Print "".join(chars).

Both are O(n) time.

Worked example

s = "hello"
n = 5

Two-pointer trace:

steplrchars beforeswapchars after
104[h,e,l,l,o]swap(h,o)[o,e,l,l,h]
213[o,e,l,l,h]swap(e,l)[o,l,l,e,h]
322[o,l,l,e,h]exit (l !< r)[o,l,l,e,h]

Output: olleh.

For s = "ab": one swap, output ba. For s = "": loop never runs, output is the empty line.

Complexity

  • Time: O(n). Each character is touched at most twice (once on the swap, once on the join).
  • Space: O(1) in a mutable-string language; O(n) in Python because of immutability.

The slice idiom s[::-1] is the same O(n) time, O(n) memory.

Common pitfalls

  • Off-by-one on the indices. r starts at n - 1, not n.
  • Using <= in the loop condition. When l == r you swap a character with itself and waste a step; correctness survives but the code is uglier. Use <.
  • Reversing a Python string directly. s[l], s[r] = s[r], s[l] raises TypeError because strings are immutable; you must convert to a list first.
  • Forgetting that print already adds a newline. Calling print(s[::-1] + "\n") produces two newlines.
  • Stripping more than the trailing newline. Calling s.strip() removes leading and trailing whitespace, which corrupts inputs that intentionally start or end with a space. Use rstrip('\n').

Where this shows up in the enterprise

  • Cloudflare edge cache: reversing cache keys to compute consistent-hash routing prefixes.
  • Bloomberg ticker normalisation: reversing ticker strings for suffix-trie lookups of related instruments.
  • Stripe descriptor processing: reversing the alphanumeric portion of merchant descriptors to deduplicate across the prefix-anchored vs suffix-anchored index.
  • GitHub Actions logs: tailing logs by reading the file in reverse-line order, where each line gets reversed for streaming display.
  • Slack message search: suffix-array construction for substring queries starts by reversing each message.
  • Spotify URI normalisation: reverse-mode hash for some legacy schemes.
  • Linux kernel string utilities: strrev and friends.
  • MySQL REVERSE() built-in: the function exists in every major SQL engine and is the same O(n) operation behind the scenes.

The pattern is "swap the symmetric pair around the centre". Any system that needs reversed-key processing, suffix indexing, or palindromic checks reaches for this two-pointer skeleton.

Variants you'll see elsewhere

  • valid-palindrome: converging pointers with an extra skip rule.
  • reverse-words-in-string: tokenise then reverse the token order.
  • reverse-string-ii: reverse the first k characters of every chunk of 2k.
  • reverse-only-letters: reverse but skip non-letter positions.
  • reverse-vowels-of-a-string: same skeleton with a different predicate at each pointer.
  • reverse-integer: numeric variant, reverse the digits, watch for overflow.

How parikshan turns this into a learning loop

After you read this article and click through to the practice link, the parikshan flow integrates four layers:

  1. The editor runs your code against public tests for instant feedback and against per-session dynamic private tests (generated freshly per session). Synthetic cases include the empty string, single character, two characters, palindromes (the output equals the input), and strings containing spaces, digits, or punctuation.
  2. Hints are graduated. Level 1 prompts you about walking from the back. Level 4 gives you the full slice idiom and the two-pointer skeleton.
  3. The AI training assistant in exam mode offers Explain, Review, Find Bugs, Add Comments, Optimise, each at an integrity cost. In practice mode AI chat is free.
  4. The dispute flow runs after the verdict. The most common dispute here is "I forgot to strip the trailing newline and my output has an extra blank line". Read the spec.

Read this concept, attempt in practice mode, then redo in exam mode without notes. When the converging-pointer skeleton is muscle memory, every related problem feels two-thirds done before you start.

In the AI-integrated workspace

In your future job, the agent writes the code. Your job is to:

  1. Recognise this as the base case for the converging-pointer family. Once you see it, you can re-derive valid-palindrome, reverse-vowels, reverse-words in your head.
  2. Tell the agent precisely: in Python use s[::-1]; in C/Go/Rust use two pointers swapping on a mutable byte buffer. The agent will reach for whichever the surrounding code calls for if you provide context; without context it will choose the obvious slice and ignore the memory budget.
  3. Read the generated code and verify: the slice produces a new string (not a list of characters), the loop bound is l < r (strict), and the final print does not add a duplicate newline.
  4. Push the edge cases the agent will miss: empty string, single character, strings with leading/trailing whitespace that must be preserved.

Candidates who can spot the converging-pointer family in five seconds turn agents into leverage. Candidates who can't end up approving a reversed(s) call that returns an iterator instead of a string and ships broken code. parikshan is built to train the first kind.