I want these cubes to move like waves

Each cube can move up and down automatically. But, I want to start each cube’s movement slightly differently and will make them look like wave. How do I do that?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveObject : MonoBehaviour
{
    public float amp;
    public float freq;
    Vector3 initPos;

    public void Start()
    {
        initPos = transform.position;
    }

    public void Update()
    {
        transform.position = new Vector3(initPos.x, Mathf.Sin(Time.time * freq) * amp + initPos.y, 0);
    }

}

What you want is to have your cubes organized in a grid , then you can offset each row by a little bit to show the wave “progress” from one row to the next.
A good start would be something like this , this could be optimized/polished to be better but i opted for readability to get the point across.

Edit : code mistake

public class WaveExample : MonoBehaviour
{
    [SerializeField]
    private GameObject cubePrefab;
    
    [SerializeField]
    private Vector2Int gridSize;
    
    [SerializeField]
    private Vector2 cellSize;
    
    [SerializeField]
    private float freq;
    
    [SerializeField]
    private float amp;
       
    private GameObject[][] grid;
    
    private void Awake()
    {
         // spawn all the cubes in a grid
         grid = new GameObject[gridSize.x];
         
         for(int x = 0 ; x < gridSize.x ; ++x)
         {
             GameObject[] row = new GameObject[gridSize.y];
             grid[x] = row;
             
             for(int y = 0; y < gridSize.y ;  ++y)
             {
                 row[y] = Instantiate(cubePrefab , transform , GetFlatPosition(x , y) , Quaternion.identity);
             }
         }
    }
    
    private void Update()
    {
        for(int x =  0 ; x < gridSize.x ; ++x)
        {
              // this offset will vary the height between each row
              // it starts at 0 with the first row , 0.5f at the center row , and 1 at the last row
              // think of it as a percentage of where we are currently in the grid as we traverse it
              float offset = x / (float) (gridSize.x - 1);
            
              for(int y = 0 ; y < gridSize.y ; ++y)
              {
                GameObject currentCube = grid[x][y];
                
                Vector3 additionalHeight = Vector3.up * (Mathf.Sin(offset + (Time.time * freq)) * amp);
                Vector3 pos = GetFlatPosition(x , y) +  additionalHeight;  
            }
        }
    }
    
    // given (x , y) coords in the grid , returns the neutral position of the cube in the grid
    private Vector3 GetFlatPosition(int x , int y)
    {
        return new Vector3(x * cellSize.x , y * cellSize.y , 0);
    }
    
    
}