As someone mentioned, in theory there is no much you can do with dynamic typing that you could not do with static typing if you would implement certain mechanisms on your own. Most of languages provide the type relaxation mechanisms to support type flexibility like void pointers, and root Object type or empty interface.

Better question is why is dynamic typing more suitable and more appropriate in certain situations and problems.

First, lets define

Entity - I would need a general notion of some entity in the code. It can be anything from primitive number to complex data.

Behavior - lets say our entity has some state and a set of methods that allow outside world to instruct the entity to certain reactions. Lets call the state + interface of this entity its behavior. One entity can have more than one behavior combined in a certain way by the tools language provides.

Definitions of entities and their behaviors - every language provides some means of abstractions which help you to define behaviors (set of methods + internal state) of certain entities in the program. You can assign a name to these behaviors and say that all instances that have this behavior are of certain type.

This is probably something that is not that unfamiliar. And as you said you understood the difference, but still. Probably not complete and most accurate explanation but I hope fun enough to bring some value :)

Static typing - behavior of all entities in your program are examined in compile time, before code is started to run. This means that if you want for example your entity of type Person to have behavior (to behave like) Magician then you would have to define entity MagicianPerson and give it behaviors of a magician like throwMagic(). If you in your code, mistakenly tell to ordinary Person.throwMagic() compiler will tell you "Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".

Dynamic typing - in dynamic typing environments available behaviors of entities are not checked until you really try to do something with certain entity. Running Ruby code that asks a Person.throwMagic() will not be caught until your code really comes there. This sounds frustrating, isn't it. But it sounds revelational as well. Based on this property you can do interesting things. Like, lets say you design a game where anything can turn to Magician and you don't really know who will that be, until you come to the certain point in code. And then Frog comes and you say HeyYouConcreteInstanceOfFrog.include Magic and from then on this Frog becomes one particular Frog that has Magic powers. Other Frogs, still not. You see, in static typing languages, you would have to define this relation by some standard mean of combination of behaviors (like interface implementation). In dynamic typing language, you can do that in runtime and nobody will care.