YeetCode
Data Structures & Algorithms · Strings - Advanced

Reverse Words in a String: Trim, Split, Reverse, Join

Reverse the word order in a string while collapsing extra whitespace — the trim/split/reverse/join pipeline in JavaScript, with a full worked walkthrough.

6 min readBy Bhavesh Singh
stringsstring parsingwhitespacereverseleetcode medium

This article has an interactive companion

Don't just read it — step through it interactively, one state change at a time.

Open the Reverse Words in a String visualizer

Reverse Words in a String looks trivial until you notice the whitespace. Reversing the words is one line; the real work is cleaning up the messy spacing that the input throws at you — leading spaces, trailing spaces, and runs of two or three spaces wedged between words.

Get the ordering of operations right and the whole thing is a four-step pipeline: trim the edges, split on whitespace groups, reverse the list, join with single spaces. Get it wrong and you leak empty strings into your word list and pollute the output with double spaces.

This guide teaches the exact pipeline the visualizer runs, in the same order, so the code, the trace, and the animation all agree.

The problem

Given a string s, reverse the order of the words. A word is a maximal run of non-space characters. The input may have leading spaces, trailing spaces, and multiple spaces between words. The output must have the words in reverse order, separated by exactly one space, with no leading or trailing whitespace.

text
Input: s = " the sky is blue " Output: "blue is sky the"

Two requirements do all the damage:

  • Whitespace is dirty on the way in, clean on the way out. The output spacing is fixed at single spaces regardless of what the input looked like.
  • A "word" is defined by non-space runs, so you can't naively split on a single space character — that produces empty tokens wherever two spaces sit side by side.

The brute force baseline

The tempting first attempt splits on a literal space and reverses:

javascript
function reverseWords(s) { const words = s.split(" "); // splits on EVERY single space words.reverse(); return words.join(" "); }

On clean input like "the sky is blue" this works. On the real input it falls apart. " the sky is blue ".split(" ") yields:

text
["", "", "the", "sky", "", "", "is", "blue", "", ""]

Every extra space becomes an empty string, and every leading/trailing space adds one at the ends. Reverse and join that and you get " blue is sky the " — the mess is preserved, just mirrored. You could patch it by filtering out empty strings after the split, but that's fighting the tool. The split itself is the wrong split.

The key insight

Split on whitespace groups, not single characters. The regex /\s+/ matches one or more consecutive whitespace characters and treats the whole run as a single delimiter. That collapses "is blue" and "is blue" into the same two-token result — the double-space problem disappears at the split.

There's one leftover edge: if the string starts or ends with whitespace, split(/\s+/) still produces a leading or trailing empty string (the split sees whitespace before the first real character). The fix is to trim() first, so the split only ever operates on interior gaps. Order matters: trim, then split.

That reframes the whole problem into a clean four-stage pipeline where each stage has one job and hands a clean value to the next.

The optimal solution

This is the exact algorithm the visualizer runs — trim, split on whitespace groups, reverse in place, join with single spaces:

javascript
function reverseWords(s) { s = s.trim(); let words = s.split(/\s+/); words.reverse(); return words.join(" "); }

Four operations, each pulling its weight:

  • trim() strips leading and trailing whitespace so the split never emits boundary empties.
  • split(/\s+/) breaks the string into words, collapsing any run of spaces into one delimiter.
  • reverse() flips the array in place — it's the classic two-pointer swap from both ends inward, O(n).
  • join(" ") rebuilds the string with exactly one space between words, normalizing the spacing for free.

Walkthrough

Tracing s = " the sky is blue " through each stage:

StageOperationState after
1s.trim()"the sky is blue" (2 leading + 2 trailing spaces removed)
2s.split(/\s+/)["the", "sky", "is", "blue"] (the triple space between "sky" and "is" collapses to one delimiter)
3words.reverse()["blue", "is", "sky", "the"]
4words.join(" ")"blue is sky the"

The stage-2 collapse is the payoff. The input had a run of three spaces between sky and is; because /\s+/ matches the whole run as one delimiter, no empty token appears in the array. Compare that to split(" "), which would have left two "" tokens sitting between "sky" and "is". By the time join(" ") runs, the spacing is uniform no matter how irregular the input was.

Complexity

ApproachTimeSpaceWhy
Split on " " + filterO(n)O(n)one pass to split, another to drop empties
Trim + split(/\s+/) + reverse + joinO(n)O(n)each stage is a single linear pass over the characters or words

Every stage touches each character or word a constant number of times: trim scans the ends, split scans once, reverse swaps n/2 pairs, join concatenates once. Total time is linear in the length of the string. Space is O(n) for the words array and the output string — you can't reverse word order without materializing the words somewhere.

Common mistakes

  • Splitting on " " instead of /\s+/. A single-space split emits empty strings between every extra space and at trimmed-off boundaries. You then have to filter them out — extra work the regex avoids.
  • Trimming after splitting instead of before. If you split first, leading/trailing whitespace has already produced boundary empty strings; trimming the array elements won't remove those phantom tokens. Trim the string first.
  • Joining with the original spacing. The output contract is single spaces. Rebuilding with anything but join(" ") risks leaking the original irregular gaps.
  • Assuming reverse() returns a new array. Array.prototype.reverse() mutates in place and returns the same array. That's fine here, but if you needed the original order later, you'd have to copy first.

Where this pattern shows up next

Whitespace-aware string parsing — split cleanly, transform, rebuild — recurs across a whole family of string problems:

  • Decode String — parse a structured string, but with a stack driving nested expansion instead of a flat split.
  • Count and Say — build each term by scanning runs of identical characters, the same run-grouping instinct as /\s+/.
  • Sum of Beauty of All Substrings — character-frequency bookkeeping across every substring window.
  • Reorganize String — rearrange characters under a constraint, where counting drives placement.

You can also step through Reverse Words in a String interactively to watch the pipeline run one stage at a time.

FAQ

Why use split(/\s+/) instead of split(" ")?

split(" ") splits on every single space character, so a run of two or three spaces produces empty-string tokens, and leading or trailing spaces add empties at the ends. split(/\s+/) matches one or more whitespace characters as a single delimiter, collapsing any run into one break. That eliminates the empty tokens at the source, so you never have to filter them out afterward.

Why trim the string before splitting?

Even with /\s+/, a string that starts or ends with whitespace produces a leading or trailing empty string, because the regex sees whitespace before the first real character (or after the last). Calling trim() first removes those edge spaces, guaranteeing the split only operates on the gaps between real words. Trim first, then split — the order is load-bearing.

What is the time and space complexity?

Both are O(n), where n is the length of the string. Trim, split, reverse, and join each make a single linear pass over the characters or words. Space is O(n) because you build a words array and a new output string; there's no way to reverse word order fully in place in JavaScript since strings are immutable.

Does reverse() reverse the letters inside each word?

No. Array.prototype.reverse() reverses the array of words, so ["the", "sky", "is", "blue"] becomes ["blue", "is", "sky", "the"]. The characters inside each word stay in their original order. Reversing the letters too would be a different problem ("Reverse Words in a String III"), solved by reversing each word individually rather than the word order.

Make it stick: run this one yourself

Don't just read it — step through it interactively, one state change at a time.

Open the Reverse Words in a String visualizer