โ† Back to DevBytes

Solving ZigZag Conversion in JavaScript: Step-by-Step Guide

Solving ZigZag Conversion in JavaScript: Step-by-Step Guide

The ZigZag Conversion problem is a classic algorithmic challenge that frequently appears in coding interviews and competitive programming platforms. It asks you to take a string and a number of rows, then rearrange the characters as if they were written in a zigzag pattern across those rows, reading row by row to produce the output. While the problem sounds deceptively simple, it teaches fundamental concepts about string manipulation, index tracking, and pattern recognition.

What Is the ZigZag Conversion Problem?

Given a string s and an integer numRows, you write the characters of s in a zigzag pattern across numRows rows. Once the entire string is laid out, you read the characters row by row to form the converted string.

For example, with s = "PAYPALISHIRING" and numRows = 3, the layout looks like this:

P   A   H   N
A P L S I I G
Y   I   R

Reading row by row produces "PAHNAPLSIIGYIR". With numRows = 4, the same string becomes "PINALSIGYAHRPI". The key insight is that characters travel downward until they hit the bottom row, then travel diagonally upward until they hit the top row, and the cycle repeats.

Why It Matters

Beyond being a popular interview question, the ZigZag Conversion problem sharpens several practical developer skills:

Approach 1: Simulating the Zigzag With an Array of Rows

The most intuitive approach is to simulate the writing process. You maintain an array of strings, one per row, and a pointer that moves down and up while appending characters. This mirrors exactly how a human would solve the problem on paper.

function convert(s, numRows) {
  if (numRows === 1 || s.length <= numRows) {
    return s;
  }

  const rows = new Array(numRows).fill("");
  let currentRow = 0;
  let goingDown = false;

  for (const char of s) {
    rows[currentRow] += char;

    // Reverse direction at the top or bottom row
    if (currentRow === 0 || currentRow === numRows - 1) {
      goingDown = !goingDown;
    }

    currentRow += goingDown ? 1 : -1;
  }

  return rows.join("");
}

console.log(convert("PAYPALISHIRING", 3)); // "PAHNAPLSIIGYIR"
console.log(convert("PAYPALISHIRING", 4)); // "PINALSIGYAHRPI"
console.log(convert("A", 1));              // "A"

Here, goingDown flips whenever we reach either boundary. The time complexity is O(n) where n is the length of the string, and space complexity is O(n) for storing the rows. This solution is easy to explain in an interview and hard to get wrong.

Approach 2: Cycle-Based Index Calculation

If you want to avoid simulation entirely, you can compute which characters belong to each row using the cycle length. For numRows rows, a full zigzag cycle contains cycleLen = 2 * numRows - 2 characters. For each row, you jump through the string by cycleLen and, for middle rows, also grab the diagonal character between cycles.

function convertByCycle(s, numRows) {
  if (numRows === 1 || s.length <= numRows) {
    return s;
  }

  const n = s.length;
  const cycleLen = 2 * numRows - 2;
  let result = "";

  for (let row = 0; row < numRows; row++) {
    for (let i = 0; i + row < n; i += cycleLen) {
      // Vertical character in this cycle
      result += s[i + row];

      // Diagonal character (only for middle rows)
      const diagonal = i + cycleLen - row;
      if (row !== 0 && row !== numRows - 1 && diagonal < n) {
        result += s[diagonal];
      }
    }
  }

  return result;
}

console.log(convertByCycle("PAYPALISHIRING", 3)); // "PAHNAPLSIIGYIR"

This approach also runs in O(n) time but uses O(1) extra space (excluding the output string). It is more elegant for environments where memory is constrained, though the index math requires careful reasoning.

How to Use It in Practice

While you rarely need to zigzag-encode strings in production, the pattern appears in scenarios like visual text formatting, simple obfuscation schemes, and educational tooling. Here is a small utility module that exposes both strategies and lets callers pick one:

// zigzag.js
export function convert(s, numRows, strategy = "simulate") {
  if (numRows <= 1 || s.length <= numRows) return s;

  if (strategy === "cycle") {
    return convertByCycle(s, numRows);
  }
  return convertBySimulation(s, numRows);
}

function convertBySimulation(s, numRows) {
  const rows = new Array(numRows).fill("");
  let row = 0, down = false;
  for (const ch of s) {
    rows[row] += ch;
    if (row === 0 || row === numRows - 1) down = !down;
    row += down ? 1 : -1;
  }
  return rows.join("");
}

function convertByCycle(s, numRows) {
  const cycleLen = 2 * numRows - 2;
  let out = "";
  for (let r = 0; r < numRows; r++) {
    for (let i = 0; i + r < s.length; i += cycleLen) {
      out += s[i + r];
      const d = i + cycleLen - r;
      if (r !== 0 && r !== numRows - 1 && d < s.length) out += s[d];
    }
  }
  return out;
}

You can then import and use it in any JavaScript project, choosing the cycle strategy when memory matters most and the simulation strategy when readability is the priority.

Best Practices

Common Pitfalls

One frequent mistake is forgetting that the diagonal characters only exist for middle rows. In the cycle approach, adding a diagonal character for the first or last row duplicates the vertical character and corrupts the output. Another common bug is allowing currentRow to go out of bounds by flipping direction at the wrong time โ€” always flip after checking the boundary, or check the boundary before moving.

Developers also sometimes assume numRows is always greater than 1. When numRows = 1, the cycle length formula 2 * numRows - 2 evaluates to 0, causing an infinite loop in the cycle approach. Guarding against this case up front prevents the bug entirely.

Conclusion

The ZigZag Conversion problem is a compact exercise that rewards clear thinking about direction, cycles, and index arithmetic. The simulation approach gives you a readable, interview-friendly solution that mirrors the problem description, while the cycle-based approach offers a memory-efficient alternative rooted in mathematical pattern recognition. By understanding both, practicing the edge cases, and following the best practices above, you will be well equipped to solve this problem confidently in any JavaScript codebase or technical interview.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles