Skip to content
Skilled.dev

Free lesson walkthrough · Swift

Spiral matrix traversal in Swift: Walk a Matrix

Traverse a rectangle in clockwise spiral order using shrinking boundaries. Follow the Swift implementation, its correctness argument, worked example, complexity, and boundary tests.

4 min read · Worked code, edge cases, and complexity

Return every value of a rectangular matrix in clockwise spiral order, starting at its top-left corner. For [[1, 2, 3], [4, 5, 6], [7, 8, 9]], the order is [1, 2, 3, 6, 9, 8, 7, 4, 5]. Reading values in this order does not require rotating the matrix or marking its cells.

Maintain four unvisited boundaries

Keep the first and last unvisited row and the first and last unvisited column. Initially those bounds describe the full matrix. Each pass walks the top edge left to right, the right edge top to bottom, the bottom edge right to left, and the left edge bottom to top. Move the corresponding boundary inward after walking an edge.

Moving the top boundary before walking the right edge prevents the top-right corner from being visited twice. Moving the right boundary before the bottom edge does the same for the bottom-right corner. The sequence of updates is part of the algorithm; treating all four edges as independent full-length walks repeats corners.

Trace the shrinking rectangle

In the three-by-three example, the top walk emits 1, 2, 3 and moves the top boundary to row 1. The right walk emits 6, 9 and moves the right boundary to column 1. The bottom walk emits 8, 7 and moves the bottom boundary to row 1. The left walk emits 4 and moves the left boundary to column 1.

The remaining rectangle contains only 5. The next top walk emits it, and the updated bounds cross. The outer loop then stops. The output length is nine, matching the cell count. The same boundary representation also handles a non-square rectangle without changing direction logic.

Guard the last row and column

After the top and right walks, there may be no bottom row left to visit. Check the row bounds before walking backward along it. Likewise, check the column bounds before walking upward along the left edge. A one-row input otherwise gets emitted forward and backward, and a one-column input can repeat its interior cells.

For [[1, 2, 3, 4]], the top walk finishes all useful work. For [[1], [2], [3]], the top walk emits 1 and the right walk emits 2 and 3. The remaining guards prevent a duplicate return trip. These narrow shapes expose mistakes that a square example can hide.

Prove complete, unique visitation

Before a pass, the bounds contain exactly the cells not yet emitted. Each edge walk consumes an exposed edge of that rectangle, and its boundary update removes precisely those cells. Corner handling and the final-edge guards ensure those consumed regions do not overlap. Any remaining interior cells form the next smaller rectangle.

The process ends when no rows or columns remain. Every original cell has then been consumed exactly once. Total time is O(r × c), and boundary tracking uses O(1) auxiliary space. The returned sequence needs O(r × c) storage. There is no separate visited matrix, and the input cells are not changed.

Check dimensions and adapt deliberately

The lesson assumes a rectangular matrix. Empty rows yield no values; an empty outer array must be checked before accessing its first row in languages that reject that access. Test both forms explicitly when supporting them. Ragged rows need a different contract because four rectangular bounds cannot describe their shape.

Test one cell, one row, one column, two-by-three, three-by-two, and an odd-sized square with a central cell. Use distinct cell values so repeated visits are visible. Compare output length with the cell count and use a direction-and-visited-set simulator as an independent oracle. A counterclockwise variant changes the edge order and start directions; it should preserve the same unvisited-region invariant.

Swift implementation

The array appends cells while four signed boundaries shrink. Guard dimensions before constructing ranges for empty or exhausted edges.

The course uses the class shown here and supplies any declared node classes. Swift arrays have value semantics with copy-on-write storage, while class instances such as linked nodes retain reference identity. Optional values require an explicit absence case. Keep the starter's argument labels when calling the method. Swift integer overflow traps, so expanded constraints require a checked arithmetic policy.

class Solution {
    func walkMatrix(_ matrix: [[Int]]) -> [Int] {
        if matrix.isEmpty { return [] }
        var result: [Int] = []

        // Initialize the upper and lower bounds indexes
        let rowCount = matrix.count
        let columnCount = matrix[0].count
        var startRow = 0
        var endRow = rowCount - 1
        var startColumn = 0
        var endColumn = columnCount - 1

        // We will keep walking as long as rows or columns remain.
        // That happens as long as the end is greater or equal to the start.
        // (stride(from:through:by:) is empty when there is nothing left to walk)
        while endRow >= startRow && endColumn >= startColumn {
            // Walk across the top starting row for each column from beginning to end
            // This is left-right across the top
            for column in stride(from: startColumn, through: endColumn, by: 1) {
                // Add the item to result in order
                result.append(matrix[startRow][column])
            }

            // Increment our start row since we visited each value
            startRow += 1

            // Walk top-bottom for the end column
            for row in stride(from: startRow, through: endRow, by: 1) {
                result.append(matrix[row][endColumn])
            }
            endColumn -= 1

            // Since we increment startRow, we need to make sure we are still in bounds
            if endRow >= startRow {
                //  Walk right-left on the bottom row
                for column in stride(from: endColumn, through: startColumn, by: -1) {
                    result.append(matrix[endRow][column])
                }
            }
            endRow -= 1

            // Since we decrement endColumn, we need to make sure we are still in bounds
            if endColumn >= startColumn {
                // walk bottom-top for the start column
                for row in stride(from: endRow, through: startRow, by: -1) {
                    result.append(matrix[row][startColumn])
                }
            }
            startColumn += 1
        }

        return result
    }
}

Practice this implementation

Open Walk a Matrix to run the implementation against the course tests, then change one boundary condition and explain the first failing case. The editor supplies the starter signature and any node types or helper APIs used above. Copy the implementation into that environment; the snippet is the solution body, without the input parser or test driver. Your saved progress belongs to the original lesson.

For more Swift conventions and practice problems, use the Swift interview guide.

Try the exercise

Write your own implementation in the course editor. This walkthrough belongs to the same lesson; your course progress stays with that exercise. Exercise access follows the course’s access settings.