With instancing, you can draw multiple objects with a single draw call. The optimal scenario is of course that you have one draw call per object type, so you'll minimize everything from draw calls to material, buffer switches.
There are multiple ways of doing it of course, but you can start simple: what you'll need is that each mesh (which knows about buffers, shaders etc) keeps a list of transforms for different instances. Instead of drawing a mesh directly, you'll just add a transform matrix to the buffer. I'm using currently std::vector for dynamic storage and I haven't had any performance issues. After you have submitted all the instances to each mesh, you can transfer the transform data to a cbuffer, buffer or vertex buffer and then draw multiple instances. I prefer a generic buffer<float4> object since it's size limitation is at 128 megabytes and it can be used for skinned objects too.
So, this way you can minimize all draw calls, shader program bindings, material changes etc.
Of course, that'll eat lots of CPU power to perform culling per object and adding objects individually to the list. So next step would be logically to divide your world with quad / octree and have each node to keep lists of list of meshes (ie. a list for each different kind of mesh inside the node). When going through your spatial tree, instead of checking and adding individual objects, you can add full lists of objects to the drawing queue.
Of course, this will result out-of-frustum objects to be drawn, but it is of course a trade off , lots of CPU saved, a bit more GPU used.
The advantage of the instancing system is that it is a bit more scalable than transforming each mesh manually. Also, I like the instancing way since you don't need to touch the vertices or indices. Probably for very few meshes the performance would be better if they were transformed manually.