JavaScript Perfect Square Spiral Pattern

Beginner
5 min read
Updated: Sep 2026
3 programs
Live preview

What Is This Pattern?

A perfect square spiral (spiral matrix) fills an n×n grid with consecutive numbers 1..n² walking clockwise around shrinking border layers.

Remember
Rule: top → right → bottom → left, then shrink boundaries

   1   2   3   4
  12  13  14   5
  11  16  15   6
  10   9   8   7     ← n = 4

Follows the growing reverse pattern in Program 61; next explore JavaScript star patterns.

How to Solve It

Create a 2D array. Maintain top, bottom, left, right. Fill four sides per layer, then move those walls inward until they cross.

MethodIdeaBest for
Four-boundary spiralFill top/right/bottom/left, then shrinkLearning, interviews, exams
Fixed-width printString(v).padStart(4, " ")Aligned columns for multi-digit values

Pseudocode

Pseudocode
top = 0, bottom = n - 1, left = 0, right = n - 1
val = 1
while top <= bottom and left <= right:
    fill top row left→right; top = top + 1
    fill right column top→bottom; right = right - 1
    if top <= bottom:
        fill bottom row right→left; bottom = bottom - 1
    if left <= right:
        fill left column bottom→top; left = left + 1
print the grid

Cheat sheet

GoalPattern
Create gridArray.from({ length: n }, () => Array(n).fill(0))
Boundariestop = 0; bottom = n - 1; left = 0; right = n - 1;
Top sidefor (j = left; j <= right; j++) a[top][j] = val++;
Right sidefor (i = top; i <= bottom; i++) a[i][right] = val++;
Bottom / leftSame idea reversed; guard with if (top <= bottom) / if (left <= right)
Print cellline += String(a[i][j]).padStart(4, " ");

Printing Numbers vs Starting a New Line

APIEffectUse for
line += …padStart(4)Stays on the same rowEach cell in a row
console.log(line)Ends the current rowAfter every row of n cells

Append cells without a newline, then end the row once.

Live Preview

Change the grid size and the clockwise spiral updates instantly — capped at 8 so the matrix stays readable.

Whole numbers from 1 to 8. Tap a chip or type a value — the preview redraws as you go.

Live result n = 4 · 16 cells
   1   2   3   4
  12  13  14   5
  11  16  15   6
  10   9   8   7

Worked Walkthrough

Trace the outer layer for n = 4 so the four-side order and boundary shrink are clear.

SideActionValues filled
Topa[0][0..3], then top++1 2 3 4
Righta[1..3][3], then right--5 6 7
Bottoma[3][2..0], then bottom--8 9 10
Lefta[2..1][0], then left++11 12

Inner 2×2 then fills 13 14 / 16 15. Total cells = n² → O(n²).

JavaScript Programs

Three complete programs: fixed 10×10, prompt size, and a compact 4×4 demo. Use View Output for samples, or Try It Yourself to edit and run live.

Example 1 — Fixed 10×10 Spiral

Hard-coded size — fill with four boundaries, print with width 4 (numbers 1–100).

JavaScript
const n = 10;
const a = Array.from({ length: n }, () => Array(n).fill(0));
let top = 0, bottom = n - 1, left = 0, right = n - 1;
let val = 1;

while (top <= bottom && left <= right) {
  for (let j = left; j <= right; j++) a[top][j] = val++;
  top++;

  for (let i = top; i <= bottom; i++) a[i][right] = val++;
  right--;

  if (top <= bottom) {
    for (let j = right; j >= left; j--) a[bottom][j] = val++;
    bottom--;
  }

  if (left <= right) {
    for (let i = bottom; i >= top; i--) a[i][left] = val++;
    left++;
  }
}

for (let i = 0; i < n; i++) {
  let line = "";
  for (let j = 0; j < n; j++) {
    line += String(a[i][j]).padStart(4, " ");
  }
  console.log(line);
}
Try It Yourself

How It Works

1. Outer layer first. Top fills 1..10, right continues downward, then bottom and left close the ring.

2. Shrink inward. After each side, move that boundary so the next layer starts one cell inside.

3. Align. padStart(4, " ") keeps 1–100 lined up in neat columns.

Example 2 — User Input Size

Read n with prompt, validate, then run the same spiral fill.

JavaScript
const n = parseInt(prompt("Enter size n:"), 10);

