There are a few ways to handle this, but it's an interesting problem to have.
With this line of code:
var position = new Vector3(Random.Range(-20.0f, 20.0f), Random.Range(-10.0f, 10.0f));
You are randomly distributing objects throughout the room, but it is possible that you run out of viable space in the room with a higher number of objects spawned.
There are a few ways games solve the problem of "overlapping objects", but I prefer to use the grid system in cases like this rather than trying to involve physics in level generation.
Grid system
You should identify your largest prefab object that you want to spawn, and then break up your map into a grid with cells of that size or larger. Then you can create a List of Tuples that each represent one cell on that grid. Randomly select 1 tuple from the list and remove it when spawning an object to guarantee that no other objects will appear in this area.
[SerializeField]
List<GameObject> prefabList = new List<GameObject>();
[SerializeField]
int numberToSpawn = 5;
[SerializeField]
Vector3 mapAnchor = Vector3.zero;
[SerializeField]
float mapWidth = 40f;
[SerializeField]
float mapHeight = 20f;
[SerializeField]
float cellSize = 1f;
// Start is called before the first frame update
void Start()
{
//calculate the number of cells along the width and height of the map
int cellWidth = Mathf.RoundToInt(mapWidth / cellSize);
int cellHeight = Mathf.RoundToInt(mapHeight / cellSize);
//Generate a list of tuples that represent each individual cell on the map
List<(int,int)> cells = gridInstantiate(cellWidth, cellHeight);
for (int i = 0; i<5; i++)
{
//Randomly select the cell on the grid
int randCellIndex = Random.Range(0, cells.Count);
(int x, int y) = cells[randCellIndex];
cells.RemoveAt(randCellIndex);
//Instantiate the object at x and y on the grid, converting it back into world space
Vector3 position = mapAnchor;
position.x = position.x + x * cellSize;
position.y = position.y + y * cellSize;
Instantiate(prefabList[Random.Range(0, prefabList.Count)], position, Quaternion.identity);
}
}
List<(int,int)> gridInstantiate(int cellWidth, int cellHeight)
{
List<(int,int)> cells = new List<(int, int)>();
for (int i = 0; i < mapWidth; i++)
{
for (int j = 0; j < mapHeight; j++)
{
cells.Add((i, j));
}
}
return cells;
}
This is what it looks like generating random circles with the above code:

To make the objects spawn more naturally, you can add some extra code to smooth that out by giving each cell a buffer, and allowing the instantiation to randomize within that buffer as an offset, preventing them from aligning so perfectly:
Add another property:
[SerializeField]
float cellBuffer = 1f;
Modify the cellheight/cellwidth calculation:
int cellWidth = Mathf.RoundToInt(mapWidth / (cellSize + cellBuffer));
int cellHeight = Mathf.RoundToInt(mapHeight / (cellSize + cellBuffer));
Give the position a random offset within the limits of the buffer:
Vector3 offset = new Vector3(Random.Range(-cellBuffer / 2, cellBuffer / 2), Random.Range(-cellBuffer / 2, cellBuffer / 2), 0);
Instantiate(prefabList[Random.Range(0, prefabList.Count)], position + offset, Quaternion.identity);
This is what the above code changes add, with another isometric diamond sprite set up as another potential prefab and the numberToSpawn set to 30:

Why do this, rather than randomly generate and check for overlap?
It's possible to run into a race condition if you randomly generate and check for overlap and there is zero space for a new object. You either don't spawn an object, or keep trying to spawn an object and fail. With this strategy, the list of cells will contain all remaining "open" positions on the map to spawn an object onto, so you don't need to perform any overlap checks. Additionally, when that list is empty you can know that there is no more room to spawn an object.