Since some of the provided solutions seem outdated or conflict with certain combinations of autoplay or loop-settings, this one here takes care of that, keeping everything inside the initialization call:
const heroSlider = new Swiper('#hero-slider .swiper', {
  loop: true,
  watchOverflow: true,
  autoplay: {
    delay: 4000,
  },
  speed: 500,
  navigation: {
    nextEl: '#hero-slider .swiper-button-next',
    prevEl: '#hero-slider .swiper-button-prev',
  },
  on: {
    beforeInit() {
      const slides = this.el.querySelectorAll('.swiper-slide');
      if (slides) {
        this.params.loop = slides.length > 1;
        this.params.autoplay.enabled = slides.length > 1;
      }
    },
  },
});
The above shows no navigation-controls and disables autoloop if we got just 1 slide, effectively disabling the swiper - otherwise controls are shown and autoloop starts as usual. We count the slides here using a bypass over the DOM instead via the Swiper-API, since the latter isn't reliable at this point - nor is it on init (with a single slide f.i., this.slides.length delivers 0 on beforeInit (no surprise) and 3 onInit, since the initial default setting of loop: true seems to produce slide-duplicates when we got just 1 slide etc.).
Long story short: Works for me!