Is the active object and zeroth selected object always the same?
Not always

After calling an add primitive operator it is almost always the case that the new object is both active and selected. In which case it rings true.

&gt;&gt;&gt; bpy.ops.mesh.primitive_cube_add()
{'FINISHED'}

&gt;&gt;&gt; C.object is C.selected_objects[0]
True


but the selection can be manipulated such that the active object is None, and there are still selected objects. 

&gt;&gt;&gt; for o in C.scene.objects:
...     o.select_set(True)
...     
&gt;&gt;&gt; C.view_layer.objects.active = None
&gt;&gt;&gt; C.object is C.selected_objects[0]
False


Then not true. 

Selecting then setting active object

&gt;&gt;&gt; for o in C.scene.objects:
...     o.select_set(True)
...     
&gt;&gt;&gt; C.view_layer.objects.active = None
&gt;&gt;&gt; C.selected_objects[0]
bpy.data.objects['Cube']

&gt;&gt;&gt; C.view_layer.objects.active = C.scene.objects['Cube.001']
&gt;&gt;&gt; C.selected_objects[0]
bpy.data.objects['Cube']


Again not true.

Or no selected objects, with or without an active object

&gt;&gt;&gt; C.view_layer.objects.active = C.scene.objects['Cube'] # or None
&gt;&gt;&gt; for o in C.scene.objects:
...     o.select_set(False)
...     

&gt;&gt;&gt; C.object is C.selected_objects[0]
Traceback (most recent call last):
  File "&lt;blender_console&gt;", line 1, in &lt;module&gt;
IndexError: list index out of range


In conclusion, suggest if you want to be sure it's the active object use context.active_object or context.object.  Check if not selected and set selection if need be  Don't rely on using context.selected_objects[0]