if (!Number.isFinite(n) || n <= 0) {
  console.log("Please enter a positive integer.");
} else {
  const a = Array.from({ length: n }, () => Array(n).fill(0));
  let top = 0, bottom = n - 1, left = 0, right = n - 1;
  let val = 1;

  while (top <= bottom && left <= right) {
    for (let j = left; j <= right; j++) a[top][j] = val++;
    top++;

    for (let i = top; i <= bottom; i++) a[i][right] = val++;
    right--;

    if (top <= bottom) {
      for (let j = right; j >= left; j--) a[bottom][j] = val++;
      bottom--;
    }

    if (left <= right) {
      for (let i = bottom; i >= top; i--) a[i][left] = val++;
      left++;
    }
  }

  for (let i = 0; i < n; i++) {
    let line = "";
    for (let j = 0; j < n; j++) {
      line += String(a[i][j]).padStart(4, " ");
    }
    console.log(line);
  }
}
Try It Yourself

How It Works

1. Prompt and validate. Require a finite n > 0 before creating the grid.

2. Same core. Boundary fill matches Example 1 — only n comes from the user.

3. Safer input tip. Cap demos for readable console output:

Safer input
if (!Number.isFinite(n) || n < 1 || n > 20) {
  console.log("Enter a whole number from 1 to 20.");
}

Example 3 — Compact 4×4 Trace

Sixteen cells — easy to dry-run every side of the outer and inner layers on paper.

JavaScript
const n = 4;
const a = Array.from({ length: n }, () => Array(n).fill(0));
let top = 0, bottom = n - 1, left = 0, right = n - 1;
let val = 1;

while (top <= bottom && left <= right) {
  for (let j = left; j <= right; j++) a[top][j] = val++;
  top++;

  for (let i = top; i <= bottom; i++) a[i][right] = val++;
  right--;

  if (top <= bottom) {
    for (let j = right; j >= left; j--) a[bottom][j] = val++;
    bottom--;
  }

  if (left <= right) {
    for (let i = bottom; i >= top; i--) a[i][left] = val++;
    left++;
  }
}

for (let i = 0; i < n; i++) {
  let line = "";
  for (let j = 0; j < n; j++) {
    line += String(a[i][j]).padStart(4, " ");
  }
  console.log(line);
}
Try It Yourself

How It Works

1. Two layers. Outer ring uses 1–12; inner 2×2 uses 13–16.

2. Guards matter. The if (top <= bottom) / if (left <= right) checks prevent double-filling when only one row or column remains.

3. Scale up next. Once the small demo is clear, use Examples 1–2 for 10×10 or user input.

Edge Cases & Pitfalls

Check these before calling the solution done.

no guards

Double-filled middle

On odd n, skipping the if (top <= bottom) / if (left <= right) checks can overwrite the center cell.

no padStart

Crooked columns

Without padStart(4, " "), single-digit and three-digit numbers misalign. Keep a fixed field width.

log inside

Broken rows

If console.log sits inside the column loop, each cell lands on its own line. Call it only after the row finishes.

n = 1

Single cell

Output is just 1 — a good sanity check for the while condition.

wrong side order

Not a spiral

Keep the clockwise order top → right → bottom → left. Swapping sides breaks the ring.

Bad input

Validate parseInt

Check Number.isFinite(n) && n > 0 before creating the 2D array.

Time and Space Complexity

ProgramTimeExtra space
Fixed / compact (Examples 1, 3)O(n²)O(n²) array
User input (Example 2)O(n²)O(n²) array

Every cell is written once and printed once → O(n²) time. The 2D array uses O(n²) space.

Key Takeaways

  • Four sides per layer: top → right → bottom → left, then shrink.
  • Guard odd centers: check top <= bottom and left <= right before the reverse sides.
  • Align with width 4: padStart(4, " ") keeps multi-digit columns neat.
  • Complexity: O(n²) time and O(n²) array space for an n×n fill.

One line: walk each ring clockwise with shrinking top/bottom/left/right until 1..n² is filled.

Frequently Asked Questions

It prints a perfect square spiral (spiral matrix) filled with numbers 1..n² in a clockwise spiral — e.g. 10×10 holds 1–100.
Those four variables mark the current layer. After filling a side, you move that boundary inward and continue.
Using String(value).padStart(4, ' ') prints each number in a fixed width of 4 characters, keeping columns aligned.
Program 61 uses a while loop on one number. Program 62 fills an n×n grid with a 2D array and boundary-based spiral loops.
Yes. This is a classic spiral matrix generation exercise: fill the grid while shrinking boundaries.
Yes. The boundary approach works for any positive n, both odd and even.
A 1×1 grid holds only the value 1 — one cell, one layer.
line += appends a padded cell on the same row. console.log(line) ends the row after n cells.
O(n²) for an n×n grid because each cell is filled and logged once.
Use parseInt with Number.isFinite after prompt() and validate n > 0 — see Example 2.

Did you know?

A perfect square spiral fills an n×n grid with numbers 1..n² by walking each border layer clockwise and tightening boundaries — runtime is O(n²).

Next: JavaScript Star Patterns

Continue with star-pattern tutorials after finishing the number series.

Star patterns →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

12 people found this page helpful