A language is a language, it is neither interpreted nor compiled, until it is. Meaning, the same language can be interpreted on the fly or it can be compiled into a binary. In fact, an interpreter does compile the code on the fly into byte code.
The main difference between compiled and interpreted languages is that an interpreted program needs a separate runtime, while a compiled program can typically run by itself or with minimal bootstrap help from the operating system. A compiler will typically also spend more time on the compilation step than an interpreter, where it can do more in-depth error checking and code optimisations. An interpreter doesn't do this only because it is a slow process, not because it's fundamentally impossible.
Type hints/inference is a completely separate problem. Many languages which are primarily compiled use type hints extensively, because the compiler has the time to use that information. Mainly interpreted languages often forgo type hints, because the additional type checking costs time at runtime, and the design goal for the language is a fast development cycle to begin with, which includes less typing. That doesn't mean those languages are "type free" by any means, every single value still has a type, and that type is known. In fact, even many compiled languages these days forgo explicit type annotation when it is unnecessary for brevity. For example:
Foo foo = new Foo();
let foo = new Foo();
The Foo
type hint here is rather superfluous; of course the compiler can infer that foo
is of type Foo
here, just looking at the right hand side of the assignment. The same goes for any other type, including numbers, strings and so on. A compiler or static type analyser can trace what values are being assigned to variables or returned from functions and infer a lot about types without a single explicit annotation.
Having said this, in unannotated languages which heavily depend on runtime information, in some situations it's not possible to know what type a variable will be until runtime. In that case a static type analyser or compiler cannot help in advance and cannot catch errors resulting from incompatible types at compile time. For example, TypeScript is a bolt-on solution for static typing for Javascript, and it walks that line. Everything you do annotate in TypeScript is rigorously type checked; anything you do not annotate or leave as any
cannot be type checked and may blow up in your face at runtime.
Type systems in statically typed languages and dynamically typed languages serve different purposes (note statically vs. dynamically typed, not "compiled" vs. "interpreted"). Static typing serves mainly to catch errors at compile time and give a compiler more information to produce better code; dynamic typing serves to define program behaviour, i.e. how values of two types should behave when operated upon, which includes throwing an error on incompatible types. Such incompatible type errors can still be predicted by a static analyser, even if not in all situations.