Kotlin is like TypeScript
BASICS
Hello World
Kotlin
println("Hello, world!")
TypeScript
console.log("Hello, world!");
Variables And Constants
Kotlin
var myVariable = 42
myVariable = 50
val myConstant = 42
TypeScript
let myVariable = 42;
myVariable = 50;
const myConstant = 42;
Explicit Types
Kotlin
val explicitDouble: Double = 70.0
// Boolean
// Byte, Short, Int, Long, Float, Double, UByte, UShort, UInt, ULong
// String
// Any, Unit
// Nothing
// dynamic
external fun require(module:String): dynamic
TypeScript
const explicitDouble: number = 70;
// boolean
// number, bigint
// string
// any, void
// never
// undefined, unknown, null, Symbol, union: string|number, string|object
declare const welcome: any;
Type Inference
Kotlin
val label = "The width is "
val width = 94
val widthLabel = label + width
TypeScript
const label = "The width is ";
const width = 94;
const widthLabel = label + width;
Type Aliases
Kotlin
typealias Name = String;
typealias NameResolver = () -> String;
TypeScript
type Name = string;
type NameResolver = () => string;
Smart casts
Kotlin
fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x is automatically cast to String
    }
}
TypeScript
function foo2(arg: unknown) {
  if (typeof arg === "string") {
    // We know this is a string now.
    console.log(arg.toUpperCase());
  }
}
Equality
Kotlin
//Structural Equality (‘==’), Referential equality (‘===’), .equals method
data class User(val id: Int, val nick: String)

fun testEquals() {
    val first: Any = User(1, "linux_china")
    val second: Any = User(1, "linux_china")
    println(first == second)  //true
    println(first === second)  //false, Referential different
    println(first.equals(second)) //true
}

TypeScript
//Equals Operator ( == ), Strict Equals Operator ( === )
let a: any = 10;
let b: any = "10"
console.log(a == b);  //true
console.log(a == b);  //true
console.log(a === b); //false, type different
Nullable/NonNull/Nullish Coalescing
Kotlin
val name1: String = "xx"
var name2: String? = null

fun testNullable() {
    name2.length //illegal
    name2?.length //nullable check
    name2!!.length //you know that
}

// Elvis Operator
val name3 = name2 ?: "good";
TypeScript
let name1: string = "xx"
let name2: string | null = null

name2.length //null check validation
let len = name2?.length //nullable check
name2!.length //you know that

