noCap

A programming language for GenZ

$ pnpm install nocap-cli -g

Playground

Run noCap directly in your browser

Loading Editor...

Tutorial

Learn noCap fundamentals with interactive tutorials.

ProgressStep 1 of 3

Move night planner

Not Tested

Problem

Implement a function planMovieNight(moviesPrices, budget) that:
  • Takes an array of movie prices and a budget
  • Returns the average price of the movies you can afford
  • Returns ghosted if you can't afford any movies

Examples

planMovieNight([15, 25, 8, 12], 30) should return 15
planMovieNight([10, 26, 10], 10) should return 10
Loading Editor...

Docs

Everything you need to know about noCap's syntax and features.

Variables

Variables in noCap are decalred using the fr keyword.

fr num = 42;

noCap uses dynamic typing - variable types are automatically inferred from their values. The language supports the following primitive types:

fr num = 42;           // integer
fr pi = 3.14;          // float  
fr message = "Hello world";  // string
fr isFalse = cap;       // boolean (false)
fr isTrue = noCap;      // boolean (true)
fr empty = ghosted;     // similar to null/nil in other languages

Arithmetic operations can be peformed between strings, integers and floats:

fr sum = 5 + 10;          // 15
fr product = 4 * 2.5;     // 10.0
fr strings = "Hello " + "World";  // "Hello World"
fr complex = "Result: " + (3 + 4.5);  // "Result: 7.5"

Arrays and Maps

For complex data types, noCap supports arrays and maps. Arrays can contain multiple items of different types and use 1-based indexing (starting from 1, not 0):

fr arr = [1, "hello", cap];

arr[3]; // cap
arr[0]; // error: out of bounds
arr[1] = 2; // [2, "hello", cap];

Maps are key-value collections that support all primitive types as keys, including strings, numbers, and booleans:

fr map = {
    "hello": "world",
    cap: "lie",
    1+2: 3
}

map[cap]; // "lie"
map[3]; // 3
map[noCap] = "truth"; // {"hello": "world", cap: "lie", 3: 3, noCap: "truth"}

Functions

Functions in noCap are declared using the cook keyword. You can return values from functions using yeet. If no explicit return is provided, the last statement is returned by default.

cook greet(name) {
  yeet "Hello, " + name + "!"; 
}

cook calculateAge(birthYear) {
  2025 - birthYear;  // Implicit return
}

greet("Alice"); // Hello Alice!
calculateAge(2000); // 25

noCap functions are first-class citizens, which means they can be assigned to variables and passed around as arguments to other functions. This enables powerful patterns like closures and currying.

fr createDiscountCalculator = cook(discountRate) {
  yeet cook(price) {
    fr discount = price * (discountRate / 100);
    yeet price - discount;
  }
}

fr studentDiscount = createDiscountCalculator(15);

studentDiscount(100);  // 85

Builtins

Apart from the user defined functions, noCap comes with a few built in functions.

The most common one you’ll see is caughtIn4K, which prints anything to the console:

caughtIn4K("Hello world!");
// You can also pass multiple items
// which will be printed on separate lines
caughtIn4K(cap, 1, "yo");

Next is count which is used to get the count of characters in a string or items in array or keys in a map:

count("len"); // 3
count([1,2]); // 2
count({ cap: "lie" }); // 1

spread generates arrays from strings or number ranges:

spread("cap"); // -> ["c", "a", "p"]
spread(1,4); // -> [1, 2, 3, 4]

And lastly slide lets you push items to an array:

fr items = [1,2,3,4];

items = slide(items, 5); // [1,2,3,4,5]

Logical Operators

noCap supports all the standard logical operators, which can be used to produce boolean output.

5 >= 6; // cap
6 > 5; // noCap
5 is 6; // cap
5 aint 6; // noCap
nah (5 >= 6); // noCap

You can also combine multiple logical operators using and / or:

cap is noCap or 7 >= 6; // noCap
5 is 5 and 2 aint 6; // noCap

Lastly all values other than ghosted and cap are considered truthy in logical contexts:

2 or cap; // noCap
nah 12; // cap
ghosted and 12; // cap
"" and 0; // noCap

Control Flow

noCap allows you to perform actions if some boolean conditions are met using vibe and nvm:

fr isLoggedIn = cap;

vibe(isLoggedIn is cap) {
    yeet "Please log in";
} nvm {
    yeet "Welcome back!";
}

For multiple conditions you can use unless

fr score = 68;

vibe(score > 70) {
    yeet "great score";   
} unless(score > 50) {
    yeet "not bad";
} nvm {
    yeet "no";
}

Loops

Lastly noCap also has loops which allow you to iterate over arrays or maps using the stalk in syntax.

fr sum = 0;

stalk (i in spread(1,4)) {
    sum = sum + i;
}

fr scores = {"Alice": 90, "Bob": 85, "Carol": 95};

stalk(person in scores) {
    caughtIn4K(person + " scored " + scores[person]);
}

For loops that run until a condition is true, use onRepeat:

fr countdown = 5;

onRepeat(countdown > 0) {
    caughtIn4K("T-minus " + countdown);
    countdown = countdown - 1;
}

And you can also skip entries in a loop using pass and break out of a loop using bounce

fr numbers = [12, -5, 8, 0, 15];

stalk(num in numbers) {
    vibe(num < 0) {
        pass;  // Skip negative numbers
    }
    
    vibe(num is 0) {
        bounce;  // Stop when we hit zero
    }
    
    caughtIn4K("Processing: " + num);
}