CS1 in Kotlin
30 min read
This semester(1) I began offering Kotlin as a language option in my CS1 course—alongside Java, which we had been using previously for many years. When I began using Kotlin three years ago, it seemed like an ideal language for teaching CS1.
And so far our experiences with it in my course have been extremely positive! Out of around 1300 enrolled students, around 20% are using Kotlin, and they are doing really well. Even with only 20% of the class using Kotlin, I suspect that this represents one of the larger current deployments of Kotlin for CS1. I’ll share how we got here, why I think Kotlin is such a great choice—particularly compared to Python and Java—and some early results from our use of Kotlin alongside Java.
Note that I wrote this in anticipation of a JetBrains education webinar that I joined as a guest. Here’s a link to my slides.
What is Kotlin?What is Kotlin?
According to Wikipedia, Kotlin is “a cross-platform, statically typed, general-purpose programming language with type inference.”
In addition, Kotlin has also been chosen by Google as the preferred language for Android development, expanding its usage to the 12 million app developers worldwide—half of the worldwide developer community—half of who develop for Android first.
There are many great online resources for learning about Kotlin, including an official Kotlin Playground tutorials and exercises. We’re also planning on publishing our own CS1 materials for Kotlin soon, which you can preview here.
Discovering KotlinDiscovering Kotlin
When I arrived at Illinois in Fall 2017, my first teaching assignment was as a co-instructor for CS1. I took over that course in Spring 2018, and have continued to teach it since.
Returning again to the language with the benefit of some perspective, as I began building tools to support my course, I was reminded of all of the things that people hate about Java.
And when considering the language from the perspective of a beginner, tack on weaknesses like an intimidating “Hello, world!”, avoidable confusion regarding equality, a character-heavy print statement, and the uncompromising objects-first requirement(2).
To me, Java just didn’t seem like a great choice for CS1.
But changing the language in CS1 is itself a herculean and dangerous task. I was new, and was doing my best to go along and get along. And Python, the other popular choice, has its own set of serious weaknesses. So I stuck it out with Java, and found a few ways around some of the languages more problematic features from a CS1 perspective. One workaround is that our interactive playgrounds support what we call snippet mode, relaxed Java syntax that supports top-level loose code and method declarations:
This allows us to work incrementally through the basics of imperative programming without needing to mumble away all of the normal concepts and keywords Java students encounter in just “Hello, world!”: classes (
class), methods (
void), arrays (
String), and even
But no number of tricks would make some of the more fundamental problems with Java go away—I’m looking at you,
So I kept thinking about alternatives.
As it turned out, Bill Chapman, my co-instructor my first semester teaching CS1, ended up dropping an important hint. He was the first person to suggest Kotlin, only a few weeks before retiring(3).
I had never heard of the language, and I’m not sure exactly when I began experimenting with it. But by the time I began rebuilding some of my teaching tools a few years later, in the summer of 2019, Kotlin was the language of choice. Jeed, our high-performance Java and Kotlin toolkit and playground backend, is the first large project that I began in Kotlin, and is something that we continue to use and improve today. Kotlin is now my primary development language for non-frontend tasks, and I maintain a bunch of different tools in the language—including our containerized autograder, course plugins for both Gradle and IntelliJ, our new small-problem authoring system (1, 2), a small library of basic CS1 data structures, and greenfield projects like a new polyglot playground backend(4).
As I began working in Kotlin I noticed something right away. I liked it. A lot! Kotlin bills itself as a language that “makes developers happier”. Not to be a shill, but that has actually been my experience. Minute for minute, hour for hour, day by day, I enjoy working in Kotlin a lot more than in Java, Python, TypeScript, or any other languages I use regularly.
Over my two-decade career as a software creator, the programming languages that I’ve used seem to just keep getting better. On one hand, that’s quite remarkable. But on the other, it’s rather unsurprising. Newer languages should be better! Not only have we learned a lot about language design, but computers have changed in quite astonishing ways. The designers of older languages like Java or Python could hardly anticipate how much faster computers would get, and so it’s hard to fault them for failing to put those capabilities to work to aid software creation.
Or, as I put it recently in an email to a student: Always bet on the future. If you’re not willing to, it’s hard to enjoy a life in technology.
Even if you’re ready to bet on the future, not everyone else is. Currently we are teaching Kotlin alongside Java in my CS1 course. Perhaps I’ll write something else at some point about the language wars that erupt whenever you want to try something new in CS1. At this point I’ll let the outcome speak for itself. I wanted to move to Kotlin, and we ended up with both Kotlin and Java. And while that might sound like a compromise, it’s a compromise that required someone (me) to do a ton of extra work to try something new while supporting the status quo. But was it worth it? Yes.
Maximizing Fun, Minimizing FrustrationMaximizing Fun, Minimizing Frustration
Obviously the fact that I happen to like Kotlin doesn’t make it a great choice for CS1. So let’s step back and consider what makes a language a good choice for introductory courses.
Let me start with an important point. Choosing a good language doesn’t, by itself, create a good introductory course. No programming language can make up for other more important elements of good course structure and design. A badly designed course will still be ineffective even if taught in a great language.
It’s equally true that you can teach a decent CS1 course even using a not-so-good language, since a well-designed course can at least partially overcome the weaknesses of a poor language. But this is no excuse for continuing to use languages that are hard on beginners! It frustrates me to no end to see introductory courses continue to use languages like C and C++ that represent extremely poor choices for introducing computer science to a general audience.
My approach to evaluating languages for CS1 is simple: Maximize fun, minimize frustration:
Fun represents the ability for students to create things that connect with their lived experience of computing. It lights the path from “Hello, world!” to making a difference in the world. You might also call it impact. In 2021, writing a command-line program or text-based game is just not that fun. Creating a graph from some data might be a bit more fun, at least for a certain population of students. But to get to maximum fun you need to show students how to create something they use every day—meaning either a website or an app.
Frustration goes hand and hand with creating software, and teaching students how to cope with frustration has to be a core consideration of CS1. But when we’re just getting started, we don’t want the language itself to introduce added frustration! Beginners naturally struggle with translating their ideas about how to solve a problem to working code. This is good frustration. Language idiosyncracities, confusing compiler errors, problems with the development environment, limited compile-time error checking, and other similar problems represent unnecessary frustration that can be overwhelming for students learning to program.
Note that, at least for beginners, fun is largely a property of what you can do with the language, while frustration is bound up in the syntax of the language and its development environment. I think that this makes sense until students get to the point where they are intrisically motivated. At this stage in my career, I find Kotlin’s elegant syntax and idioms to be inherently enjoyable, but I’m approaching projects with the attitude: I’m definitely going to build this, I’m not just not sure how. Beginners aren’t there yet.
I consider Kotlin the best choice for CS1 today because it does a great job of both maximizing fun and limiting frustration. Now, let me try to convince you. As described above, we’ll make the case for fun primarily based on what you can create with Kotlin, and the case for frustration primarily based on Kotlin’s syntax and features of the language itself. And as we go, I’ll draw out comparisons to Java and Python, which are probably the two most widely-used alternatives for CS1.
Frontend FunFrontend Fun
Students get excited about computer science when they can create the same things they use every day. Projects that consist entirely of command-line text interfaces or other backend components are unlikely to excite students who entered the field hoping to solve problems and change the world. Nor is it necessary or healthy for students to spend years and years “mining code deep underground”(5) before creating something that they can recognize, or demo to a parent or friend. My course has successfully used Android projects for the last several years, allowing students to build entire apps including UI design and implementation.
Minimizing Frustration: Pythonic Clarity, Java’s Type SafetyMinimizing Frustration: Pythonic Clarity, Java’s Type Safety
As many CS1 courses progressed from Java to Python, strong typing was abandoned in favor of syntactic clarity. Perhaps that was a necessary tradeoff at the time, given the limitations of Java and Python. But Kotlin type inference now gives you much of the best of both worlds: the clarity and conciseness that are particularly useful for small programming examples in CS1, but the added benefit of strong type checking to catch common mistakes and mimimize frustration.
Put another way—the compiler should be a great friend and aid to CS1 students. Unfortunately older compilers required so much syntactic noise that this got in the way of CS1 instruction. Happily, newer compilers—like most other computer software—have become more powerful and effective. Today, the small amount of additional syntax the Kotlin compiler requires from CS1 students is worth it for the kinds of mistakes it will help them identify and correct.
Let’s examine some code examples to see how Kotlin combines some of the best features of both Python and Java.
Kotlin is more user-friendly right out of the gate. Here is the minimal syntactically-correct “Hello, world!” in all three languages.
Java’s “Hello, world!” is positively terrifying to beginners, full of syntax clutter and unfamiliar concepts:
Even just trying to explain what
System.out.println is to a beginner is daunting.
The result is a lot of hand-waving and under-rug-brushing that leaves students with the sense that programming is full of arcane secret knowledge, rather that straightforward and ultimately quite comprehensible.
Both Python and Kotlin do much better:
And Kotlin also supports scripts that contain top-level code, just like Python:
Type Safety and InferenceType Safety and Inference
But Kotlin’s clarity does not come at the cost of type safety. Compare and contrast the following examples of basic type errors in Java, Python, and Kotlin. In Java, type information is scattered everywhere, even in this simple example. But at least it works:
Python is cleaner, but at the cost of no type checking:
And yes, I am aware that newer version of Python have introduced type hints. But nobody seems to use them, at least not in the CS1 courses I’ve reviewed. I’m sure that they are coming, and that will be a great improvement! Done right, gradual typing tools like TypeScript can be pretty fantastic.
Kotlin allows us to omit types from our declarations, but can still infer variable types and examine operations properly:
I think there’s an interesting open question about whether Kotlin introductory programmers will actually learn to think more effectively about types, since their own brain has to do the type inference when reading with even simple examples above, rather than being able to rely on the type duplication that Java provides. This would be a fascinating question to examine at some point.
Now let’s discuss possibly the worst Java footgun for introductory computer science: the difference between
It’s reasonable to start beginners in Java working with primitive types.
So when you teach them about equality, you teach them
==, which is the correct way to compare primitive types:
It’s also reasonable to have Java
Strings be the first object that you discuss in CS1, since they are both useful and also the only Java object you can create with a literal:
When you introduce objects, you are careful to remind students that, when you compare two objects, you must use
Yes, it’s ugly, you say.
But you must.
All the time!
What is happening here?
Unfortunately, Java also implements
String interning, meaning that two
String literals will also be referentially equal.
Meaning that our first chance to practice with objects (
String) is also a terrible source of confusion regarding equality.
Yes, I know. Everyone knows this about Java. Everyone—except people that don’t know Java yet! I cannot tell you how many times I see students making this mistake, or variants of it. I cannot tell you how many times I’ve made this mistake myself. And yes, a decent IDE will also warn you about this, but introductory students aren’t always programming in an IDE, and sometimes it’s better for them not to.
So you can plan on fighting this particular bug all semester long.
Or just use Kotlin, where
== works properly, and calls
.equals behind the scenes:
When you actually want referential equality—and you rarely ever do in CS1—you can break out the triple-barrel:
Python also seems to work properly in this regard:
Things get worse for Java when using containers, such as lists, that you really want to have in CS1.
Before you can start working with lists in Java you need to discuss
import and type parameters,
and you’ll also need to be prepared to answer questions about the difference between interfaces (
List) and their implementations (
Manipulating lists requires awkward calls to
.get, which clutter the code and are visually extremely distinct from the bracket notation used by arrays, even though they behave identically.
So much for computational thinking.
And as if that weren’t bad enough, Java list literals created with
Arrays.asList are actually immutable by default!
Here’s the right way, which requires yet another import and more type clutter:
This is one great example of a major Java footgun that primarily affects introductory programming courses. It’s rare in real code to actually want to create a list literal, since usually you’re going to populate it with data flowing in from some other part of the program. But when teaching CS1 it’s extremely common to want to create list literals, as you demonstrate to students how lists works using small examples with prepopulated data.
In contrast, Python gets this right—no imports, bracket notation:
And Kotlin does as well, particularly when working with list literals where we can utilize type inference:
I actually tend to think that operator overloading is not a great language feature, since it’s easy for people to get overly excited about and start to abuse. But it’s use in Kotlin to facilitate array-like lists and maps might actually make it worth it.
null is both a poor idea and a common stumbling block for novice programmers.
So the fact that Kotlin places so much attention on improving
null safety is both great for the language and for CS1.
Kotlin also chooses a middle ground here—neither eliminating
null nor allowing you to ignore it, as you can in Java, C++, Python, and pretty much every other older language.
This is an ideal stance pedagogically, given that CS1 students will need to wrestle with
null values in other languages for the foreseeable future.
Kotlin allows you to introduce and discuss
null while still enabling beginners to write
And of course this is also another great argument for using a compiler.
Again, compare the following code in Java, Python, and Kotlin. Java lets you shoot yourself directly in the foot:
Python isn’t much better.
null by any other name is still
Kotlin’s ability to allow you to safely work with
null is one of its selling points, and a great feature:
About the only place I’ve seen
null-safety in Kotlin fall down is when working with maps, but that’s a fundamentally hard problem.
One way to introduce objects naturally in CS1 is to present them as a way to represent more complex data than can be stored using primitive types: a person has both a name and an age. Students can get a fair amount of mileage out of objects as records even before introducing the idea of object methods—particularly when working with data.
Kotlin’s data classes are a perfect fit for this use of objects.
In only a few lines, students can be introduced to simple compound types and begin using them to store and manipulate data.
At some point later data classes can easily be transitioned to classes that provide methods.
Kotlin’s data classes are more convenient that Java’s very ceremony-forward classes, or Python’s classes, even incorporating recent support for Python
Compare Java, Python, and Kotlin’s approach to create a simple class to model data—one that acts purely as a record linking multiple fields together. Java requires a ton of ceremony even to just link two values together:
Python is a bit better, and even more so you use recent support for data classes:
Kotlin’s primary-constructor syntax on even normal classes already eliminates almost all of Java and Python’s excessive ceremony.
Data classes also come with sensible
toString and copy methods, and support destructuring assignment:
You want CS1 students to reach for a class when they need to work with structured data, and not be tempted to commit dictionary abuse or an even worse solution(8). Java’s classes require enormous amounts of syntactic ceremony, and top-level classes much be separated into their own file. This causes even simple record-like classes to feel like a heavyweight abstraction.
Natural Progression from Blocks to Functions to ClassesNatural Progression from Blocks to Functions to Classes
One weakness of Java and other non-pedagogical programming languages is that the minimum runnable example forces CS1 students to squint at concepts that they have not been introduced to yet. As show above, in Java if I want to print “Hello, world!” I need to wrap that in both class and method declarations.
Because Kotlin supports both scripts (
.kts files) and top-level method declarations it naturally supports introducing CS1 students to programming by building up from blocks to methods to classes.
Note that it shares this desirable feature with Python, so many Python examples used in CS1 should port to Kotlin easily.
We have already compared the simplest possible class between Java, Python, and Kotlin above. But also compare Java, Python, and Kotlin in the progression from loose code to functions. In Java absolutely everything has to be surrounded by unintelligible class clutter:
Python allows you to work from the ground up, seamlessly:
And, with support for scripts, you can do the same in syntactically-valid Kotlin:
As described previously, we have used templating tricks in my CS1 course to work around Java’s limitations in this area. While this can help, it does engender some mild confusion—particularly as you begin to remove the templates and have them write more complete pieces of code. Overall it is desirable to have students writing code that is valid without requiring any magic behind the scenes.
Other ConsiderationsOther Considerations
In addition to being more fun than and less frustrating than Java or Python, there are several other things that make Kotlin a great choice for an introductory programming course.
Leveling the Playing FieldLeveling the Playing Field
Many CS1 courses—including mine—mix students with a variety of programming backgrounds: from no to substantial prior experience. Many incoming freshman acquired that experience through high school AP CS courses—either AP CS A or AP CS Principles. Those courses tend to be taught in Java (AP CS A) or Python (AP CS Principles).
As a result, many students that do enter college CS1 with prior experience know either Java or Python. By teaching CS1 in a language that is neither Java nor Python, we can level the playing field and provide value to students that already know some programming. Learning a second language is admittedly easier than learning a first programming language, but is also definitely harder than reviewing a language that you already know.
For this reason Kotlin is a better fit for both beginners (it is more beginner-friendly for reasons outlined below) and for students with some prior experience (it is different than what they already know).
Exacerbating existing skills gaps by teaching CS1 in a language that many students already know—like Java or Python—also makes calibrating the difficulty level of CS1 more challenging. Students that already know Java may provide the instructor with a false sense that most students are following the material, when in fact they are relying on prior knowledge and true beginners are struggling. A few times in my CS1 course we’ve noticed a big experience gap in performance on a particular quiz, a sure sign that we haven’t taught a particular concept well to students that didn’t already know it, but also a sign that is very easy to miss without appropriate cohort analysis.
Kotlin is both an excellent introductory language and one that, at least for now, will be unfamiliar to most incoming students—both beginners and those with prior programming experience. This makes it an ideal choice for CS1 and a great way to address the incoming skill gap.
High Quality Online ResourcesHigh Quality Online Resources
While all programmers benefit from good online resources, they are particularly important for CS1 students. Not only will CS1 students inevitably search for help online, but as less experienced programmers they are less able to judge the quality of the answers that they find. Given that they can also ask their course staff for help when needed, it may actually be preferable for CS1 students to not find an answer online than to find a bad one.
Modern languages just seem to have a leg up in this area—probably because they know that they are competing for fickle developers in a crowded space. But Kotlin has all of what you expect these days: attractive documentation incorporating embedded runnable code snippets, and tutorials for beginners and more advanced programmers. The quality of the code used in their documentation and examples is excellent—which is not always the case with Java.
JetBrains is also providing resources and systems specifically tailored to educators that should allow us to design our own Kotlin-based course while leveraging a fair amount of their infrastructure.
Support for Multiple Programming ParadigmsSupport for Multiple Programming Paradigms
CS1 courses may rotate between instructors with different ideas about how to introduce students to programming. Best practices also change over time. So it is helpful to have a language that naturally supports multiple programming paradigms and styles—without having to resort to specialized libraries or approaches that violate established language norms.
At minimum Kotlin supports what seem to be the most common CS1 programming styles: imperative, object-oriented, and functional. Obviously this is because it is less opinionated than say Java (about objects-first) or Haskell (about purely functional). There are straightforward migration paths to Kotlin for CS1 courses taught in Java, Python, C, C++, and many functional languages. It’s less clear for functional languages with Scheme-like syntax, since the syntax is just so different. But the concepts should translate.
Kotlin’s syntactic flexibility also makes is a great choice for a multi-course sequence that introduces students to different programming styles along the way. You can image a first course in Kotlin that focused on imperative and object-oriented patterns, and then a second course that leverage it’s support for functional styles. Or you could flip the order! Paradigm shifts in many curricula are accomplished by switching languages, but a new language introduces a bunch of other distractors that might drown out the stylistic differences. Kotlin allows you to illustrate the differences between an iterative and functional approach to list summation within a few lines in the same language:
We’ve been building out a toolchain for supporting Kotlin in our course, and as part of it we’ve developed some feature analysis tools. So it may also be possible to even enforce a functional style in Kotlin, by prohibiting looping and certain selection statements. That would be fun to try.
And, Of Course, Great ToolsAnd, Of Course, Great Tools
I would be remiss if I didn’t mention that JetBrains, the company that maintains Kotlin, is in the business of selling developer tools. And it shows. Kotlin support in the IntelliJ IDE is fantastic, and JetBrains seem to be launching a lot of other exciting tools as well—such as Code With Me, which we’re going to continue to evaluate for use in CS1.
Experiences with Kotlin in CS1Experiences with Kotlin in CS1
This is fairly long already, and the semester isn’t quite over. So I’ll postpone a full examination of our experiences with Kotlin in my CS1 course for another post.
But as a brief summary, and to whet your appetite. We’ve released Kotlin alongside Java as a first-class language citizen in CS 124, my CS1 course. Students complete 70 daily lessons, all available in Java and Kotlin. In almost all cases, the topics of the Java and Kotlin lessons are the same each day, with only the languages used in the examples and on the problems being different.
The daily lessons include hundreds of interactive playgrounds along with 446 videos and interactive walkthroughs created by 17 different authors, with primary contributions from me and Colleen Lewis. Our Java materials have a two-semester head start and are still more comprehensive—1116 videos and interactive walkthroughs from 135 contributors. But our Kotlin content is catching up quickly.
We build programming proficiency through daily practice and homework problems and on weekly proctored quizzes. Our current materials include 58 practice problems, 63 graded homework problems, 37 practice quiz questions, and 36 graded quiz questions, for a total of 194 programming challenges. But this is only a subset of our full library of 480 small programming problems, created and graded using our custom problem authoring framework—which is, of course, written in Kotlin! Students also complete a multi-part Android development project, which is available in both languages.
We have 201 staff who know only Java, 13 who know only Kotlin, and 91 able to help students in both languages. And, out of 1282 current students, 259 are using Kotlin (20%) and the remainder Java. At this point the Kotlin students are doing a bit better than the Java students, overall, but I suspect that this is due to the fact that Kotlin attracted a slightly more experienced group of students.
All in all, the results are fairly easy to summarize. Kotlin works for CS1! And the fact that it already works this well, in our first semester, when we still have a lot more staff, materials, and institutional knowledge supporting Java, makes me very excited about the future.
If you would like to use Kotlin in your CS1 course, either alongside Java or on its own, I would love to help! We have all kinds of cool tools that I’d love to share with you and work on together, and the format of our materials opens up the possibility for exciting cross-institution collaborations. Please get in touch.