Part of why this [EDIT: to clarify - the debug/break loop process paired with the REPL] works in Smalltalk and Lisp is their dynamic nature, and also to the fact that they are systems as much as languages. In principle, Ruby and Python should be able to have as capable dev environments. With more static languages like C, Java, C#, and others you run into the problem of needing more "skeleton". You can't write:

void foo() {
bar();
}

in those languages and expect the compiler to be thrilled about it. But you could get closer. Java runs on the JVM, C# on the CLR, those provide systems on which the languages (or programs written in those languages) are executed and the kind of access needed to approach the debugger-driven development approach. I haven't done this in a long time but with C# you can do:

void foo() {
throw new NotImplementedException();
}

Now when your program execution hits `foo` you can catch it in the debugger (versus just crashing or otherwise terminating, or needing to set a breakpoint). With VS (and probably others, I did this with VS) you can change this code and recompile it and resume from that point. Now you decide to make the call to `bar`, oops, that method doesn't exist yet so you also have to create `bar`, and you can stub it out the exact same way. So it's not as easy and pleasant as in Lisp or Smalltalk where you can forgo the skeletal framework of the program and jump straight into it, building each function, method, class, etc. only as needed (truly JIT development), but you can get close. I believe Java has some similar facilities with some dev environments. There is more work, but it's not totally impossible.

All that said, I really enjoy the way you can do this in Lisp (and Smalltalk). I've been debating re-solving the Synacor Challenge [0] and recording how I did it in Lisp the first time. I used the debugger in precisely this way, writing a basic version which could read in the binary blob and did minimal parsing, but then entered an ecase (case expression that errors if there is no matching case). For every instruction that I hadn't implemented (none of them at the start) it brought me to the debugger, I modified the program, resumed it and it continued until the next unimplemented instruction. By the time I'd finished, I had the interpreter for the byte code up and running and was immediately getting to work on the rest of the challenge. This also worked as a nice goal-directed approach, implementing the whole interpreter was a "big" (not really) task, how to break it down and which part to do first? Well, interpreting the binary blob acted as my (very complex) test case and gave me immediate feedback (I also added some basic debugger capabilities to the interpreter) on whether I had done it correctly. No need to make the whole thing before testing it, I got to incrementally test each small change.

That approach could also be made to work with C# as I described before, write a basic skeleton and parse the instructions but throw a `NotImplementedException` whenever you get to an instruction you haven't, well, implemented yet. Then using VS change the throw to a method call (or do it locally) that implements the handler for that instruction. Repeat until all of them are filled out. It's not as smooth as the Lisp or Smalltalk cases would be, but it's doable.

[0] https://challenge.synacor.com - programming challenge by the same guy that does Advent of Code.

----------

Also, this has come up on here a few times in the past since it was written (2020). Two past submissions with comments:

https://news.ycombinator.com/item?id=25620256 - Jan 3, 2021 (208 comments)

https://news.ycombinator.com/item?id=25618457 - Jan 3, 2021 (5 comments, 4 + 1 dead, don't know why it's dead)