There is no 'proper' way, just different approaches that vary from simple-but-slow to complex-but-fast.
You can stick with that method, if your car list is small enough. However, there is a bug in your basic algorithm, as it will compare each pair of cars twice. To fix that, your second loop should start the value of
car2 from the position in the list after
Most faster methods of detecting collisions like this use spatial properties of the objects so that the objects can be ordered in such a way that 1 comparison might reject multiple pairings.
Probably the simplest approach using this is to sort the objects along one axis (x or y) and use that knowledge to exit the loop early. eg. Sort cars by x ascending, and when you find a
car2 with an x value greater than the right-hand side of the current
car1, you know you no longer need to compare it against any more possible
car2 objects as they must be even further away.
A more complex approach is to divide the world in 2 dimensions using a grid of squares that are bigger than the objects inside them. You track which grid square each object is in, and conversely which objects are in that grid square. So each object only needs to check for collision against other objects in that square (and maybe the 8 adjacent squares). Technically it's a 'bin' data structure.
Beyond that, there are other ways to partition the world that change as the objects move. A common example is a quadtree which subdivides the space to keep a similar number of objects in each square. Others can be found under the general term of spatial partitioning but as with any data structure, some are optimised for quick lookup while being expensive to add or change data within them.
In your particular case... I would probably just stick with the 2 loops, and upgrade to the coordinate-sorted approach later if necessary.