Display hardware does not change every time a graphics command is used.
The display updates, for our purposes, every 1/60 seconds. This period is a "frame." At the frame boundary, the hardware takes whatever SmileBASIC says the screen should look like and dumps it to the actual screen.
Flicker happens when your program is still drawing to the internal screen buffer when it gets sent to the actual screen, resulting in an incomplete image being shown.
Figure 1: Behavior of WAIT, VSYNC 1, and VSYNC 2 for a loop with highly variable execution time, taking 0.35 frames the 1st iteration and 1.20 frames in the 2nd iteration. (In most cases, the main loop and drawing should consistently take less than 1 frame, and VSYNC  is sufficient)
VSYNC syncs with this screen refresh by idling until the nth next1 frame boundary, so that the drawing time will not interfere with the screen update.
However, if the drawing time takes longer than 1 frame, then flicker will still occur (in the image, the entirety of the loop time is used in drawing) because VSYNC does not delay the screen update. To prevent this, you can use a graphics buffer and swap pages2 so that the last COMPLETE image is always shown. You will also want to sync with a lower frame rate if your loop execution time does not meet the demands of a 60-frame-per-second refresh.
WAIT just idles for n/60 seconds, and should not be used for framerate control. It is worth noting that WAIT will also update the "last VSYNC" timer.
Both WAIT and VSYNC are blocking, i.e. no other code will run until they have finished. This is useful for VSYNC, but makes WAIT generally undesirable. For real-time event timing, it is suggested to implement your own timing system that checks MAINCNT or MILLISEC for a time condition.
1 since the last VSYNC/WAIT finish2 12Me21 (2016). Flicker-less graphics without VSYNC/WAIT. http://smilebasicsource.com/page?pid=270