//Nullish Coalescing
let x1 = name2 ?? "default value";
String Interpolation
Kotlin
val apples = 3
val oranges = 5
val fruitSummary1 = "I have ${apples + oranges} pieces of fruit."
val fruitSummary2 = """I have ${apples + oranges} pieces of fruit."""
val fruitSummary3 = html("""I have ${apples + oranges} pieces of fruit.""")
TypeScript
const apples = 3;
const oranges = 5;
const fruitSummary1 = `I have ${apples + oranges} pieces of fruit.`;
const fruitSummary2 = html`I have ${apples + oranges} pieces of fruit.`;
Range Operator
Kotlin
val names = arrayOf("Anna", "Alex", "Brian", "Jack")
val count = names.count()
for (i in 0..count - 1) {
    println("Person ${i + 1} is called ${names[i]}")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack
TypeScript
import * as _ from 'lodash';
const names = ["Anna", "Alex", "Brian", "Jack"];
const count = names.length;
for (let i of _.range(0, count)) {
    console.log(`Person ${i + 1} is called ${names[i]}`)
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack
Inclusive Range Operator
Kotlin
for (index in 1..5) {
    println("$index times 5 is ${index * 5}")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
TypeScript
import * as _ from 'lodash';

for(let index of _.range(1, 6)) {
    console.log(`${index} times 5 is ${index * 5}`)
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
COLLECTIONS
Arrays/List
Kotlin
fun main12() {
    val shoppingList = arrayOf(
        "catfish", "water",
        "tulips", "blue paint"
    )
    shoppingList[1] = "bottle of water"
    val freezeList = listOf("first", "second", "third")
    val mutableList = mutableListOf("first", "second", "third")
}
TypeScript
let shoppingList = ["catfish", "water",
    "tulips", "blue paint"];
shoppingList[1] = "bottle of water";
Set
Kotlin
val set1 = setOf(1 , 2 , 3 , 4 , 3)
val mutableSet = mutableSetOf(1 , 2 , 3 , 4 , 3);

TypeScript
let mySet = new Set(); //ES2015
mySet.add(1);
mySet.delete(1)
Stream/Sequence
Kotlin
val wordsSequence = listOf("one", "two", "three", "four").asSequence()
val list2 = wordsSequence
    .filter { println("filter: $it"); it.length > 3 }
    .map { println("length: ${it.length}"); it.length }
    .toList()
TypeScript
let words = ["catfish", "water", "tulips", "blue paint"];
let list2 = words.filter(word => {
    return word.length > 3;
}).map(word => {
    return word.length
});
Map
Kotlin
val occupations = mutableMapOf(
    "Malcolm" to "Captain",
    "Kaylee" to "Mechanic"
)
occupations["Jayne"] = "Public Relations"
TypeScript
let occupations = {
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
};
occupations["Jayne"] = "Public Relations";
Tuple
Kotlin
// data class as Tuple in Kotlin
data class GasPrices(val a: Double, val b: Double, val c: Double)

fun main7() {
    val price = GasPrices(3.59, 3.69, 3.79)
    val (a, b, _) = price
}
TypeScript
type GasPrices = [number, number, number]

//Labeled Tuple
type Range = [start: number, end: number];

function main4() {
    let price: GasPrices = [3.59, 3.69, 3.79]
    let [a, b, c] = price;
}
Filter/Map/Reduce
Kotlin
fun main13() {
    val shoppingList = arrayOf("catfish", "water", "tulips", "blue paint")
    shoppingList.filter { it.startsWith("c") }
        .map { it.length }
        .reduce { acc, i -> acc + i }

    shoppingList.filter { it.startsWith("c") }
        .forEach(::println)
}
TypeScript
let shoppingList2 = ["catfish", "water", "tulips", "blue paint"];

shoppingList2
    .filter(item => item.startsWith("c"))
    .map(item => item.length)
    .reduce((acc, item) => acc + item)

shoppingList2
    .filter(item => item.startsWith("c"))
    .forEach(item => {
        console.log(item)
    })
Empty Collections
Kotlin
val emptyArray = arrayOf<String>()
val emptyMap = mapOf<String, Float>()
TypeScript
const emptyArray: string[];
const emptyDictionary: {[key: string]: number};
FUNCTIONS
Functions
Kotlin
fun greet(name: String, day: String): String {
    return "Hello $name, today is $day."
}
greet("Bob", "Tuesday")
TypeScript
function greet(name: string, day: string): string {
    return `Hello ${name}, today is ${day}.`
}
greet("Bob", "Tuesday");
Variable Number Of Arguments
Kotlin
fun sumOf(vararg numbers: Int): Int {
    var sum = 0
    for (number in numbers) {
        sum += number
    }
    return sum
}

fun main3() {
    sumOf(42, 597, 12)
    // sumOf() can also be written in a shorter way:
    fun sumOf(vararg numbers: Int) = numbers.sum()
}
TypeScript
function sumOf(...numbers: number[]): number{
    let sum = 0;
    for (let number of numbers) {
        sum += number;
    }
    return sum;
}
sumOf(42, 597, 12);

Function Type
Kotlin
fun makeIncrementer(): (Int) -> Int {
    val addOne = fun(number: Int): Int {
        return 1 + number
    }
    return addOne
}
val increment = makeIncrementer()
increment(7)

// makeIncrementer can also be written in a shorter way:
fun makeIncrementer() = fun(number: Int) = 1 + number
TypeScript
function makeIncrementer():(number) => number{
    function addOne(number: number): number {
        return 1 + number;
    }
    return addOne
}
let increment = makeIncrementer();
increment(7);

// makeIncrementer can also be written in a shorter way:
let makeIncrementer = () => (number: number) => 1 + number;
Functional Interface
Kotlin
fun interface IntPredicate {
    fun accept(i: Int): Boolean
}

val isEven = IntPredicate { it % 2 == 0 }

fun main() {
    println("Is 7 even? - ${isEven.accept(7)}")
}
TypeScript
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;

mySearch = function (source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
};
Lambda/Arrow Function
Kotlin
//lambda
val printText = { text: String ->
    println(text)
}
TypeScript
//arrow function
let printText = (text: string) => {
    console.log(text)
}
Named Arguments
Kotlin
fun area(width: Int, height: Int) = width * height
area(width = 2, height = 3)

// This is also possible with named arguments
area(2, height = 2)
area(height = 3, width = 2)
TypeScript
function area({width, height}:{width:number, height:number}):number {
    return width * height;
}
area({width: 2, height: 3});
Function Generics
Kotlin
fun  identity(value: T): T {
    return value
}

val str = identity("Hello")
TypeScript
function identity(value: T): T {
    return value;
}

let str: string = identity("Hello");
Generator Function
Kotlin
val sequence2 = sequence {
    val start = 0
    // yielding a single value
    yield(start)
    // yielding an iterable
    yieldAll(1..5 step 2)
    // yielding an infinite sequence
    yieldAll(generateSequence(8) { it * 3 })
}

println(sequence2.take(7).toList()) // [0, 1, 3, 5, 8, 24, 72]
TypeScript
function* counter(max: number): Generator {
    let i = 0;
    while (i < max) {
        if (yield i++) {
            break;
        }
    }
}

for (let num of counter(3)) {
    console.log(num);
}
Async Generator Function
Kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

fun simple(): Flow = flow { // flow builder
    for (i in 1..3) {
        delay(100) // pretend we are doing something useful here
        emit(i) // emit next value
    }
}

fun main() = runBlocking {
    // Collect the flow
    simple().collect { value -> println(value) }
}
TypeScript
// async generator function
async function* g() {
  yield 1;
  await sleep(100);
  yield* [2, 3];
  yield* (async function*() {
    await sleep(100);
    yield 4;
  })();
}

async function f() {
  //The for-await-of Statement
  for await (const x of g()) {
    console.log(x);
  }
}
Optional/Default Value Params
Kotlin
//Optional Parameters by default value
fun sayHello(name: String = "") {

}

fun main4() {
    sayHello()
    sayHello("Jackie")
}
TypeScript
// Optional Parameters
function sayHello(hello?: string) {
    console.log(hello);
}

//default-initialized parameters
function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

sayHello()
sayHello("Jackie")

buildName("Jack")
buildName("Jack", "Ma")
Method Extension
Kotlin
import kotlin.js.RegExp

fun String.isEmailValid(): Boolean {
    val pattern = RegExp("^[\\w.-]+@([\\w\\-]+\\.)+[A-Z]{2,8}$", "i")
    return pattern.test(this)
}
TypeScript
declare interface String {
    isEmailValid(): boolean;
}

String.prototype.isEmailValid = function (this: string): boolean {
    let re = new RegExp("^[\\w.-]+@([\\w\\-]+\\.)+[A-Z]{2,8}$", 'i');
    return re.test(this);
};
CLASSES
Declaration
Kotlin
class Shape {
    private var numberOfSides = 0

    constructor(numberOfSides: Int) {
        this.numberOfSides = numberOfSides
    }

    fun simpleDescription() =
        "A shape with $numberOfSides sides."
}

val shape2 = Shape(4)
TypeScript
class Shape {
    numberOfSides = 0;

    constructor(numberOfSides: number) {
        this.numberOfSides = numberOfSides;
    }

    simpleDescription(){
        return `A shape with ${this.numberOfSides} sides.`;
    }
}

let shape2 = new Shape(4)
Usage
Kotlin
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
TypeScript
let shape = new Shape();
shape.numberOfSides = 7;
let shapeDescription = shape.simpleDescription();
Subclass
Kotlin
open class NamedShape(val name: String) {
    var numberOfSides = 0

    open fun simpleDescription() =
        "A shape with $numberOfSides sides."
}

class Square(var sideLength: BigDecimal, name: String) :
        NamedShape(name) {
    init {
        numberOfSides = 4
    }

    fun area() = sideLength.pow(2)

    override fun simpleDescription() =
        "A square with sides of length $sideLength."
}

val test = Square(BigDecimal("5.2"), "square")
test.area()
test.simpleDescription()
TypeScript
class NamedShape {
    numberOfSides: number = 0;
    name: string;

    constructor(name: string) {
        this.name = name
    }

    simpleDescription():string {
        return `A shape with ${this.numberOfSides} sides.`;
    }
}

class Square extends NamedShape {
    sideLength: number;

    constructor(sideLength: number, name: string) {
        super(name);
        this.sideLength = sideLength;
        this.numberOfSides = 4;
    }

    area(): number {
        return this.sideLength * this.sideLength;
    }

    simpleDescription(): string {
        return "A square with sides of length " +
	       this.sideLength + ".";
    }
}

let test = new Square(5.2, "square");
test.area();
test.simpleDescription();
Checking Type
Kotlin
var movieCount = 0
var songCount = 0

class Movie
class Song

fun main10() {
    val library: List = listOf("first")
    for (item in library) {
        if (item is Movie) {
            ++movieCount
        } else if (item is Song) {
            ++songCount
        }
    }
}
TypeScript
let movieCount = 0;
let songCount = 0;

class Movie {
}

class Song {
}

let library: unknown[] = [new Movie(), new Song()]

for (const item of library) {
    if (item instanceof Movie) {
        ++movieCount;
    } else if (item instanceof Song) {
        ++songCount;
    }
}
DownCasting
Kotlin
for (current in someObjects) {
    if (current is Movie) {
        println("Movie: '${current.name}', " +
	    "dir. ${current.director}")
    }
}
TypeScript
for (let current in someObjects) {
    if (current instanceof Movie) {
        console.log(`Movie: '${movie.name}', ` +
            `dir. ${movie.director}`);
    }
}
Protocol/Interface
Kotlin
interface Nameable {
    fun name(): String
}

fun f<T: Nameable>(x: T) {
    println("Name is " + x.name())
}
TypeScript
interface Nameable {
    name(): string;
}

function f(x: Nameable) {
    console.log("Name is " + x.name());
}
Mixins
Kotlin
//mixin in Kotlin just Kotlin interface
interface MyInterface {
    val prop: Int // abstract

    fun foo() {
        print(prop)
    }
}

class Child : MyInterface {
    override val prop: Int = 29
}
TypeScript
// Disposable Mixin
class Disposable {
    isDisposed: boolean = false;

    dispose() {
        this.isDisposed = true;
    }
}

class SmartObject {
}

interface SmartObject extends Disposable {
}

applyMixins(SmartObject, [Disposable]);

let smartObj = new SmartObject();
smartObj.dispose();

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            // @ts-ignore
            Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name));
        });
    });
}

