array manipulation
algorithm design
programming
data structures
code optimization

Tetris-ing an array

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

“Tetris-ing an array” usually refers to compacting, packing, or rearranging elements so gaps are removed and values are moved according to constraints, similar to pieces dropping in Tetris. This pattern appears in memory compaction, inventory slot systems, game grids, and data cleanup tasks. The first step is to define exact movement rules: stable compaction (preserve order), gravity by column, or best-fit packing. Without a clear rule set, implementations become brittle and impossible to test. This article covers practical array “tetris” strategies and their trade-offs.

Core Sections

1. Stable one-dimensional compaction

If the goal is to move non-empty values left and keep relative order, use two-pointer compaction.

python
1def compact_stable(arr, empty=0):
2    write = 0
3    for read in range(len(arr)):
4        if arr[read] != empty:
5            arr[write] = arr[read]
6            write += 1
7    while write < len(arr):
8        arr[write] = empty
9        write += 1
10    return arr
11
12print(compact_stable([0, 4, 0, 2, 7, 0, 3]))
13# [4, 2, 7, 3, 0, 0, 0]

This is O(n) and in-place.

2. Column gravity in 2D grids

For board-style behavior, compact each column downward.

python
1def apply_gravity(grid, empty=0):
2    rows, cols = len(grid), len(grid[0])
3    for c in range(cols):
4        write = rows - 1
5        for r in range(rows - 1, -1, -1):
6            if grid[r][c] != empty:
7                grid[write][c] = grid[r][c]
8                write -= 1
9        for r in range(write, -1, -1):
10            grid[r][c] = empty
11    return grid

This mimics pieces falling straight down while preserving vertical order within a column.

3. Packing with constraints

Some problems require fitting variable-width items into bins/rows. In that case, use greedy packing (first-fit, best-fit) and accept that globally optimal packing may be NP-hard.

python
1def first_fit(widths, capacity):
2    bins = []
3    for w in widths:
4        placed = False
5        for b in bins:
6            if sum(b) + w <= capacity:
7                b.append(w)
8                placed = True
9                break
10        if not placed:
11            bins.append([w])
12    return bins

This is often sufficient for UI layout or basic scheduling.

4. Complexity and correctness concerns

Compaction and gravity are usually O(n) or O(rows*cols). Bugs often come from overwriting unread values, forgetting to clear residual slots, or violating stability assumptions.

Use property-based tests where possible:

  • number of non-empty items unchanged
  • no non-empty appears after an empty in compacted 1D array
  • column multiset preserved after gravity

5. Visualization for debugging

For grid operations, print intermediate states step-by-step. Visual diffs catch index mistakes quickly, especially off-by-one errors near boundaries.

python
for row in grid:
    print(row)
print("---")

6. Production considerations

If “tetris” logic runs at high frequency (games, real-time dashboards), avoid repeated allocations and branch-heavy code in hot loops. Pre-allocate buffers, prefer in-place transforms, and benchmark with realistic board sizes.

Validation and production readiness

A reliable solution should include explicit validation and observability, not just a working snippet. Add representative test inputs for normal flow, malformed input, and boundary values so behavior is stable under change. Where timing or throughput matters, keep a small benchmark scenario and run it after refactors to catch accidental slowdowns early. If external systems are involved, include retry, timeout, and failure-path tests to verify the system degrades gracefully rather than hanging or failing silently.

Operationally, document assumptions close to the implementation: dependency versions, environment requirements, timezone or locale expectations, and any platform-specific behavior. Add structured logs for key decision points and failures so production incidents are diagnosable without reproducing every condition locally. For teams, define a minimal rollout checklist that covers backward compatibility, monitoring alerts, and rollback steps. These checks reduce incidents caused by integration gaps, which are more common than syntax errors in real deployments.

Common Pitfalls

  • Implementing movement rules without defining stability and direction explicitly.
  • Overwriting values during in-place moves due to incorrect read/write ordering.
  • Forgetting to reset vacated positions after compaction.
  • Assuming greedy packing always gives optimal arrangement.
  • Testing only one board shape and missing edge conditions.

Summary

Array “tetris” tasks become manageable when you define the movement contract first: stable compaction, column gravity, or constrained packing. Two-pointer and column-wise strategies are efficient and reliable for most use cases. Add invariant-based tests and simple visualization to catch indexing bugs early. With clear rules and performance-aware implementation, these transformations stay correct and scalable.


Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.