Flatpak and Snap have some different approaches, both come with pros and cons.
The first difference is centralization vs. decentralization. Snap is centralized, there's no way to make your private repos fully under your control. If you want private snap repo, you must talk to Canonical. From the user perspective, there's are no competing snap repos, only the Canonical one. They also support update pushing, so users will get updates whether they want them or not. This is a huge departure from the way apt/yum repos were managed.
This mirrors in the runtime support: snaps have centralized core16 / core18 runtimes or whatever Canonical comes with. Flatpak runtimes are decentralized and everyone can make one. You can use org.freedesktop.Platform, org.gnome.Platform, org.kde.Platform, maybe in future there will be org.centos.Platform or even something entirely different.
Flatpak focuses on desktop - their main method of app integration are .desktop files and dbus names. Snap allows arbitrary applications, including services or cli apps. This impacts the visibility from outside the sandbox: flatpak uses namespaces, so outside you won't get to see the private bind mounts that the containers use. With snap, your mount namespace is polluted with the squashfs mount points.
For sandboxing, Flatpak uses namespaces and secomp, with optional SELinux. Snap requires AppArmor, but packages with confinement: classic are not sandboxed at all.
Flatpak uses OSTree to store the app, framework or addon files. OSTree is often called git for binaries, it is content-addressable repository with checkouts and pulls. Snaps are squasfs images.
Things they have in common: both are sandboxed (except classic snaps), that's why the first launch is slower than unsandboxed applications. There are some things, that are being done, for example font caches are being created, that the unsandboxed apps do not have to do, because the host environment already has them. Both allows you to target a specific runtime/SDK, so there's no surprise for the app at install or runtime. Both have a permission system, that can control, how the app can interact with other apps or host, though the permissions currently defined may differ.