Enums
Kotlin
enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
}
TypeScript
enum Direction {
    NORTH, SOUTH, WEST, EAST
}

enum Arrow {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}
Annotations/Decorators
Kotlin
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1)
    }
}
TypeScript
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    @enumerable(false)
    greet() {
        return "Hello, " + this.greeting;
    }
}
Object expressions
Kotlin
val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    // object expressions extend Any, so `override` is required on `toString()`
    override fun toString() = "$hello $world"
}
TypeScript
const helloWorld = {
  name: (): string => {
    return "";
  }
}
Object as Function/invoke operator/Hybrid Types
Kotlin
class Config {
    operator fun invoke(): String {
        return "invoke"
    }
}

fun main5() {
    val config = Config()
    println(config())
}
TypeScript
interface Counter {
    (start: number): string;

    interval: number;

    reset(): void;
}

function getCounter(): Counter {
    let counter = (function (start: number) {
        console.log("start")
    }) as Counter;
    counter.interval = 123;
    counter.reset = function () {
    };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
Indexed operator/Indexable Types
Kotlin
//Indexed access operator
class StringArray {
    operator fun get(index: Int): String {
        return ""
    }

    operator fun set(index: Int, value: String) {

    }

    operator fun get(index: String): String {
        return ""
    }

    operator fun set(index: String, value: String) {

    }
}

fun main8() {
    val stringArray = StringArray()
    stringArray[0]
    stringArray["first"] = "second"
}
TypeScript
//Indexed access operator
class StringArray {
    operator fun get(index: Int): String {
        return ""
    }

    operator fun set(index: Int, value: String) {

    }

    operator fun get(index: String): String {
        return ""
    }

    operator fun set(index: String, value: String) {

    }
}

fun main8() {
    val stringArray = StringArray()
    stringArray[0]
    stringArray["first"] = "second"
}
Data Class
Kotlin
data class Point(val x: Double, val y: Double)

val point = Point(1.0, 2.0);
TypeScript
type Point = {
    x: number,
    y: number
}

let point: Point = {x: 1.0, y: 2.0};
readonly property
Kotlin
//use val for readonly
data class Foo2(val bar: Double, val bas: Double)

fun main11() {
    val foo2 = Foo2(1.0, 2.0)
    foo2.bar = 2.0 //illegal
}
TypeScript
type Foo2 = {
    readonly bar: number;
    readonly bas: number;
};
const foo: Foo2 = {bar: 123, bas: 456};
foo.bar = 456; // illegal
Generics
Kotlin
class Box(val value: T) {

}

val box = Box(1.0)
TypeScript
class Box {
    value: T;

    constructor(value: T) {
        this.value = value;
    }
}

let x: Box = new Box(1.0);
Dynamic keys
Kotlin
class MyClass {
    private val store = mutableMapOf()
    operator fun get(key: String): Any? {
        return store[key]
    }

    operator fun set(key: String, value: Any) {
        store[key] = value
    }
}
TypeScript
interface DictionarySupport {
    [index: string]: any;
}
Async
Coroutines/Promise
Kotlin
suspend fun findNickById(id: Int): String {
    return "nick $id"
}

suspend fun main() {
    val nick = findNickById(1)
}
TypeScript
//Promise
const promise1 = new Promise((resolve, reject) => {
    resolve("good")
    //reject(new Error('failed'));
});

promise1.then(value => {
    console.log('Hi', value);
}).catch(error => {
    console.log('Oops', error);
});

(async () => {
    let value = await promise1
    console.log(value)
})();

Flow/Observable
Kotlin
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.onEach

fun findVip(): Flow {
    return arrayOf("first", "second").asFlow()
}

fun main2() {
    findVip().onEach {
        print(it)
    }
}
TypeScript
import { from } from 'rxjs';
const result = from( [10, 20, 30]);

result.subscribe(x => console.log(x));
Module/Namespace
Namespace
Kotlin
package validation

import kotlin.js.RegExp

val zipCodeRegex = RegExp("[0-9]+")

interface StringValidator {
    fun isAcceptable(s: String): Boolean;
}

class ZipCodeValidator : StringValidator {
    override fun isAcceptable(s: String): Boolean {
        return s.length == 5 && zipCodeRegex.test(s);
    }
}

TypeScript
namespace Validation {
    const numberRegexp = /^[0-9]+$/;

    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string): boolean {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZipCode"] = new Validation.ZipCodeValidator();
External Declare
Kotlin
@file:JsModule("lodash")
package lodash

@JsName("toUpper")
external fun toUpper(text: String): String
TypeScript
//index.d.ts
export = _;
export as namespace _;

declare const _: _.LoDashStatic;
declare namespace _ {
    interface LoDashStatic {
    }
}
Module Export
Kotlin
@JsExport
@JsName("hello")
fun hello(name: String): String {
    return "Hello name"
}
TypeScript
export function hello(name: string): string {
    return `Hello ${name}!`
}
Tools/Library
Unit Test
Kotlin
// testImplementation(kotlin("test-js")) in build.gradle.ts
import kotlin.test.Test
import kotlin.test.assertEquals

class DemoTest {
    @Test
    fun testBar() {
        console.log("Hello World!")
        assertEquals(4, 2 * 2)
    }
}
TypeScript
// jest, ts-jest
test("hello test", () => {
    console.log("hello test");
});
Tools
markdown
### Kotlin Tools
* Gradle: project build tool with dev server support
* dukat: Convert declarations(d.ts) to Kotlin external declarations
markdown
### TypeScript Tools
* npm/yarn: project build tool
* webpack: bundle your assets and dev-server support
Pros/Cons
Pros
Kotlin
//When expression
when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
}

// DSL
html {
  head {
     title {+"XML encoding with Kotlin"}
  }
}

//Operator overloading: https://kotlinlang.org/docs/operator-overloading.html#unary-operations

//Delegated properties
class Example {
    var p: String by Delegate()
}
TypeScript
// tagged literal
const code = html`xxx`

//String Literal Types
type EventType = "click" | "mouseover";

// union
type TextOrNumber = string | number;

// tuple
let x2: [string, number] = ["hello", 10];