The container resolves concrete classes automatically, which is convenient enough that most code type-hints the concrete class and the abstraction never happens.
// AppServiceProvider::register()
$this->app->bind(Catalogue::class, DatabaseCatalogue::class);
$this->app->singleton(Clock::class, function () { return new SystemClock(); });
// swap for one context only
$this->app->when(ReportBuilder::class)
->needs(Catalogue::class)
->give(SearchCatalogue::class);
Contextual binding is the part worth knowing about: it lets one consumer get a different implementation without a factory or a flag. Bindings belong in register(); resolving anything there instead of in boot() works until a provider that has not registered yet is needed, and then fails in an order-dependent way.