Dart isn't optionally typed any more. It's now a fully statically typed language, that also has a special "dynamic" type. That puts it in the same boat as C# and Scala, among others.
Optional or gradual typing does seem like an obvious brilliant idea from the outside. Start out dynamic when the program is small, layer in types when it grows to the point where you need them. Capture the union of both dynamically typed and statically typed users. Everyone wins!
In practice, we found ourselves in an uncanny valley where we were too typed for the dynamic typing folks, and too unsafe for the static ones. We couldn't deliver the user experience either camp expected. We learned, the hard way, that a statically typed language is not simply a dynamically typed language plus some type annotations. Everything about how you use the language is different.
---
The way you design APIs is different
Python's tuple type has a subscript operator to return an element at the given index. That's a perfectly reasonable, simple, clean API in a dynamically typed language. If you want to have statically typed tuples, that API doesn't even make sense:
t = (1, True, "three")
x = t[datetime.datetime.today().weekday() % 3]
What is the static type of x?
Another example: Python's list type has a sort() method. It takes an optional "key" argument that is a callback that converts each value to a key of some time and then sorts using those projected keys. If you pass a key function, then sort() needs to be a generic function that takes a type parameter for the return type of the key function, like:
sort<R>(key: (T -> R))
But if you don't pass the key function, the R type argument is meaningless. Should it be a generic method or not?
An even gnarlier question is "What kinds of lists can be sorted at all?" The sort() method works by calling "<" on pairs of elements. Not all types support that operation. Of those that do, not all of them accept their own type as the right-hand operand. How do you design the list class and the sort() method such that you ensure you won't get a type error when you call sort()?
To handle this kind of stuff, the "best practices" for your API design effectively become "the way you would design it in a fully statically-typed language". But those restrictions are one of the main reasons people like dynamic languages.
You can mitigate some of this with very sophisticated type system features. Basically design a type system expressive enough to support all of the patterns people like in dynamically typed languages. That's the approach TypeScript takes. But one of the main complaints with static type systems is that they are too complex for humans to understand and too slow to execute.
This makes that even worse. TypeScript's type system is very complex and type-checking performance is a constant challenge. In order to let you write "dynamic style" code, TypeScript effectively makes you pay for a super-static type system.
---
User expectations are bimodal
Once you ask people to design their APIs such that they can be statically typed and then let them start writing type annotations, we observed that they very quickly flipped a mental bit and expected the full static typing experience. They expected real static safety where certain errors were proven to be absent. They expected the performance of a statically-typed "native" language.
But most optional or gradually typed languages are unsound in order to allow typed and untyped code to intermingle. That means type errors can still sneak through and bite you at runtime. It means you get none of the compile-time performance benefits of static types. Sorbet asks you to write your code with all of the discipline, restrictions, and cognitive effort of a statically-typed language. In return, it gives you the runtime performance of... Ruby.
Worse, actually, because it is checking your type annotations dynamically at runtime. It basically turns your type annotations into assertions. So you get even more potential runtime failures.
This was how Dart 1.0 worked. I used to joke that we gave you the best of both worlds: the brevity of Java and the speed of JavaScript. And then I cried a little.
---
This sounds like I'm criticizing this approach to languages. I actually think TypeScript, Flow, Sorbet, and others are a really smart solution to a very challenging problem. If you have a very large corpus of dynamically-typed code that you want to keep extracting value out of, they give you a way to do that while getting some of the benefits of types. If I was sitting on a giant pile of JS or Ruby that I had no plans to rewrite, I would absolutely use one of these tools.
But for new development, I think you're much better off choosing a modern statically typed language if you think there's a chance your program will grow to some decent size. By that, I mean C#, Go, Swift, Dart, Kotlin, etc. Type inference gives you most of the brevity of dynamic types and you'll get all the safety and performance you want in return for your effort to type your code.
If you're going to do the work to make your code typable, you should get as much mileage out of it as you can. So far, no one I know has figured out how to do that with an optionally or gradually typed language.
---
This is, of course, just my personal preference. And I'm biased because I've already walk the long painful educational road to understand static types. One of the real large benefits of dynamic types is there is much less to learn before you can start writing real code. For new users, hobbyists, or people where programming isn't their main gig, this is huge. I love that dynamically typed languages exist and can serve those people.
But my experience is that if you're a full time professional software engineer writing real production code eight hours a day, it's worth it to get comfortable with static typing and use it. The fact that basically every large software shop that had a big investment in dynamically typed languages is trying to layer static typing on now probably tells us something. Google with Closure Compiler and Dart. Microsoft with VB.Net, TypeScript, and Pyright. Facebook with Hack and Flow. Apple with Swift.