In Zig's case, it's compile time. An expression like `foo.bar(1, 2)` can only compile if `foo` has a method named `bar` with the right number of arguments. In a duck-typed system, `foo` can be any type of value you like, as long as it "fits" the shape.
In contrast, many languages require you to describe the shape up-front, and then constrain `foo` to implement that shape. For instance, in Rust we would have to write something like `trait Bar { fn bar(a: u32, b: 32); }`, and then say something like `foo: T where T: Bar`, before we're allowed to call the `bar` method.
Both are compile-time safe, but one requires more ceremony (but is more explicit), while the other is simpler (but you might have to guess the requirements).