article thumbnail
Julia Crash Course
Write Like Python, Run Like C—Welcome to the Future
16 min read
#

Master Julia: The Language That Solves the Two-Language Problem

Have you ever prototyped a solution in Python only to rewrite the performance-critical parts in C? You're not alone. For decades, developers have been trapped in what's known as the "two-language problem" - forced to choose between productivity and performance. Julia changes that equation entirely.

Created in 2012 by Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and Alan Edelman, Julia was born from a simple but ambitious goal: combine the speed of C with the expressiveness of Python. What seemed impossible became reality when Julia 1.0 launched in 2018. Today, organizations like NASA, the Federal Reserve, and major pharmaceutical companies rely on Julia for mission-critical applications where both speed and developer productivity matter.

This isn't just another programming language tutorial. This is your fast-track guide to productive Julia development, whether you're coming from Python, MATLAB, R, or any other language. Let's dive in.

Why Julia Matters

Julia solves real problems. Here's what makes it unique:

Blazing Fast: Julia's just-in-time (JIT) compilation delivers C and Fortran-like performance without the complexity. We're talking genuine speed, not "fast for a scripting language."

Genuinely Dynamic: Julia feels like Python when you're writing it but runs like compiled C. You get the best of both worlds without compromise.

Multiple Dispatch: Instead of object-oriented complexity, Julia uses multiple dispatch - an elegant approach to function overloading that makes your code cleaner and more extensible.

Built for Mathematics: Native support for mathematical notation and operations means your code can look like the equations in your textbook.

Rich Ecosystem: Over 9,000 registered packages cover everything from machine learning to web development.

Parallel by Design: Built-in support for parallel and distributed computing means scaling up doesn't mean starting over.

Getting Started with Julia

Installation

Julia is self-contained and doesn't require external dependencies. Let's get it installed.

Check if Julia is already installed:

julia --version

Windows: Download the installer from julialang.org/downloads and run it. Make sure to add Julia to your PATH during installation.

Alternatively, if you use Chocolatey:

choco install julia

macOS (using Homebrew):

brew install julia

Linux (using juliaup - recommended):

curl -fsSL https://install.julialang.org | sh

Manual Installation: Download from julialang.org/downloads, extract the archive, and add the bin directory to your PATH.

Verify installation:

julia --version

You should see something like "julia version 1.10.x"

The Julia REPL: Your New Best Friend

Julia comes with an excellent Read-Eval-Print Loop (REPL). Start it by typing:

julia

This opens an interactive environment perfect for experimentation. Press Ctrl+D or type exit() to quit.

Your First Julia Script

Let's start simple. Create a file called hello.jl:

println("Hello, Julia!")

Run it:

julia hello.jl

Notice the similarity to Python? That's intentional. Julia is designed to be intuitive for beginners while providing advanced features for experts.

Core Concepts

Comments and Documentation

Julia supports three comment styles:

# Single-line comment

#=
Multi-line comment
Perfect for longer explanations
or temporarily disabling code blocks
=#

"""
Documentation string (docstring)
Used to document functions, types, and modules
Supports Markdown formatting!
"""
function greet(name)
    return "Hello, $name!"
end

Fundamental Syntax

Semicolons are Optional

Semicolons suppress output in the REPL but are optional in scripts:

x = 5        # Output shown in REPL
y = 10;      # Output suppressed in REPL

Everything is an Expression

Almost everything in Julia returns a value, which leads to elegant code:

result = if x > 0
    "positive"
else
    "non-positive"
end

println(result)

Arrays Start at 1

Unlike Python or C (0-indexed), Julia arrays start at 1. This matches mathematical convention and makes certain algorithms more intuitive:

arr = [10, 20, 30]
println(arr[1])      # 10 (first element)
println(arr[end])    # 30 (last element)

Unicode Support

Julia fully supports Unicode, including in variable names. Type \alpha and press TAB in the REPL or supported editors:

# Greek letters for mathematical clarity
alpha = 0.05
theta = 3.14159/4
delta_x = 0.001

Variables and Types

Julia is dynamically typed but can use type annotations for clarity and performance. This gives you flexibility during development and optimization when needed.

Basic Types

# Integers
x = 42                    # Int64 on 64-bit systems
small = Int8(10)          # 8-bit integer
big = BigInt(10)^100      # Arbitrary precision

# Floating point
y = 3.14                  # Float64
precise = BigFloat(3.14)  # Arbitrary precision float

# Boolean
is_true = true
is_false = false

# Strings
name = "Julia"
char = 'J'                # Single character (not a string!)

# Symbols (like Ruby symbols or Lisp atoms)
sym = :my_symbol

# Nothing (like None in Python or null elsewhere)
result = nothing

# Type checking
println(typeof(x))        # Int64
println(typeof(y))        # Float64
println(typeof(name))     # String

Type Annotations for Performance

When performance matters, type annotations help the compiler generate faster code:

# Variable with type annotation
age::Int64 = 30

# Function with type annotations
function add(x::Int, y::Int)::Int
    return x + y
end

# This works
println(add(5, 3))        # 8

# This would error: MethodError
# println(add(5.0, 3.0))  # Expects integers, not floats

Type Conversion

Julia makes type conversion straightforward:

# Explicit conversion
x = Int64(3.14)           # 3 (truncates)
y = Float64(10)           # 10.0
s = string(42)            # "42"
n = parse(Int, "123")     # 123

# Checking types
println(isa(42, Int))     # true
println(42 isa Int)       # true (alternative syntax)

Working with Strings

Julia's string handling is powerful and intuitive. String interpolation alone will make you wonder how you lived without it.

String Basics

# String basics
name = "Julia"
version = "1.10"

# String interpolation with $
greeting = "Hello, $name version $version!"
println(greeting)  # Hello, Julia version 1.10!

# Expression interpolation
x = 5
y = 3
println("$x + $y = $(x + y)")  # 5 + 3 = 8

# Multi-line strings
multiline = """
    This is a
    multi-line
    string
    """

# String concatenation
full_name = "Julia" * " " * "Lang"     # Using *
full_name2 = string("Julia", " ", "Lang")  # Using string()

# Raw strings (no escaping or interpolation)
path = raw"C:\Users\Name\Documents"

String Operations

text = "Julia Programming"

# Common operations
println(length(text))              # 17
println(lowercase(text))           # julia programming
println(uppercase(text))           # JULIA PROGRAMMING
println(occursin("Program", text)) # true (substring check)
println(startswith(text, "Julia")) # true
println(endswith(text, "ing"))     # true

# Replace
println(replace(text, "Julia" => "Python"))  # Python Programming

# Splitting and joining
words = split(text)                # ["Julia", "Programming"]
println(join(words, "-"))          # Julia-Programming

# Trimming whitespace
padded = "  spaces  "
println(strip(padded))             # "spaces"

# String indexing and slicing
println(text[1])                   # 'J' (character, not string!)
println(text[1:5])                 # "Julia" (substring)
println(text[end-10:end])          # "Programming"

# Character iteration
for char in "Julia"
    println(char)
end

Collections: Arrays, Tuples, and More

Julia's collection types are designed for both convenience and performance.

Arrays (Vectors and Matrices)

Arrays are Julia's workhorse data structure:

# Creating arrays
empty_arr = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, true]      # Heterogeneous array
typed = Int64[1, 2, 3, 4, 5]       # Type-specific array

# Array creation functions
zeros_arr = zeros(5)               # [0.0, 0.0, 0.0, 0.0, 0.0]
ones_arr = ones(3)                 # [1.0, 1.0, 1.0]
range_arr = collect(1:10)          # [1, 2, 3, ..., 10]
range_step = collect(0:2:10)       # [0, 2, 4, 6, 8, 10]

