Using the Unit of Work pattern backed up by the fflib-apex-common GITHUB library, you can delegate most of this work and make your coding somewhat simpler
UnitOfWork will insert objects in a user-defined order so C2 will always be inserted before C3 (junction)
Code would look something like this:
... build map of C1 suitable for location when building C3
fflib_ISobjectUnitOfWork uow = Application.UnitOfWork.newInstance();
for (C2__c c2: c2s) { // assuming list of C2__c has already been constructed in memory
uow.registerNew(c2); // tell UoW there's a new C2
C3__c c3 = new C3(...); // build C3__c fields except lookups
uow.registerNew(c3,C3__c.C2__c,c2); // tell Uow that this c3 has c2 as parent
uow.registerRelationship(c3,C3__c.C1__c,someMapLookupofTheC1YouNeed); // tell Uow this C3 has C1 as parent
}
uow.commitWork(); // all C2s and C3s get inserted in proper order
This pattern is extremely flexible and extensible, allowing one to construct elaborate chains of sobjecttypes without having to manage too many intermediate maps yourself. I have used it, for example, to construct Account-Contact-Order-OrderItem-PricebookEntry-Product2 where Order has lookups to Pricebook2, Account and Contact, OrderItem is child of Order, and OrderItem has lookup to PricebookEntry.
Even better, because you are using the UnitOfWork layer, you can use ApexMocks to do unit testing, verifying that you are constructing the correct objects without having to do actual DML - thus making your testmethods easier to write as well as run faster. ApexMocks is not a substitute for integration testing but a tool for better unit tests.
An in-depth discussion of all of this can be found (in addition to trailhead) in an excellent book by Andy Fawcett.