Efficient rendering with many light sources To render a scene with a single light source using phong shading, one can calculate the final color of each fragment passed into the fragment shader based on the ambient/diffuse/specular components of both the material and the light source.

This can be easily extended to accommodate for multiple light sources by adding together the results of applying each individual light source onto the fragment as such:

final_color = (0, 0, 0, 1)
for each light:
    final_color += apply_light(material, light)
final_color = clamp(final_color, (0,0,0,1), (1,1,1,1))


However, with a very large number of light sources, this process is quite slow; with N lights, this approach requires calculations for phong shading to be done N times per fragment. 

Is there a better approach to rendering scenes with a very large number of light sources (hundreds, thousands, etc.)?
Yes, but you need a paradigm shift. 

What you are accustomed is called forward rendering. You submit your geometry and then you proceed immediately with the shading pass. In the basic forward rendering you can either loop inside the shader for each light or perform one pass per light and blend the result together (with additive blending). 

But things have evolved quite a lot. Enters: Deferred Rendering

Now that are so many variants that describe them all in details will take way more than acceptable for an answer here. So here I am just going to describe the gist of Deferred shading, there are plenty of other resources that you can easily find using google, hopefully after reading this you'll have the right keywords to find what you need. 

The basic idea is to defer the shading to after down the pipeline. You have two main steps: 


Render your geometry and all the information needed for shading into multiple render targets. This means that typically in a basic implementation you would have a depth buffer, a buffer containing the normals of your geometry and the albedo colour. You'll soon find that you need other information of the materials (e.g. roughness, "metallic" factor etc.). 


This image from wikipedia shows three buffer (color, depth and normals) 



Again, the amount, type and content of the buffers used varies quite a lot among different projects. You will find the set of buffers with the name of GBuffers.


After this is the time of applying the actual lighting. During the lighting pass for each light you want to draw a light volume that depends on the type of light:


For a directional light you render a full screen quad. 
For a point light you render a sphere where the radius is based on the attenuation of your point light. 
For a spot-light you render a cone which dimensions again depend on the characteristics of your light. 



In the pixel shader of this pass you pass your GBuffers and perform your lighting and shading using the information in them. This way you process only the pixels affected by each of the lights having a sensible speed-up if compared to the classical forward rendering. 

It has also various disadvantages, most notably the handling of transparent  objects and higher consumption of bandwidth and video memory. But also it is trickier to handle various models for materials. 

You have other side-advantages (as having lots of info ready for post-processing) and is also pretty easy to implement. But this is not the coolest thing around for lots of lights anymore. 

Newer techniques are for example Tiled rendering ones. The main idea of those is to subdivide the scene in screen space "tiles" and assign to each tile the lights affecting it. This exists both in a deferred and forward fashion.  These techniques lead to some problems when you have various depth discontinuities in a tile, but is generally faster than the classical deferred and it solves various problems of it. For example, among the advantages, with tiled deferred you read the GBuffers once per lit fragment and pixels in the same tile coherently process the same lights. 

Further evolution on this side is the Clustered shading which is conceptually similar to the tiled based approaches, having instead of screen space tiles, clusters with a 3D extent. This method handles better the depth discontinuities problem and generally performs better than the tiled methods. 

IMPORTANT NOTE: I have described the basics of the deferred shading. There are multiple variations, optimizations and improvements around so I urge you to experiment with a simple version and then do some researches on other techniques such the one I mentioned above.