# Accessing elements
println(numbers[1])                # 1 (first element)
println(numbers[end])              # 5 (last element)
println(numbers[2:4])              # [2, 3, 4] (slice)

# Modifying arrays
numbers[1] = 10
println(numbers)                   # [10, 2, 3, 4, 5]

# Adding elements
push!(numbers, 6)                  # Append to end
println(numbers)                   # [10, 2, 3, 4, 5, 6]

pushfirst!(numbers, 0)             # Prepend to beginning
println(numbers)                   # [0, 10, 2, 3, 4, 5, 6]

# Removing elements
pop!(numbers)                      # Remove and return last element
popfirst!(numbers)                 # Remove and return first element

# Array operations
println(length(numbers))           # Array length
println(sum(numbers))              # Sum of elements
println(maximum(numbers))          # Maximum value
println(minimum(numbers))          # Minimum value

Array Comprehensions: Elegant and Fast

Array comprehensions are one of Julia's most powerful features:

# Basic comprehension
squares = [x^2 for x in 1:10]
println(squares)                   # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# Conditional comprehension
evens = [x for x in 1:20 if x % 2 == 0]
println(evens)                     # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# Broadcasting (apply operation to all elements)
arr = [1, 2, 3, 4, 5]
doubled = arr .* 2                 # [2, 4, 6, 8, 10]
println(doubled)

# Element-wise operations with the dot operator
a = [1, 2, 3]
b = [4, 5, 6]
println(a .+ b)                    # [5, 7, 9]
println(a .* b)                    # [4, 10, 18]

Matrices (2D Arrays)

Julia handles matrices with grace:

# Creating matrices
matrix = [1 2 3; 4 5 6; 7 8 9]     # 3x3 matrix (semicolons separate rows)
println(matrix)

# Matrix creation functions
zero_matrix = zeros(3, 3)          # 3x3 matrix of zeros
identity = I(3)                    # 3x3 identity matrix (requires LinearAlgebra)
random_matrix = rand(3, 3)         # 3x3 matrix of random values

# Accessing matrix elements
println(matrix[1, 2])              # 2 (row 1, column 2)
println(matrix[1, :])              # [1, 2, 3] (entire first row)
println(matrix[:, 2])              # [2, 5, 8] (entire second column)

# Matrix operations
A = [1 2; 3 4]
B = [5 6; 7 8]

println(A + B)                     # Matrix addition
println(A * B)                     # Matrix multiplication
println(A .* B)                    # Element-wise multiplication
println(A')                        # Transpose

Tuples: Immutable Sequences

Tuples provide fast, immutable sequences:

# Creating tuples
point = (3, 4)
person = ("Alice", 30, "Engineer")

# Accessing elements
println(point[1])                  # 3
println(person[1])                 # "Alice"

# Unpacking
x, y = point
println("x=$x, y=$y")              # x=3, y=4

name, age, job = person
println("$name is a $age-year-old $job")

# Named tuples (incredibly useful)
contact = (name="Alice", email="alice@example.com", age=30)
println(contact.name)              # Alice
println(contact.email)             # alice@example.com

Dictionaries: Key-Value Magic

Dictionaries provide fast lookups and flexible data storage:

# Creating dictionaries
empty_dict = Dict()
ages = Dict("Alice" => 30, "Bob" => 25, "Charlie" => 35)

# Accessing values
println(ages["Alice"])             # 30

# Adding/updating
ages["Diana"] = 28
ages["Alice"] = 31                 # Update existing

# Checking keys
println(haskey(ages, "Alice"))     # true
println(haskey(ages, "Eve"))       # false

# Getting with default
println(get(ages, "Alice", 0))     # 31
println(get(ages, "Eve", 0))       # 0 (default)

# Iterating
for (name, age) in ages
    println("$name is $age years old")
end

# Keys and values
println(keys(ages))                # Key iterator
println(values(ages))              # Value iterator
println(collect(keys(ages)))       # Convert to array

# Deleting
delete!(ages, "Bob")
println(ages)

Sets

Sets provide unique collections with fast membership testing:

# Creating sets
empty_set = Set()
numbers = Set([1, 2, 3, 4, 5])
unique_nums = Set([1, 1, 2, 2, 3])  # Duplicates removed: Set([1, 2, 3])

# Set operations
set1 = Set([1, 2, 3, 4])
set2 = Set([3, 4, 5, 6])

println(union(set1, set2))         # Set([1, 2, 3, 4, 5, 6])
println(intersect(set1, set2))     # Set([3, 4])
println(setdiff(set1, set2))       # Set([1, 2])

# Membership
println(3 in numbers)              # true
println(10 in numbers)             # false

# Adding/removing
push!(numbers, 6)
delete!(numbers, 2)

Control Flow

Conditionals

Julia's conditional syntax is clean and expressive:

age = 25

# Basic if-else
if age < 18
    println("Minor")
elseif age < 65
    println("Adult")
else
    println("Senior")
end

# If as expression (returns value)
status = if age >= 18
    "Adult"
else
    "Minor"
end
println(status)

# Ternary operator
category = age >= 18 ? "Adult" : "Minor"
println(category)

# Short-circuit evaluation
x = 10
x > 5 && println("x is greater than 5")    # Prints if true
x < 5 || println("x is not less than 5")   # Prints if first is false

Important Note on Truthiness

Julia's approach to truthiness differs from Python:

# Only false and nothing are falsy
# Everything else (including 0, empty string, empty array) is truthy!
if 0
    println("Zero is truthy in Julia!")    # This WILL print!
end

# To check for empty collections
arr = []
if isempty(arr)
    println("Array is empty")
end

Comparisons

# Comparing values
x = 5
y = 10
println(x == y)                    # false (equality)
println(x != y)                    # true (inequality)
println(x < y)                     # true
println(x >= y)                    # false

# Chained comparisons (reads like mathematical notation!)
println(1 < 2 < 3)                 # true
println(1 < 2 > 1.5)               # true

Loops: Iteration Made Easy

For Loops

# For loop with range
for i in 1:5
    println("Count: $i")
end

# For loop with array
fruits = ["apple", "banana", "cherry"]
for fruit in fruits
    println(uppercase(fruit))
end

# For loop with enumerate (get index and value)
for (index, fruit) in enumerate(fruits)
    println("$index: $fruit")
end

# For loop with dictionary
ages = Dict("Alice" => 30, "Bob" => 25)
for (name, age) in ages
    println("$name is $age years old")
end

# Nested loops (single line syntax)
for i in 1:3, j in 1:3
    println("i=$i, j=$j")
end

While Loops

# While loop
count = 0
while count < 5
    println("Count: $count")
    count += 1
end

# Break and continue
for i in 1:10
    if i == 3
        continue  # Skip to next iteration
    end
    if i == 8
        break     # Exit loop
    end
    println(i)
end

List Comprehensions Redux

Comprehensions aren't just syntactic sugar - they're often faster than explicit loops:

# List comprehensions
squares = [x^2 for x in 1:10]
println(squares)

# Conditional comprehension
evens = [x for x in 1:20 if x % 2 == 0]
println(evens)

# Nested comprehension
matrix = [i*j for i in 1:5, j in 1:5]
println(matrix)

Functions: First-Class Citizens

Functions are central to Julia's design. They're first-class citizens that can be passed around, returned from other functions, and stored in data structures.

Basic Functions

# Standard function definition
function greet(name)
    return "Hello, $name!"
end

println(greet("Julia"))

# Implicit return (last expression is returned)
function add(x, y)
    x + y
end

println(add(5, 3))  # 8

# Compact assignment form
square(x) = x^2
println(square(5))  # 25

# Multiple return values (returns tuple)
function minmax(a, b)
    if a < b
        return a, b
    else
        return b, a
    end
end

min_val, max_val = minmax(10, 5)
println("Min: $min_val, Max: $max_val")

Optional and Keyword Arguments

# Optional arguments (with defaults)
function power(base, exponent=2)
    return base^exponent
end

println(power(5))      # 25 (5^2)
println(power(5, 3))   # 125 (5^3)

# Keyword arguments (after semicolon)
function create_user(name; age=0, email="")
    println("Name: $name")
    println("Age: $age")
    println("Email: $email")
end

create_user("Alice", age=30, email="alice@example.com")
create_user("Bob")  # Uses defaults for age and email

# Mixing positional and keyword arguments
function process(x, y; verbose=false, precision=2)
    result = x + y
    if verbose
        println("Adding $x and $y")
    end
    return round(result, digits=precision)
end

println(process(3.14159, 2.71828, verbose=true, precision=3))

Variable Arguments

# Splat operator ... for variable arguments
function sum_all(numbers...)
    total = 0
    for n in numbers
        total += n
    end
    return total
end

println(sum_all(1, 2, 3, 4, 5))  # 15

# Spreading arrays
numbers = [1, 2, 3, 4, 5]
println(sum_all(numbers...))     # 15 (spread array into arguments)

Anonymous Functions

# Lambda-style anonymous function
double = x -> x * 2
println(double(5))  # 10

# Anonymous function with multiple arguments
add = (x, y) -> x + y
println(add(3, 4))  # 7

# Long-form anonymous function
greet = function(name)
    return "Hello, $name!"
end
println(greet("World"))

# Using with higher-order functions
numbers = [1, 2, 3, 4, 5]
squared = map(x -> x^2, numbers)
println(squared)  # [1, 4, 9, 16, 25]

evens = filter(x -> x % 2 == 0, numbers)
println(evens)  # [2, 4]

Multiple Dispatch: Julia's Superpower

Here's where Julia truly shines. Multiple dispatch lets you define functions with the same name but different type signatures, and Julia automatically calls the right one based on the types of all arguments:

# Define function for integers
function process(x::Int)
    println("Processing integer: $x")
    return x * 2
end

# Define function for strings
function process(x::String)
    println("Processing string: $x")
    return uppercase(x)
end

# Define function for arrays
function process(x::Array)
    println("Processing array of length $(length(x))")
    return sum(x)
end

# Julia automatically dispatches to the correct method
println(process(5))           # Calls Int version
println(process("hello"))     # Calls String version
println(process([1, 2, 3]))   # Calls Array version

# You can see all methods
methods(process)

Multiple dispatch eliminates the need for complex type checking and leads to cleaner, more maintainable code. It's one of the key reasons Julia code is both fast and elegant.

File I/O

Julia makes file operations straightforward and safe:

# Reading entire file as string
content = read("example.txt", String)
println(content)

# Reading file line by line
lines = readlines("example.txt")
for line in lines
    println(line)
end

# Reading with open (auto-closes)
open("example.txt") do file
    for line in eachline(file)
        println(line)
    end
end

# Writing to file (overwrites)
open("output.txt", "w") do file
    write(file, "This is line 1\n")
    write(file, "This is line 2\n")
end

# Appending to file
open("output.txt", "a") do file
    write(file, "This is appended\n")
end

# Using println to write
open("output.txt", "w") do file
    println(file, "Line 1")
    println(file, "Line 2")
    println(file, "Line 3")
end

# Check file existence
if isfile("example.txt")
    println("File exists")
    println("File size: $(filesize("example.txt")) bytes")
end

# Working with directories
if isdir("mydir")
    println("Directory exists")
else
    mkdir("mydir")  # Create directory
end

# List files in directory
for file in readdir(".")
    println(file)
end

# List files with full path
for file in readdir(".", join=true)
    if isfile(file)
        println("File: $file")
    end
end

Error Handling

Julia uses try-catch-finally for exception handling, similar to Python and other modern languages:

# Basic try-catch
try
    result = 10 / 0
catch e
    println("Error occurred: $e")
finally
    println("This always executes")
end

# Catching specific exceptions
try
    x = parse(Int, "not a number")
catch e
    if isa(e, ArgumentError)
        println("Caught ArgumentError: $(e.msg)")
    else
        println("Caught unexpected error: $e")
        rethrow()  # Re-throw if unexpected
    end
end

# Multiple catch blocks (using if-elseif)
try
    open("nonexistent.txt")
catch e
    if isa(e, SystemError)
        println("System error: File not found")
    elseif isa(e, ArgumentError)
        println("Argument error occurred")
    else
        println("Unknown error: $e")
    end
end

# Throwing exceptions
function validate_age(age)
    if age < 0
        throw(ArgumentError("Age cannot be negative"))
    end
    if age > 150
        throw(ArgumentError("Age seems unrealistic"))
    end
    return true
end

try
    validate_age(-5)
catch e
    println("Validation failed: $(e.msg)")
end

# Custom exceptions
struct ValidationError <: Exception
    msg::String
end

function check_positive(x)
    if x <= 0
        throw(ValidationError("Value must be positive, got $x"))
    end
    return x
end

try
    check_positive(-10)
catch e
    if isa(e, ValidationError)
        println("Validation error: $(e.msg)")
    end
end

# Error function (shorthand for throw)
function divide(a, b)
    b == 0 && error("Division by zero!")
    return a / b
end

# Using @assert macro
function process_data(data)
    @assert !isempty(data) "Data cannot be empty"
    @assert all(x -> x > 0, data) "All values must be positive"
    return sum(data)
end

Structs and Custom Types

Julia's type system allows you to create custom data structures that are both expressive and performant:

# Simple struct (immutable by default)
struct Point
    x::Float64
    y::Float64
end

p = Point(3.0, 4.0)
println("Point: ($(p.x), $(p.y))")

# Mutable struct
mutable struct Person
    name::String
    age::Int
    email::String
end

alice = Person("Alice", 30, "alice@example.com")
println(alice.name)

# Modify mutable struct
alice.age = 31
println(alice.age)

# Struct with constructor
struct Rectangle
    width::Float64
    height::Float64

    # Inner constructor for validation
    function Rectangle(width, height)
        if width <= 0 || height <= 0
            error("Dimensions must be positive")
        end
        new(width, height)
    end
end

rect = Rectangle(10.0, 5.0)

# Outer constructor (define outside struct)
Rectangle(side::Float64) = Rectangle(side, side)  # Square
square = Rectangle(5.0)

# Methods for structs
area(r::Rectangle) = r.width * r.height
perimeter(r::Rectangle) = 2 * (r.width + r.height)

println("Area: $(area(rect))")
println("Perimeter: $(perimeter(rect))")

Parametric Types (Generics)

# Parametric types (generic types)
struct Pair{T}
    first::T
    second::T
end

int_pair = Pair{Int}(1, 2)
str_pair = Pair{String}("hello", "world")

println(int_pair.first)
println(str_pair.first)

Abstract Types and Inheritance

# Abstract types for hierarchy
abstract type Animal end

struct Dog <: Animal
    name::String
    breed::String
end

struct Cat <: Animal
    name::String
    color::String
end

# Methods for abstract types
make_sound(d::Dog) = println("$(d.name) says: Woof!")
make_sound(c::Cat) = println("$(c.name) says: Meow!")

dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Tabby")

make_sound(dog)
make_sound(cat)

Modules and Packages

Creating Modules

Modules help organize your code:

# Define a module
module MyMath
    export add, multiply  # Export functions to make them available

    function add(x, y)
        return x + y
    end

    function multiply(x, y)
        return x * y
    end

    # Not exported, but still accessible with MyMath.subtract
    function subtract(x, y)
        return x - y
    end
end

# Using the module
using .MyMath  # The dot indicates local module

println(add(5, 3))              # 8 (exported, directly accessible)
println(multiply(4, 2))         # 8 (exported)
println(MyMath.subtract(10, 3)) # 7 (not exported, need prefix)

# Alternative: import specific names
import .MyMath: add

# Or import all
import .MyMath

Using Packages

Julia has a built-in package manager (Pkg) that makes dependency management painless:

# Enter package mode in REPL by pressing ]
# Or use Pkg programmatically

using Pkg

# Install packages
Pkg.add("DataFrames")
Pkg.add("Plots")
Pkg.add("HTTP")

# Update packages
Pkg.update()

# Remove package
Pkg.rm("PackageName")

# Using installed packages
using DataFrames
using Plots
using HTTP

# Create a DataFrame
df = DataFrame(
    name = ["Alice", "Bob", "Charlie"],
    age = [30, 25, 35],
    city = ["NYC", "LA", "Chicago"]
)
println(df)

# Make a simple plot
x = 1:10
y = x.^2
plot(x, y, title="Square Function", xlabel="x", ylabel="y")

Essential Packages

Data Science & Analysis:

Plotting & Visualization:

Machine Learning:

Web & Network:

Database:

Database Access

Julia provides excellent database connectivity through various packages. Here's how to work with databases:

Using JDBC for Universal Database Access

using JDBC

# Initialize JDBC (first time)
JDBC.usedriver("path/to/jdbc-driver.jar")

# Connect to database
conn = JDBC.connect(
    "jdbc:mysql://localhost:3306/mydb",
    "username",
    "password"
)

try
    # Simple query
    stmt = JDBC.createStatement(conn)
    rs = JDBC.executeQuery(stmt, "SELECT * FROM users WHERE age > 25")

    while JDBC.next(rs)
        id = JDBC.getInt(rs, "id")
        name = JDBC.getString(rs, "name")
        age = JDBC.getInt(rs, "age")
        println("ID: $id, Name: $name, Age: $age")
    end

    JDBC.close(rs)
    JDBC.close(stmt)

    # Prepared statement (prevents SQL injection)
    prep = JDBC.prepareStatement(conn,
        "INSERT INTO users (name, email, age) VALUES (?, ?, ?)")

    JDBC.setString(prep, 1, "Alice Smith")
    JDBC.setString(prep, 2, "alice@example.com")
    JDBC.setInt(prep, 3, 30)

    rows = JDBC.executeUpdate(prep)
    println("Inserted $rows row(s)")

    JDBC.close(prep)

    # Transaction
    JDBC.setAutoCommit(conn, false)

    try
        stmt1 = JDBC.createStatement(conn)
        JDBC.executeUpdate(stmt1,
            "UPDATE accounts SET balance = balance - 100 WHERE user_id = 1")
        JDBC.executeUpdate(stmt1,
            "UPDATE accounts SET balance = balance + 100 WHERE user_id = 2")

        JDBC.commit(conn)
        println("Transaction committed")
        JDBC.close(stmt1)
    catch e
        JDBC.rollback(conn)
        println("Transaction rolled back: $e")
    finally
        JDBC.setAutoCommit(conn, true)
    end

finally
    JDBC.close(conn)
end

Using SQLite for Local Databases

using SQLite
using DataFrames

# Create/open database
db = SQLite.DB("mydata.db")

# Create table
SQLite.execute(db, """
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE,
        age INTEGER
    )
""")

# Insert data
SQLite.execute(db,
    "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
    ["Alice", "alice@example.com", 30])

# Query into DataFrame
df = DataFrame(SQLite.DBInterface.execute(db,
    "SELECT * FROM users WHERE age > 25"))
println(df)

# Close database
SQLite.close(db)

Using MySQL (Native Driver)

using MySQL

# Connect to MySQL
conn = MySQL.connect(
    "localhost",
    "username",
    "password",
    db="mydb"
)

try
    # Query
    result = MySQL.query(conn, "SELECT * FROM users")

    for row in result
        println("Name: $(row[:name]), Age: $(row[:age])")
    end

    # Insert with parameters
    MySQL.execute(conn,
        "INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
        ["Bob", "bob@example.com", 25])

finally
    MySQL.disconnect(conn)
end

Julia in WaSQL

WaSQL provides robust support for Julia, allowing you to leverage Julia's high-performance numerical computing and elegant syntax within your database-driven web applications. You can use Julia in page controllers, functions, and standalone scripts alongside PHP, Python, Groovy, and other supported languages.

Julia's blazing-fast performance makes it ideal for computationally intensive tasks in web applications: data analysis, scientific calculations, machine learning inference, and complex business logic. The combination of WaSQL's database-first architecture with Julia's speed creates a powerful environment for building high-performance data-driven applications.

Key Benefits in WaSQL:

Example WaSQL Usage:

# In a WaSQL page controller field
using JSON3
using Statistics

# Process data from database
function analyzeUserMetrics(user_data)
    ages = [user["age"] for user in user_data]

    metrics = Dict(
        "count" => length(ages),
        "mean_age" => mean(ages),
        "median_age" => median(ages),
        "std_dev" => std(ages),
        "min_age" => minimum(ages),
        "max_age" => maximum(ages)
    )

    return JSON3.write(metrics)
end

# This can be called from PHP or accessed via AJAX
println(analyzeUserMetrics(user_data))

Best Practices

Following these practices will help you write better Julia code:

  1. Use type annotations for function arguments when performance matters
  2. Avoid global variables in performance-critical code (use functions)
  3. Use the exclamation mark suffix for functions that modify arguments (e.g., push!, sort!)
  4. Leverage multiple dispatch instead of if-else chains for type handling
  5. Use broadcasting with the dot operator for element-wise operations
  6. Prefer comprehensions over loops for creating arrays
  7. Use @time macro to profile code performance
  8. Write tests using the built-in Test module
  9. Use meaningful variable names (Unicode is okay for math symbols)
  10. Keep functions small and focused on a single task

Common Gotchas

Watch out for these Julia quirks:

Performance Tips

Julia is fast by default, but these tips will help you maximize performance:

# Use @time to measure performance
@time begin
    result = sum([i^2 for i in 1:1000000])
end

# Use @benchmark for detailed benchmarking (requires BenchmarkTools)
using BenchmarkTools
@benchmark sum([i^2 for i in 1:1000000])

# Avoid global variables in loops (slow)
# Bad:
x = 0
for i in 1:1000000
    x += i
end

# Good:
function sum_numbers(n)
    x = 0
    for i in 1:n
        x += i
    end
    return x
end
result = sum_numbers(1000000)

# Use in-place operations to reduce allocations
a = rand(1000)
b = rand(1000)

# Allocates new array
c = a .+ b

# In-place (faster, no allocation)
c = similar(a)
c .= a .+ b

# Or use map!
map!(+, c, a, b)

Your Julia Journey Starts Now

Congratulations on completing this Julia crash course! You now have a solid foundation to build upon. Julia is a language that rewards exploration and experimentation, so the best next step is to start writing code.

Next Steps

  1. Practice: Build a project--data analysis script, web API, or scientific simulation
  2. Official Documentation: docs.julialang.org
  3. JuliaAcademy: Free courses at juliaacademy.com
  4. Think Julia: Free book at benlauwens.github.io/ThinkJulia.jl
  5. Julia Discourse: Join the community at discourse.julialang.org
  6. Explore packages: Browse juliahub.com and juliapackages.com

Project Ideas to Get You Started

Remember, Julia is designed to be easy to learn but powerful to use. The best way to master it is by writing code, making mistakes, and experimenting. Julia's excellent error messages will guide you along the way.

The two-language problem is solved. Now go build something amazing.

Happy Julia coding!