Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Hi, im quantinium

This is my notes website.

Backend

Understanding HTTP

HTTP is an application layer protocol designed to transfer information between client and servers. The protocol operates on a simple exchange i.e. a client sends a request describing the action on resources and the userver returns a response with the outcome. The resource is identified using Uniform Resource Locator(URL) and the action is the specified by an HTTP method.

  • HTTP follows a client-server model i.e. a client sends a request to a server, and the server sends back a response.
  • HTTP is stateless by design. Each request is self-contained and the server does not retain any memory of previous request by the same client. This allows HTTP to scale horizontally as any server behind a load balancer handles any request without needing shared state.

HTTP Message

An HTTP messages are the mechanism used to exchange data between a server and a client in HTTP protocol. There are two types of messages:

  • Request: sent by client to trigger an action on server
  • Response: answer sent by server in response to request.

HTTP Message Format

An HTTP message has the following format:

  • A start line that describes the HTTP version along with the request method or the outcome of the request
  • An optional set of HTTP headers containing metadata that describe the message
  • An empty line indicating the metadata of message is complete
  • An optional body containing data associated with the message.

The start line and headers of the HTTP message are collectively known as the head of the requests, and the part afterwards that contain its content is known as the body.

HTTP request

A HTTP request looks like

POST /users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49

name=FirstName+LastName&email=bsmth%40example.com

Request Line

the start line in HTTP/1.x requests is called request-line and is made up of three parts

<method> <request-target> <protocol>
  • Method: The HTTP method is one of the methods that define what the request must do.
  • Request target: It is ussually an absolute or relative URL and helps to identify the resource.
  • Protocol: This declares the HTTP version version used, which defines the structure of the remaining message, acting as an indicator of the expected version to use for the response.

Request Headers

Headers are metadata sent with a request after the start line and before the body. The Host header is the only required header in HTTP/1.1 and in HTTP/1.x, each header is a case-insensitive.

Request Body

The request body is the part of a request that carries information to the server. Only PATCH, POST and PUT requests have a body. The body can be of various formats such as text, json, etc whatever the server expects.

HTTP Response

Responses are the HTTP messages a server sends back in reply to a request. The response lets the client know what the outcome of the request was.

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://example.com/users/123

{
  "message": "New user created",
  "user": {
    "id": 123,
    "firstName": "Example",
    "lastName": "Person",
    "email": "bsmth@example.com"
  }
}

Request Line

The start line is called a status-line in response and has three parts:

<protocol> <status-code> <reason-phrase>
  • Protocol: The HTTP version of the message
  • Status Code: The numeric status code that indicates the status of request such as 200 being success.
  • Reason Phrase: The optional text after the status code is a brief, purely informational, text description of the status to help a human understand the outcome of a request

Response Headers

Response headers are the metadata sent with a response. In HTTP/1.x, each header is a case-insensitive string followed by a colon (:) and a value whose format depends upon which header is used. There are two types of headers:

  • Response Headers: It give additional context about the message or add extra logic to how the client should make subsequent requests
  • Representation Headers: if the message has a body, they describe the form of the message data and any encoding applied such as text/html, application/json, etc. This allows a recipient to understand how to reconstruct the resources as it was before it was transmitted over the network.

Response Body

Request Body is included in most messages when responding to a client that tells the recipient about the action performed on the server by the request such as GET request has the data in the response body.

HTTP Methods

The method in the request line tell the server which action to perform on the resource. HTTP defines the following methods:

MethodPurposeRequest has payload bodyResponse has payload bodySafeIdempotentCacheable
GETRetrieve a resourceOptionalYesYesYesYes
HEADRetrieve headers only (no body)OptionalNoYesYesYes
POSTSubmit data for processing such as adding entry in databaseYesYesNoNoYes*
PUTReplace a resource entirelyYesYesNoYesNo
DELETERemove a resourceOptionalYesNoYesNo
CONNECTEstablish a TCP tunnel through an HTTP proxyOptionalYesNoNoNo
OPTIONSAsk server what methods it supportsOptionalYesYesYesNo
TRACEMake server echo back the sent request for detecting if data was modified while being sentNoYesYesYesNo
PATCHPartially update a resourceYesYesNoNoNo

NOTE: POST is technically cacheable per the HTTP spec, but almost never cached in practice. RFC 9110 says a POST response can be cached if the server explicitly includes the right headers, specifically Cache-Control or Expires, along with a Content-Location header that matches the request URI.

Status Code

The status code in the response indicates the outcome of the request. Status codes are grouped into five classes by their first digit

ClassRangeMeaning
1xx100–199Informational, request received, processing continues
2xx200–299Success, request accepted and processed
3xx300–399Redirection, further action needed
4xx400–499Client error, malformed or unauthorized request
5xx500–599Server error, valid request, server failed

Note: This note is for 418 I'm a teapot enjoyers. Click for #cat

HTTP Versions

HTTP has gone through many iteration over the years such as:

HTTP/0.9 (1991)

The original, extremely primitive version. It had only one method, GET and no headers, no status codes, no metadata of any kind. You requested a path, and the server sent back raw HTML and closed the connection.

# Request
GET /page.html

# Response
<html>hello world</html>

HTTP/1.0 (1996)

This was the real first version of HTTP.

  • Added Headers: It added headers for both request and response
  • Status Codes: Added status codes
  • Methods: Added more methods to the spec make the total methods 3 (GET, POST and HEAD)
  • Content Type: Added support for more content type than HTML
  • HTTP versioning: Added versioning to request line

HTTP/1.1 (1997)

It added the following features

  • Persistent Connections (Keep-Alive): TCP connection stays open after a request, so multiple requests can reuse it.
  • Pipelining: Client can send multiple requests without waiting for responses but responses must come back in order. This caused a serious problem called Head-of-Line (HOL) blocking
  • Chunked Transfer Encoding: Server can stream a response in chunks without knowing the total size upfront.
  • Host Header (mandatory): Since multiple websites can share one IP address (virtual hosting), the Host header tells the server which site the client wants.
  • New Methods: Added new methods: PUT, DELETE, OPTIONS, TRACE, and CONNECT.
  • Caching improvements: Cache-Control, ETag, If-None-Match, If-Modified-Since were added.

HTTP/2 (2015)

It is a complete rewrite of how HTTP is transmitted, while keeping the same semantics (methods, headers, status codes all unchanged). Based on Google’s experimental SPDY protocol. It added the following features:

  • Binary Framing: HTTP/1.x is plain text. HTTP/2 is binary. All communication is split into small frames and tagged with a type. Faster to parse, more compact, less error-prone.
[Text HTTP/1.1]          [Binary HTTP/2]
GET /index HTTP/1.1  →   0x00 0x00 0x12 0x01 ...
Host: example.com
  • Multiplexing: Multiple requests and responses travel over a single TCP connection simultaneously, each in their own numbered stream thus solving HOL.
  • Header Compression (HPACK): HTTP/1.1 headers are repetitive plain text sent on every request. HPACK compresses them and maintains a shared table of previously seen headers on both ends, so repeated headers (like User-Agent, Host, Cookie) are sent as tiny references instead of full strings.
  • Server Push: Server can proactively send resources the client hasn’t asked for yet. For example, when a browser requests index.html, the server can push style.css and app.js immediately, anticipating the browser will need them.
  • Stream Prioritization: Clients can assign priority weights to streams so critical resources (HTML, CSS) load before less important ones (analytics scripts).

HTTP/3 (2022)

HTTP/3 replacing TCP entirely with a new transport protocol called QUIC, which runs over UDP.

  • QUIC (Quick UDP Internet Connections): Originally developed by Google, QUIC bakes in everything TCP provided (reliability, ordering, congestion control) but does it at the application layer on top of UDP.
  • 0-RTT and 1-RTT Handshakes: TCP + TLS requires 2–3 round trips before data can flow. QUIC combines the transport and TLS handshake into one, needing just 1 round trip (1-RTT). For repeat connections to a known server, QUIC can send data in the very first packet i.e. 0-RTT.
  • Connection Migration: TCP connections are tied to a 4-tuple (source IP, source port, dest IP, dest port). If you switch from Wi-Fi to mobile data, your IP changes and the connection breaks. QUIC uses a Connection ID instead, so connections survive network changes seamlessly.
  • TLS 1.3 built in: QUIC mandates TLS 1.3 encryption.

HTTP Caching

Caching is the practice of storing a copy of a response so future requests can be served from that copy instead of hitting the server again. It reduces latency, saves bandwidth and reduces server load. There are two types of caches:

  • Private Cache: It is a cache tied to a specific client, typically a browser cache. It is suitable for personalized content.
  • Shared Cache: It is located between the client and server and can store responses that can be shared among users and shared caches can be further sub-classified into proxy caches and managed caches
    • Proxy Cache: It is a cache that is operated by someone other than the website owner such as ISP, a coorporation, network admin. Since website owner doesnt control them, it can lead to cause issues such as providing stale data, caching responses that shouldnt be cached, etc. It has become ineffective due HTTPS and data being encrypted.
    • Managed Cache: Managed caches are explicitly deployed by service developers to offload the origin server and to deliver content efficiently. Examples include reverse proxies, CDNs, and service workers in combination with the Cache API.

Heuristic Caching

HTTP is designed to cache as much as possible, so even if no Cache-Control is given, responses will get stored and reused if certain conditions are met. This is called heuristic caching. For example, take the following response. This response was last updated 1 year ago.

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Last-Modified: Tue, 22 Feb 2021 22:22:22 GMT

<!doctype html>

It can be said that data that hasnt changed in a year has low prob. of change for some time. Therefore client stores this response despite lack of max-age. The spec. recommends about 10% of the time after storing. Heuristic caching is a workaround that came before Cache-Control support became widely adopted, and basically all responses should explicitly specify a Cache-Control header.

Fresh and State based on age

Stored HTTP responses have two states: fresh and stale. The fresh state usually indicates that the response is still valid and can be reused, while the stale state means that the cached response has already expired.The criterion for determining when a response is fresh and when it is stale is age. In HTTP, age is the time elapsed since the response was generated.

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800

<!doctype html>

The cache that stored the example response calculates the time elapsed since the response was generated and uses the result as the response’s age. For the example response, the meaning of max-age is the following:

  • If the age of the response is less than one week, the response is fresh.
  • If the age of the response is more than one week, the response is stale

Expires or max-age

In HTTP/1.0, freshness used to be specified by the Expires header. The Expires header specifies the lifetime of the cache using an explicit time rather than by specifying an elapsed time.

Expires: Tue, 28 Feb 2022 22:22:22 GMT

Vary

The vary header tells caches to store seperate variants of the same URL based on specific request header values. A response with Vary:Accept-Encoding instructs the cache to key stored entries on the Accept-Encoding header. A client requesting br encoding and a client requesting gzip each get their own cached copy

Vary: Accept-Encoding, Accept-Language

Without Vary, a cache serves the same stored response to all clients regardless of request headers. This causes problems when the origin returns different representations based on encoding, language, or other negotiated features.Vary: * effectively disables caching. Every request is treated as unique

Validation

When a stored response becomes stale, the cache does not discard the response but cache sends a conditional request to the origin to check whether the resource is changed. This process is called revalidation.

Validators

Two validator mechanism exist:

  • Etag: Its an opaque identifier representing a specific version of the resource.
  • Last-Modified: A timestamp indicating when the resource last changed. Has one-second resolution, making Etags the more reliable validator for rapidly changing resources.

Conditional request headers

The cache attaches validator to the revalidation request:

  • If-None-Match sends the stored ETag. If the origin has the same ETag, the response has not changed. It takes more precedence.
  • If-Modified-Since sends the stored Last-Modified date. If the resource has not changed since the data, the origin confirms freshness.

Invalidation

Unsafe methods like POST, PUT and DELETE change server state. When a cache receives a non-error-response to an unsafe request, the cache must invalidate stored responses for the target URI. The cache also invalidates response for URI’s in the Location and Content-Location headers if they share the same origin. Invalidation marks stored responses as requiring revalidation.

Cache Groups

Cache Groups provide a mechanism for grouping related cached responses so a single unsafe request invalidates an entire set of resources. The Cache-Groups response header assigns a response to one or more named groups. The value is a list of case-sensitive strings.

HTTP/1.1 200 OK
Cache-Groups: "product-listings", "homepage"

The Cache-Group-Invalidation response header triggers invalidation of all cached responses belonging to the named groups. The header is processed only on responses to unsafe methods like POST or PUT.

HTTP/1.1 200 OK
Cache-Group-Invalidation: "product-listings"

After receiving this response, a cache invalidates all stored responses tagged with the “product-listings” group from the same origin. The invalidation does not cascade: invalidated responses do not trigger further group-based invalidations.

Cache-Control directives

The Cache-Control header carries directives controlling cache storage, reuse and revalidation. Directives appear in both request and response

Response Directives

DirectiveEffect
max-age=NResponse is fresh for N seconds
s-maxage=NOverrides max-age in shared caches; implies proxy-revalidate
no-cacheCache stores the response but must revalidate before every reuse
no-storeCache must not store any part of the response
publicAny cache stores the response, even for authenticated requests
privateOnly private caches store the response
must-revalidateOnce stale, the cache must revalidate before reuse; serves 504 on failure
proxy-revalidateSame as must-revalidate for shared caches only
no-transformIntermediaries must not alter the response body
must-understandCache stores the response only if the status code semantics are understood
immutableResponse body does not change while fresh; skip revalidation on user-initiated reload
stale-while-revalidate=NServe stale for up to N seconds while revalidating in the background
stale-if-error=NServe stale for up to N seconds when revalidation encounters a 500–599 error

Request Directives

DirectiveEffect
max-age=NAccept a response no older than N seconds
max-stale[=N]Accept a stale response, optionally no more than N seconds past expiry
min-fresh=NAccept a response fresh for at least N more seconds
no-cacheDo not serve from cache without revalidating first
no-storeDo not store the request or response
no-transformIntermediaries must not alter the body
only-if-cachedReturn a stored response or 504

CDN and edge caching

A CDN (content delivery network) operates a distributed network of shared caches at edge locations close to end users. CDN caches follow the same HTTP caching rules as proxy caches, with some platform-specific extensions.

Cache keys

CDN caches identify stored responses by a cache key, typically the request URL. Many CDNs extend the cache key with additional components: query string parameters, request headers (per Vary), Cookies, or geographic region. A misconfigured cache key is a common source of cache poisoning or unintended content sharing between users.

Cache purging

CDNs provide purging APIs to invalidate cached content before expiry. Purging by URL removes a single resource. Purging by tag (surrogate key) removes all responses tagged with a specific label. Tag-based purging is useful for invalidating all pages referencing a changed asset or data source.

Googlebot and HTTP caching

Google’s crawling infrastructure implements heuristic HTTP caching. Googlebot supports ETag / If-None-Match and Last-Modified / If-Modified-Since for cache validation when re-crawling URLs. When both validators are present, Googlebot uses the ETag value as the HTTP standard requires. The Cache-Control max-age directive helps Googlebot determine how often to re-crawl a URL. A page with a long max-age is re-fetched less frequently, while a page with a short max-age or no-cache signals the content changes often and warrants more frequent visits.

Common caching patterns

Versioned assets (cache forever)

Static assets with a fingerprint or version string in the URL are safe to cache indefinitely.

Cache-Control: max-age=31536000, immutable

The URL /assets/app.d9f8e7.js changes whenever the file content changes. The immutable directive tells the browser not to revalidate even on a user-initiated reload.

HTML pages (always revalidate)

HTML pages change frequently and benefit from revalidation on every load.

Cache-Control: no-cache

The cache stores the response but checks with the origin before serving. Combined with a strong ETag, this pattern ensures fresh content with minimal transfer cost when nothing changed.

Sensitive content (never cache)

Login pages, banking portals, and other pages with private data must not be cached.

Cache-Control: no-store

Shared cache with revalidation fallback

API responses served through a CDN with graceful degradation during outages.

Cache-Control: s-maxage=300, stale-if-error=3600

The CDN caches the response for five minutes. On origin failure, the CDN serves stale content for up to one hour.

HTTP Content Negotiation

Content negotiation is the mechanism by which a client and server agree on the best representation of a resource. The server selects from available variants based on client preferences expressed through HTTP headers, or presents alternatives for the client to choose from. A single resource from a URI can contain various representation such as json, xml, png, avif, etc depending upon the resource. Three negotiation patterns exists to determine which variet to send to client:

  • Proactive negotation: the server picks the best representation using preferences the client sent in the request. This is the dominant pattern using Accept, Accept-Encoding, Accept-Charset and Accept-Language headers
  • Reactive negotiation: the server lists available representations and the client picks one.
  • Request content negotiation: the server advertises preferences in a response, influencing how the client formats subsequent requests

HTTP Compression

HTTP compression reduces the size of data transferred between servers and clients. A client sends an Accept-Encoding header listing supported content codings. The server picks one, compresses the response body, and indicates the choice in a Content-Encoding header. This exchange is a form of proactive content negotiation.

The Accept-Encoding request header lists acceptable codings with optional quality values:

Accept-Encoding: br, gzip;q=0.8, zstd;q=0.9

The server selects one coding and returns the compressed body with two key headers:

  • Content-Encoding names the coding applied
  • Content-Length reflects the compressed size, not the original

Content Encoding

The most common ones are:

  • gzip: The gzip coding uses the GZIP file format , combining LZ77 and Huffman coding. Supported by every HTTP client and server. The gzip coding remains the most widely deployed content coding on the web.
  • br(Brotli): The br coding uses the Brotli algorithm , developed by Google. Brotli achieves higher compression ratios than gzip at comparable decompression speeds. Brotli includes a built-in static dictionary of common web content patterns, an advantage for compressing HTML, CSS, and JavaScript.All major browsers support Brotli over HTTPS connections.
  • zstd(Zstandard): The zstd coding uses the Zstandard algorithm , developed at Facebook. Zstandard offers a wide range of compression levels, from fast modes exceeding gzip speed to high modes rivaling Brotli compression ratios. Zstandard decompression is fast regardless of the compression level used. When used as an HTTP content coding, Zstandard encoders must limit the window size to 8 MB and decoders must support at least 8 MB. This cap prevents excessive memory consumption in browsers and other HTTP clients
  • dcb(Dictionary-Compressed Brotli): The dcb coding compresses a response using Brotli with an external dictionary. The compressed stream includes a fixed header containing the SHA-256 hash of the dictionary, allowing the client to verify the dictionary matches before decompression. Dictionary-based Brotli supports compression windows up to 16 MB.
  • dcz(Dictionary-Compressed Zstandard): The dcz coding compresses a response using Zstandard with an external dictionary. Like dcb, the compressed stream starts with a header containing the SHA-256 hash of the dictionary. The header is structured as a Zstandard skippable frame, making the format compatible with existing Zstandard decoders.
  • deflate: The deflate coding wraps a DEFLATE compressed stream inside the zlib format . Historical inconsistencies between implementations (some sent raw DEFLATE without the zlib wrapper) made deflate unreliable. Modern HTTP favors gzip or br instead.
  • compress: The compress coding uses adaptive Lempel-Ziv- Welch (LZW). Rarely encountered in modern HTTP.
  • identity: The identity coding means no transformation was applied. This value appears in Accept-Encoding to signal acceptance of uncompressed responses. A server is always allowed to send an uncompressed response, even when the client lists only compression codings.

Routing

Routing is how a server decides what to do with an incoming request. Every request has two key pieces of information:

  • the HTTP method
  • URL path
GET /api/users
───┬─── ────┬───
   │        └── WHERE (route path)
   └─────────── WHAT (HTTP method)
        │
        ▼
Router matches → runs the correct handler function

Static Routes

Static routes have a fixed, unchanging path. Every part of the URL is a literal string.

GET    /api/books      → return all books
POST   /api/books      → create a new book
GET    /api/users      → return all users

Dynamic Routes

Dynamic routes contain variable segments in the URL path, denoted by a colon(:). These variables let you target a specific resource by its identifier. The server extracts the dynamic value from the URL and uses it in its logic

GET /api/users/:id
GET /api/users/123     → id = "123"
GET /api/users/456     → id = "456"
GET /api/users/abc     → id = "abc"

Query Parameters

Query parameters are key-value pairs appended to the URL after a ?. They are used for optional, non-semantic data like filtering, sorting, and pagination. Multiple query params are separated by &. They are especially important for GET requests since GET has no request body

GET /api/books?page=2
GET /api/search?query=javascript&sort=newest
GET /api/products?category=shoes&minPrice=50&maxPrice=200

Nested Routes

Nested routes express hierarchical relationships between resources by embedding multiple identifiers in a single path

/api/users/123/posts/456
      │         │
      │         └── Post ID 456 belonging to user 123
      └── User ID 123

/api          → base path
/users        → static, "users" collection
/123          → dynamic, specific user (path param)
/posts        → static, "posts" collection under that user
/456          → dynamic, specific post (path param)

Route versioning

As APIs evolve, you often need to make breaking changes such as changing response shapes, renaming fields, removing endpoints. If you change an existing endpoint, every client using it breaks. Versioning solves this by running multiple versions of the API simultaneously

GET /api/v1/products   → old format, still running for existing clients
GET /api/v2/products   → new format, for new clients

Common versioning strategies beyond URL versioning include passing the version as a header API-Version: 2 or as a query param ?version=2, but URL versioning is the most common and explicit.

Catch-All Routes

A catch-all (wildcard) route is a fallback handler that matches any request that didn’t match any defined route. It prevents the server from returning a confusing raw error. For example the following code returns 404 on every route that is not registered before it.

app.all('*', (req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

Working of a router

When a request comes in, the router iterates through registered routes in order and pattern-matches the method + path:

Incoming: GET /api/users/123/posts

Registered routes:
  GET  /api/users           → no
  POST /api/users           → no (wrong method)
  GET  /api/users/:id       → no (path too long)
  GET  /api/users/:id/posts → yes MATCH → run handler

The router extracts dynamic segments, populates req.params, and calls the handler. If nothing matches, the catch-all fires.

Serialization and Deserialization

Different programming languages store data in their own native formats:

  • JavaScript → Objects
  • Rust → Structs
  • Python → Dicts
  • Go → Structs These formats live in memory and are language-specific. You can’t just send a JS object over a network or any boundary to another system that wouldnt understand js object such as a rust or go server. There we agree on a neutral, language-agnostic format. Both sides can convert to/from this format. This process is called serialization and deserialization.

Serialization

This is the act of flattening your in-memory, language specific data structure into a transferrable sequence of bytes. The client converts its native data into agreed format before sending it.

const user = {
  name: "quantinium",
  age: 21,
  scores: [98, 87, 91],
  active: true
};

// Serialization → convert to a JSON string (bytes, language-agnostic)
const payload = JSON.stringify(user);
// '{"name":"quantinium","age":21,"scores":[98,87,91],"active":true}'

fetch("/api/user", {
  method: "POST",
  body: payload
});

Serialization Standards

You can use anything as a transfer format as long as both sides have a serializer and deserializer for it. Standards fall into two categories:

Text based

Human-readable, easy to debug, universally supported. Slower and larger than binary.

FormatUsed For
JSONREST APIs, web — the dominant standard
XMLEnterprise, SOAP, legacy systems
YAMLConfig files (Docker, Kubernetes, GitHub Actions)
TOMLConfig files (Cargo.toml, pyproject.toml)
CSVTabular data exports, spreadsheets

Binary

Not human-readable. Significantly smaller and faster. Used when performance matters.

FormatUsed For
ProtobufgRPC, internal microservices (by Google)
AvroKafka, data pipelines, data lakes
MessagePackReal-time apps, Redis internals, gaming
FlatBuffersGames, mobile — zero-copy deserialization

Deserialzation

The reciever get those bits and reconstructs them into its own native type.

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize)]
struct User {
    name: String,
    age: u32,
    scores: Vec<u32>,
    active: bool,
}

async fn create_user(Json(user): Json<User>) -> impl IntoResponse {
    println!("{}", user.name);
}

Schema/Contract

Both sides need to agree on the shape of the data. This agreement is called a schema or contract

  • Implicit schema — both sides just follow a convention. Fragile, breaks silently.
  • Explicit schema — tools enforce the shape at the boundary.
    • Protobuf → .proto file shared between services
    • JSON Schema / OpenAPI → describes and validates JSON structure
    • Zod (TypeScript) → runtime validation on received data

Full Round Trip

CLIENT (JavaScript)                         SERVER (Rust)
───────────────────                         ─────────────

JS Object in RAM
{ name: "quantinium", age: 21 }
        │
        │ JSON.stringify()     ← SERIALIZE
        ▼
"{"name":"quantinium","age":21}"
        │
        │ HTTP POST (bytes over TCP)
        ▼
                                "{"name":"quantinium","age":21}"
                                        │
                                        │ serde_json::from_str()  ← DESERIALIZE
                                        ▼
                                User { name: "quantinium", age: 21 }
                                        │
                                        │ (process the request, build response)
                                        │
                                User { name: "quantinium", verified: true }
                                        │
                                        │ serde_json::to_string()  ← SERIALIZE
                                        ▼
                                "{"name":"quantinium","verified":true}"
                                        │
                                        │ HTTP 200 (bytes over TCP)
                                        ▼
"{"name":"quantinium","verified":true}"
        │
        │ JSON.parse()         ← DESERIALIZE
        ▼
JS Object in RAM
{ name: "quantinium", verified: true }

Authentication and Authorization

What is System Design?

System design is the process of defining how different parts of a software system interacts to meet both functional and non-functional requirements.

Key components of a system

A typical software system can be broken down into several key components:

  • Client - The part of the system with which the user interacts. It is responsible for displaying information, collecting user input and communicating with the backend.
  • Server - The backend handles the core functionality of the system. It processes incoming requests, executes business logic, interacts with databases and other services, and send requests back the client.
  • Databases - This is responsible for storing and managing data. It can be of various types such as relational/non-relational database, in-memory database, distributed object storage systems, etc.
  • Networking Layer - This include components like load balancers, API’s, communication protocol that ensure reliable and efficient interaction between different part of system
  • Third-Party Services - These are external API’s or platform that extend the system’s capabilities such as payment processors, email or sms services, authentication services, etc.

Process of System Design

System design is a step by step process that starts with understanding the requirements and ends with a detailed blueprint.

1. Requirement Gathering

This includes asking question about the requirements of the product which include:

  • Functional Requirements
  • Non-Functional Requirements
  • Number of users expected
  • Expected data volume, traffic patterns
  • Constraints such as budget, compliance rules, etc

2. Making Approximate Calculations

This involves making approximation about the number of resources would be needed.

  • Data size
  • Requests per second
  • Bandwidth needs
  • Number of resources required

3. High Level Design

Now that we have an overview of what our system will need, we can create a visual or high level design of the system’s components and how they interact with each other. This includes:

  • The main modules and services
  • Data flow between them
  • External Dependencies (eg. third-party API’s, external databases)

4. API Design

Once the high level design is done, we can move on to define the components in detail such as:

  • Type of database used
  • Schema, tables and relationships
  • Design API’s for services.

5. Deep Dive

Here we zoom into each component and define:

  • Internal logic, caching, concurrency handling
  • Scaling strategies
  • Replication, partitioning and fault tolerance
  • Availability, reliability, latency
  • Non functional requirements

6. Identify Bottlenecks and Trade-offs

In this phase we see the bottlenecks of our designed systems and the trade-offs we made. We make sure to try to eliminate bottlenecks if reasonable and important and reduce and justify the trade-offs if made in the desing.

Scalability

Java

  • class - a blueprint or template defining the structure and behavior of objects
  • object - an instance of a class representing a specific identity
  • encapsulation - bundling data and methods witihin a class, restricting direct access to data using access modifiers (public, private, protected).
  • inheritance - mechanism where a class inherits properties and methods of another class.
  • polymorphism - it means many forms. it means when methods of a class can morph into a different function with the help of inheritence: method overloading and overriding.
  • Abstraction - hiding complex implementation details and exposing only necessary features. implemented using abstract class and interfaces.
  • Decomposition - process of breaking a complex system into more maneagable and simple parts.
  • Abstraction Mechanism - techniques to achieve abstraction
    • parameterization - making a component accepts parameters.
    • specification - defining behavior and properties of a component.

Kinds of Abstraction

  • Procedural Abstraction - focuses on abstracting a single procedure or action into a single component
  • Data Abstraction - Hiding internal representation of data and exposing only necessary operations. ex - stack , queue, etc.
  • Control Abstraction - Abstract control flow intro reusable constructs. ex- map, forEach, etc
  • Type Abstraction - Generalizes data types enabling to work with self-defined and more complex data types.
  • Functional Abstraction - Treats function as first class entities, abstracting behavior that can be passed or composed.
  • Module Abstraction - Group related procedures and data into a single unit.

Benefits

  • modularity - the gang of four
  • reusability - inheritance and polymorphism allows code reuse
  • scalability - code organization helps in scaling to large codebases (netflix btw)
  • maintainability - changes to one class doesnt affect others. thus simplifying updates
  • real world modelling - oop mirrors real world by having objects and methods.
  • flexibility
  • security

Evolution

  • 1960s - early programming was procedural, mostly was c, fortran, algol, etc
  • 1967 - Simula 67, developed by Ole-Johan Dahl and Kristen Nygaard, introduced the concept of classes and objects, laying the foundation for OOP.
  • 1970s - Smalltalk, developed by Alan Kay at Xerox PARC, fully embraced OOP principles, introducing inheritance, polymorphism, and message passing.
  • 1980 - 1980s: OOP gained traction with languages like C++ (Bjarne Stroustrup), which added OO features to C, and Objective-C
  • 1990 - Java (Sun Microsystems) simplified OOP for cross-platform development, emphasizing portability and safety.
  • 2000s–Present: Modern languages like Python, Ruby, and C# integrated OOP with other paradigms (e.g., functional programming). Frameworks and libraries (e.g., Spring, Django) further standardized OO practices.

Programming Paradigms

Programming paradigms define the style and methodology to write code. Some of the major paradigms are :-

  • Procedural -
    • focuses on procedures or data that operate on data.
    • program is a sequence of tasks.
    • ex - c, fortran
  • Object Oriented -
    • Organized code around objects, which combine data (attributes) and operations (methods).
    • ex - c++, java, python
  • Functional -
    • treats computation as the evaluation of mathematical functions, avoiding state changes and mutable data.
    • ex - haskell, lisp

Procedural vs OOP

AspectObject-Oriented ApproachProcedure-Oriented Approach
Basic UnitObjects (instances of classes)Functions or procedures
FocusData and how it is manipulated (data-centric)Procedures and sequence of tasks (process-centric)
StructurePrograms are organized around classes and objectsPrograms are divided into functions
Data HandlingData is encapsulated (private/protected)Data is often global, accessible to all functions
ModularityHigh, due to encapsulation and classesModerate, relies on function separation
ReusabilityHigh, via inheritance and polymorphismLimited, requires rewriting or copying code
ScalabilityScales well for large, complex systemsBecomes cumbersome in large systems
SecurityBetter, due to encapsulation and access controlLower, as data is often exposed
ExamplesJava, C++, Python (OO mode)C, Fortran, Pascal
Real-World ModelingNaturally models real-world entities (e.g., Car, Person)Less intuitive for complex entities
MaintenanceEasier to modify and extendHarder, as changes may affect multiple functions

Basic Features

java is

  • high level
  • object oriented - built around object and classes
  • platform-independent - due to jvm
  • robust - proper types, exception handling, garbage collection
  • secure - sand boxed, byte-code execution and security manager to ensure safe execution
  • multi-threaded
  • released in 1995 by sun micro-systems.

use cases

  • web application - spring-boot, hibernate
  • mobile apps
  • enterprise software - Netflix BTW
  • big data - Hadoop
  • embedded and iot

features

  • simple and familiar syntax to c
  • no pointers and manual memory management
  • object oriented
  • platform independent due to jvm
  • robust
  • secure
  • multithreaded
  • high performance due to JIT
  • portable
  • architecture neutral
  • dynamic
  • rich std library

JVM

java virtual machine is an abstract computing machine that serves as runtime environment for executing java bytecode

  • translates java bytecode into machine code for execution
  • manages memory, security and execution of java application
  • provides a consistent environment across different hardware and operating systems.

Architecture

  • Class Loader Subsystem -
    • Loading - loads, links and initializes .class files into memory.
    • Linking- ensure bytecode is safe for execution -> allocate memory for static variables and assign default values -> resolve symbolic links to direct references.
    • Initialization - executes static initializers and assigns values to static fields.
  • Runtime Data Areas -
    • Method area - stores class level data, class structures, method bytecode, constant pool, static vars. shared accross all threads.
    • Heap - stores all objects and their instance vars.
    • Stack - per-threaded memory for method calls, storing stack frames.
    • PC (program counter) - per-threaded register that stores the address of the current instruction being executed.
    • Native method stack - per-thread memory for native methods.
  • Execution Engine -
    • executes bytecode by translating it into machine code.
    • components -
      • interpreter - executes bytecode line by line
      • just in time compiler (JIT) - compiles frequently executed code for faster execution.
      • garbage collector - automatically reclaim memory from unused object in the heap.
  • Native method interface - enables java code to interact with native application in c or c++
  • Security Manager - enforces security policies, restricted untrusted code from accessing sensitive resources.

OOP

its a paradigm based on concept of objects which combines data (attributes) and behavior (methods).

class

it is a blueprint or template for creation of an object. it defines - attributes - data stored in objects (variables). - methods - behaviors or functions that object can perform

  • a class encapsulates data and behavior, supporting oop principles like encapsulation, inheritance and polymorphism.
  • contains fields, methods, constructors and optionally other classes and interfaces.
class Car {
    // Fields
    String model;
    int year;

    // Constructor
    Car(String model, int year) {
        this.model = model;
        this.year = year;
    }

    // Method
    void displayInfo() {
        System.out.println("Model: " + model + ", Year: " + year);
    }
}

creating objects

  • an object is an instance of a class, created from class blueprint.
  • objects are created using new keyword which allocated memory to the heap.
  • a constructor is called during object creation to initlialize fields.
  • each object has its own copy of instance variables but shares the class’s methods.
public class Main {
    public static void main(String[] args) {
        // Creating objects
        Car car1 = new Car("Toyota", 2020);
        Car car2 = new Car("Honda", 2022);

        // Accessing object methods
        car1.displayInfo(); // Outputs: Model: Toyota, Year: 2020
        car2.displayInfo(); // Outputs: Model: Honda, Year: 2022
    }
}

assigning object reference variables

  • variables of a class type are references to object.
  • a reference variable holds the memory address of an object on the heap.
  • assigning one variable to another variables make both point to the same object
  • objects are not duplicated during assignment. only references are copied
  • setting a references to null removes its association with any object, making the object eligible for garbage collection.
public class Main {
    public static void main(String[] args) {
        // Create objects
        Car car1 = new Car("Toyota", 2020);
        Car car2 = new Car("Honda", 2022);

        // Assigning object reference
        Car ref = car1; // ref now points to the same object as car1
        ref.displayInfo(); // Outputs: Model: Toyota, Year: 2020

        // Modify object via ref
        ref.model = "Nissan";
        car1.displayInfo(); // Outputs: Model: Nissan, Year: 2020 (car1 reflects change)

        // Set car1 to null
        car1 = null;
        ref.displayInfo(); // Still works: Model: Nissan, Year: 2020 (ref still points to object)
        // car1.displayInfo(); // Would throw NullPointerException
    }
}

Access Modifiers

  • public: Inherited and accessible everywhere.
  • protected: Inherited and accessible in the same package or subclasses (even in different packages).
  • default (no modifier): Inherited only within the same package.
  • private: Not inherited or accessible in the subclass directly.

Inheritance

  • it is an oop concept that allows a class to inherit properties and behaviors from another class.
  • It promotes code reusability, modularity and establishes a hierarchical relationship between classes
  • the extends keyword is used to establish inheritance.
class Superclass {
    // Fields and methods
}
class Subclass extends Superclass {
    // Additional fields and methods, or overrides
}
  • purpose :
    • reusability - reuse existing code from superclass.
    • extensibiliy - add new features or modify inherited behavior in the subclass.
    • polymorphism - enable polymorphic behavior where a superclass reference can point to a subclass object.
class Animal {
    String name;
    
    Animal(String name) {
        this.name = name;
    }
    
    void eat() {
        System.out.println(name + " is eating.");
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name); // Call superclass constructor
    }
    
    // Override eat method
    @Override
    void eat() {
        System.out.println(name + " is eating bones.");
    }
    
    // New method
    void bark() {
        System.out.println(name + " is barking.");
    }
}

class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy");
        dog.eat();  // Output: Buddy is eating bones.
        dog.bark(); // Output: Buddy is barking.
    }
}

types of inheritance

  • single inheritance - a subclass inherits from a superclass
class A {}
class B extends A {} // B inherits from A
  • multilevel inheritance - a class inherits from a superclass which further inherits from another class.
class A {}
class B extends A {}
class C extends B {} // C inherits from B, which inherits from A
  • hierarchical inheritance - multiple classes inherit from the same subclass``
class A {}
class B extends A {}
class C extends A {} // B and C both inherit from A
  • multiple inheritance - a class inherits from multiple super classes (is not supported in java via class). is done using interfaces
interface I1 {}
interface I2 {}
class C implements I1, I2 {} // Valid
  • hybrid inheritance - a combination of two or more types of inheritance.

limitations

  • no multiple inheritance
  • tight coupling - overuse of inheritance can lead to tightly coupled code leading to harder maintainability
  • fragile base case problem - changes to the superclass can unintentionally break subclasses
class Vehicle {
    String brand;
    int speed;
    
    Vehicle(String brand, int speed) {
        this.brand = brand;
        this.speed = speed;
    }
    
    void move() {
        System.out.println(brand + " is moving at " + speed + " km/h.");
    }
}

class Car extends Vehicle {
    int doors;
    
    Car(String brand, int speed, int doors) {
        super(brand, speed);
        this.doors = doors;
    }
    
    @Override
    void move() {
        System.out.println(brand + " car with " + doors + " doors is moving at " + speed + " km/h.");
    }
}

class Main {
    public static void main(String[] args) {
        Vehicle vehicle = new Car("Toyota", 120, 4);
        vehicle.move(); // Output: Toyota car with 4 doors is moving at 120 km/h.
    }
}

Polymorphism

  • polymorphism allows a single reference (a superclass or interface) to refer to objects of different subclasses, with specific behavior determined by the actual object type at runtime or compile time.
  • it enables code to work with objects in generalized way while allowing specific implementation to vary.
  • purpose
    • flexibility - write generic code that works with multiple types.
    • extensibility - add new subclasses without modifying existing code.
    • reusability - use a common interface or superclass to handle diverse objects.

types of polymorphism

  • runtime polymorphism - achieved through method overriding, where the method to be called is determined at runtime based on actual object type.
  • compile time polymorphism - achieved using method overloading or operator overloading where method is chosed at compile time based on method signature.

runtime polymorphism

  • achieved through method overriding, where a subclass provides a specific implementation of a method defined in superclass or interface.
  • jvm determines the actual type of object at runtime and invokes the overridden method of subclass, even if the reference type is the superclass or interface.
class Animal {
    void sound() {
        System.out.println("Generic animal sound");
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Meow");
    }
}

class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // Superclass reference, Dog object
        Animal animal2 = new Cat(); // Superclass reference, Cat object
        
        animal1.sound(); // Output: Woof (Dog's method called)
        animal2.sound(); // Output: Meow (Cat's method called)
    }
}

compile time polymorphism

  • achieved using method overloading, where multiple methods in the same class have the same name but different parameters lists.
  • the compiler determines which method to call at compile time based on method signature and arguments passed.
class Calculator {
    int add(int a, int b) {
        return a + b;
    }
    
    double add(double a, double b) {
        return a + b;
    }
    
    int add(int a, int b, int c) {
        return a + b + c;
    }
}

class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(2, 3));       // Output: 5
        System.out.println(calc.add(2.5, 3.5));   // Output: 6.0
        System.out.println(calc.add(1, 2, 3));    // Output: 6
    }
}

advantages

  • flexibility - write generic code that works with multiple types
  • extensibility - add new subclasses or implementations without modifying existing code
  • maintainability - reduces code duplication by using common interface or superclasses.

limitations

  • performance overhead - runtime polymorphism involves dynamic method dispatch, which is slightly lower than static binding.
  • complexity - overuse of polymorphism can make code harder to understand.
  • downcasting risks - incorrect downcasting can lead to classCastException

Inheritance vs Polymorphism

AspectInheritancePolymorphism
DefinitionA mechanism where a class (subclass) inherits fields and methods from another class (superclass).A mechanism allowing objects of different classes to be treated as instances of a common superclass or interface.
PurposeEnables code reuse and establishes an “is-a” relationship between classes.Enables flexibility by allowing a single interface to represent different types or behaviors.
MechanismAchieved using the extends keyword for classes or implements for interfaces.Achieved through method overriding (runtime) or method overloading (compile-time).
RelationshipCreates a hierarchical relationship (e.g., Dog is an Animal).Allows objects to be processed uniformly despite different implementations (e.g., Animal reference for Dog or Cat).
TypeStructural concept (defines class relationships).Behavioral concept (defines how objects behave or are accessed).
Code Examplejava<br>class Animal {<br> void eat() { System.out.println("Eating"); }<br>}<br>class Dog extends Animal {}<br>java<br>class Animal {<br> void sound() { System.out.println("Sound"); }<br>}<br>class Dog extends Animal {<br> @Override<br> void sound() { System.out.println("Woof"); }<br>}<br>Animal a = new Dog();<br>a.sound(); // Outputs: Woof<br>
Execution TimeDetermined at compile time (class structure is fixed).Runtime (method overriding) or compile-time (method overloading).
FlexibilityFixed hierarchy; adding new classes requires modifying the structure.Highly flexible; new subclasses can be added without changing existing code.
DependencyDoes not require polymorphism (can exist without overriding).Often relies on inheritance (for method overriding) or interfaces.
ScopeFocuses on sharing and extending code (fields, methods).Focuses on dynamic behavior or method selection.

Relationship Between Inheritance and Polymorphism

  • Dependency: Polymorphism (specifically runtime polymorphism via method overriding) often relies on inheritance, as it requires a superclass-subclass relationship or interface implementation. However:
    • Inheritance can exist without polymorphism (e.g., a subclass uses inherited methods without overriding them).
    • Polymorphism can occur without inheritance in the case of interfaces (e.g., a class implements an interface but doesn’t extend a class).
  • Complementary Roles:
    • Inheritance provides the structure (class hierarchy) for code reuse.
    • Polymorphism provides the behavior (dynamic method invocation) for flexibility.

Abstract Classes

  • it is a class in java that can’t be instantiated but is used as a blueprint for other classes. it is designed to be extended by subclasses.
  • it is declared using keyword abstract.
  • it may contain
    • abstract methods - methods without a body, must be implemented by subclass
    • concrete methods - methods with implementation that subclasses use or override.
    • fields - constructors and nested classes.
  • cannot be instantiated.
  • purpose -
    • provide a common structure and behavior for related classes.
    • enforces a contract by requiring subclasses to implement abstract methods.
    • enables code reuse through shared fields and methods.
    • support polymorphism by allowing abstract class references to point to subclass objects.
abstract class Animal {
    String name;

    // Constructor
    Animal(String name) {
        this.name = name;
    }

    // Abstract method (must be implemented by subclasses)
    abstract void makeSound();

    // Concrete method (shared by all subclasses)
    void sleep() {
        System.out.println(name + " is sleeping.");
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name); // Call superclass constructor
    }

    @Override
    void makeSound() {
        System.out.println(name + " says Woof!");
    }
}

class Cat extends Animal {
    Cat(String name) {
        super(name);
    }

    @Override
    void makeSound() {
        System.out.println(name + " says Meow!");
    }
}

class Main {
    public static void main(String[] args) {
        // Animal animal = new Animal("Generic"); // Error: Cannot instantiate
        Animal dog = new Dog("Buddy");
        Animal cat = new Cat("Whiskers");

        dog.makeSound(); // Output: Buddy says Woof!
        dog.sleep();     // Output: Buddy is sleeping.
        cat.makeSound(); // Output: Whiskers says Meow!
        cat.sleep();     // Output: Whiskers is sleeping.
    }
}

Interfaces

  • interfaces in java is a reference type that defines a set of abstract methods that a class must implement.
  • it specifies what a class must do without dictating how it should do it.
  • features -
    • declared using interface keyword.
    • all methods in an interface are implicitely pubilc and abstract.
    • can contain constants
    • cannot instantiate directly.
    • supports multiple inheritance
interface InterfaceName {
    // Constant
    int CONSTANT = 10;

    // Abstract method
    void methodName();

    // Default method (Java 8+)
    default void defaultMethod() {
        System.out.println("Default implementation");
    } 

    // Static method (Java 8+)
    static void staticMethod() {
        System.out.println("Static method");
    }
}

implementing interfaces

  • a class implements an interface using implements keyword, agreeing to provide implementations for all abstract methods defined in the interface.
  • rules -
    • a class must implement all abstract methods of an interface, or it must be declared abstract.
    • a class can implement multiple interfaces.
    • the implementing class must use public access for interface methods (since interfaces methods are implicitly public)
    • interfaces support polymorphism, an interface reference can point to any implementing class’s object.
interface Movable {
    void move(); // Abstract method
    default void stop() {
        System.out.println("Stopping...");
    }
}

class Car implements Movable {
    @Override
    public void move() {
        System.out.println("Car is moving.");
    }
}

class Bike implements Movable {
    @Override
    public void move() {
        System.out.println("Bike is moving.");
    }
}

class Main {
    public static void main(String[] args) {
        Movable car = new Car();
        Movable bike = new Bike();

        car.move();  // Output: Car is moving.
        car.stop();  // Output: Stopping...
        bike.move(); // Output: Bike is moving.
        bike.stop(); // Output: Stopping...
    }
}

features

  • default methods - methods with a body. can be overriden.
interface Vehicle {
    void drive();
    default void honk() {
        System.out.println("Beep beep!");
    }
}

class Truck implements Vehicle {
    @Override
    public void drive() {
        System.out.println("Truck is driving.");
    }
}

class Main {
    public static void main(String[] args) {
        Truck truck = new Truck();
        truck.drive(); // Output: Truck is driving.
        truck.honk();  // Output: Beep beep!
    }
}
  • static methods - belong to the interface itself not instances
interface Utility {
    static void helper() {
        System.out.println("Static helper method.");
    }
}

class Main {
    public static void main(String[] args) {
        Utility.helper(); // Output: Static helper method.
    }
}
  • constants - fields in an interface are implicitly public, static, final keyword.

Abstract Classes vs Interfaces

FeatureInterfaceAbstract Class
DefinitionA contract with abstract methods, default methods, static methods, and constants.A class with abstract and/or concrete methods, fields, and constructors.
Keywordinterfaceabstract class
InstantiationCannot be instantiated.Cannot be instantiated.
MethodsAbstract methods (implicitly public), default methods, static methods.Abstract and concrete methods (any access modifier).
FieldsOnly public, static, final fields (constants).Can have instance fields (state) with any access modifier.
ConstructorsNo constructors.Can have constructors for initialization.
InheritanceA class can implement multiple interfaces (implements).A class can extend only one abstract class (extends).
Multiple InheritanceSupported (via multiple interfaces).Not supported (single inheritance for classes).
Access ModifiersMethods are implicitly public.Methods/fields can be public, protected, private, or default.
StateStateless (no instance fields).Can maintain state (instance fields).
Use CaseDefine a contract or role (e.g., Comparable, Runnable).Provide partial implementation and shared state (e.g., Vehicle with fields).
PolymorphismSupports polymorphism (interface reference to implementing class).Supports polymorphism (abstract class reference to subclass).
Examplejava<br>interface Flyable {<br> void fly();<br>}<br>java<br>abstract class Animal {<br> abstract void sound();<br> void sleep() { ... }<br>}<br>
interface Drawable {
    void draw();
    default void describe() {
        System.out.println("This is a drawable object.");
    }
}

abstract class Shape {
    String color;

    Shape(String color) {
        this.color = color;
    }

    abstract double getArea();

    String getColor() {
        return color;
    }
}

class Circle extends Shape implements Drawable {
    double radius;

    Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    double getArea() {
        return Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a " + getColor() + " circle.");
    }
}

class Main {
    public static void main(String[] args) {
        Circle circle = new Circle("Red", 5);
        circle.draw();        // Output: Drawing a Red circle.
        circle.describe();    // Output: This is a drawable object.
        System.out.println("Area: " + circle.getArea()); // Output: Area: 78.53981633974483
    }
}

Common Concepts

Primitive Data types

java has 8 primitive data types

TypeSizeDescriptionDefault ValueExample Values
byte1 byte8-bit signed integer0-128, 0, 127
short2 bytes16-bit signed integer0-32768, 0, 32767
int4 bytes32-bit signed integer0-2^31, 42, 2^31-1
long8 bytes64-bit signed integer0L-2^63, 100L, 2^63-1
float4 bytes32-bit floating-point (IEEE 754)0.0f3.14f, -0.001f
double8 bytes64-bit floating-point (IEEE 754)0.0d3.14159, -2.71828
char2 bytes16-bit Unicode character‘\u0000’‘A’, ‘7’, ‘\n’
boolean~1 bitTrue or false valuefalsetrue, false

Variables

a named storage space that hold value of a specific data type. rules -

  • names are case sensitive
  • start with a letter, _, $ and can include digits.
  • local variables need explicit initializations, instance/static variables get default values.

Operators

operators are symbols that perform operations on variables and values.

arithmetic operators

OperatorDescriptionExample
+Addition5 + 3 = 8
-Subtraction5 - 3 = 2
*Multiplication5 * 3 = 15
/Division6 / 2 = 3
%Modulus (remainder)7 % 3 = 1

unary operators

OperatorDescriptionExample
++Increment by 1x++ (x = x+1)
--Decrement by 1x-- (x = x-1)
+Unary plus+5
-Unary minus-5
!Logical NOT!true = false

relational operators

OperatorDescriptionExample
==Equal to5 == 5 (true)
!=Not equal to5 != 3 (true)
>Greater than5 > 3 (true)
<Less than3 < 5 (true)
>=Greater than or equal5 >= 5 (true)
<=Less than or equal3 <= 5 (true)

logical operators

OperatorDescriptionExample
&&Logical ANDtrue && false = false
||Logcal ORtrue || false = true
!Logical NOT!true = false

bitwise operators

OperatorDescriptionExample
&Bitwise AND5 & 3 = 1
|Bitwise OR5 | 3 = 7
^Bitwise XOR5 ^ 3 = 6
~Bitwise NOT~5 = -6
<<Left shift5 << 1 = 10
>>Right shift5 >> 1 = 2
>>>Unsigned right shift5 >>> 1 = 2

assignment operators

OperatorDescriptionExample
=Assignx = 5
+=Add and assignx += 3 (x = x+3)
-=Subtract and assignx -= 3
*=Multiply and assignx *= 3
/=Divide and assignx /= 3
%=Modulus and assignx %= 3

ternary operator

OperatorDescriptionExample
?:Conditional expressionx > 0 ? "Positive" : "Non-positive"

Instanceof operator

  • Checks if an object is an instance of a class or interface.
  • Example: if (obj instanceof String) { ... }.

expressions

combination of variables, literals, operators and method call that evaluate to a single value. ex -3 + 5 , Math.sqrt(16).

statements

complete unit of execution in java. ends with ;

array

fixed sized order collection of elements of the same type. can be 1D, 2D and multi dimentional

int x = 10;
double y = 5.5;
boolean isPositive = x > 0;

double result = x * y + 3;
System.out.println("Result: " + result);

if (isPositive) {
    System.out.println("x is positive");
}

int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
    System.out.println("Number: " + numbers[i]);
}

Methods

  • a method is a block of code within a class that defines a specific behavior or action an object can perform.
  • it encapsulates functionality and can operate on an object’s data fields or external inputs.
  • components -
    • return type - specifies what a method return. ex - void, int, etc.
    • method name - descriptive identifier (function name)
    • parameters - optional inputs
    • body - code that executes when method is called.
class Rectangle {
    int length;
    int width;

    // Method to calculate area
    int calculateArea() {
        return length * width;
    }

    // Method to set dimensions
    void setDimensions(int l, int w) {
        length = l;
        width = w;
    }
}

public class Main {
    public static void main(String[] args) {
        Rectangle rect = new Rectangle();
        rect.setDimensions(5, 3);
        int area = rect.calculateArea(); // Returns 15
        System.out.println("Area: " + area);
    }
}

static methods

  • a static method belongs to a class rather than an instance of the class.
  • it can be called without creating an object.
  • declared with static keyword
class MathUtils {
    // Static method
    static int square(int num) {
        return num * num;
    }
}

public class Main {
    public static void main(String[] args) {
        int result = MathUtils.square(4); // No object needed
        System.out.println("Square: " + result); // Outputs: Square: 16
    }
}

constructors

  • it is a special method used to initialize objects when they are are created.
  • it sets an initial state of an object’s field.
  • has the same name as the class.
  • no return type
  • called automatically.
  • typically public but can be private.
class Book {
    String title;
    int pages;

    // Constructor
    Book(String title, int pages) {
        this.title = title; // this distinguishes field from parameter
        this.pages = pages;
    }

    void display() {
        System.out.println("Title: " + title + ", Pages: " + pages);
    }
}

public class Main {
    public static void main(String[] args) {
        Book book = new Book("Java Basics", 300);
        book.display(); // Outputs: Title: Java Basics, Pages: 300
    }
}

overloading constructors

  • constructor overloading allows a class to have multiple constructors with different parameter lists.
  • constructors must differ in the number, type or order of parameters.
class Student {
    String name;
    int age;
    String major;

    // Default constructor
    Student() {
        this.name = "Unknown";
        this.age = 18;
        this.major = "Undeclared";
    }

    // Parameterized constructor
    Student(String name, int age) {
        this.name = name;
        this.age = age;
        this.major = "Undeclared";
    }

    // Another parameterized constructor
    Student(String name, int age, String major) {
        this.name = name;
        this.age = age;
        this.major = major;
    }

    // Using this() to call another constructor
    Student(String name) {
        this(name, 18); // Calls constructor with name and default age
    }

    void display() {
        System.out.println("Name: " + name + ", Age: " + age + ", Major: " + major);
    }
}

public class Main {
    public static void main(String[] args) {
        // Using different constructors
        Student s1 = new Student(); // Default
        Student s2 = new Student("Alice", 20);
        Student s3 = new Student("Bob", 22, "Computer Science");
        Student s4 = new Student("Charlie");

        // Display results
        s1.display(); // Outputs: Name: Unknown, Age: 18, Major: Undeclared
        s2.display(); // Outputs: Name: Alice, Age: 20, Major: Undeclared
        s3.display(); // Outputs: Name: Bob, Age: 22, Major: Computer Science
        s4.display(); // Outputs: Name: Charlie, Age: 18, Major: Undeclared
    }
}

Packages

  • packages are mechanism for organizing classes and interfaces into namespace, preventing naming conflicts, improving code maintainability, and controlling access to classes.
  • They group related code together, similar to folders in a file system.

defining a package

  • a package is defined using the package keyword at the beginning of a java source file. It specifies the namespace where the class resides.
// File: com/example/MyClass.java
package com.example;

public class MyClass {
    public void display() {
        System.out.println("Hello from MyClass!");
    }
}

classpath

  • the classpath is an environment variable or command line parameter that tells the java compiler and jvm where to find the compiled .class files and libs.
  • purpose -
    • helps locate classes in packages when compiling or running java programs.
    • specifies directories, JAR files, or ZIP file containing class files.

package naming

  • package names follow a hierarchical naming convention to avoid conflicts and ensure uniqueness.
  • conventions -
    • use lowercase letters.
    • follow reverse domain name structure to ensure uniqueness.
  • rules -
    • must be valid identifiers (no spaces, special characters, or reserved keywords).
    • should map to directory structure.
    • avoid using java keywords or starting with numbers.

accessibility of packages

  • packages control access to classes and their members using [[access modifiers]]. the accessibility of package members depends on these modifiers and package structure.

using package members

  • to use classes, interfaces or other members from a package, you need to either import them or use their fully qualified name.
  • using fully qualified name -
com.example.MyClass obj = new com.example.MyClass(); obj.display()
  • using import - import a specific class or an entire packages to use its members without the fully qualified name.
import packageName.ClassName;
  • static imports
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;

public class Test {
    public static void main(String[] args) {
        System.out.println("PI: " + PI); // No Math.PI needed
        System.out.println("Sqrt: " + sqrt(16)); // No Math.sqrt needed
    }
}

Strings

Characters

  • a character is a single 16-bit Unicode character, represented by the char primitive data type.
  • It can store any character from the Unicode character set (e.g., letters, digits, symbols).
    char ch = 'A'; // Stores the character 'A'
    char unicodeChar = '\u0041'; // Unicode for 'A'
    

Strings

  • A string is a sequence of characters.
  • In Java, strings are objects of the String class (from the java.lang package), not primitive types.
  • Strings are immutable, meaning their content cannot be changed after creation.
    String str = "Hello, World!";
    

string class

key features

  • immutability: ensures thread safety and security (e.g., in class loading or as keys in hash-based collections).

  • string pool: java maintains a pool of string literals to optimize memory. Strings created using literals (e.g., "Hello") are stored in the pool, while those created with new String("Hello") are not (unless interned).

    String str1 = new String(); // Empty string
    String str2 = new String("Hello"); // String from literal
    char[] chars = {'H', 'i'};
    String str3 = new String(chars); // String from char array
    
  • string methods

    • length(): Returns the number of characters.
    • charAt(int index): Returns the character at the specified index.
    • substring(int beginIndex, int endIndex): Returns a substring.
    • toUpperCase() / toLowerCase(): Converts case.
    • indexOf(String str): Returns the index of the first occurrence of str.
    • equals(Object obj) / equalsIgnoreCase(String str): Compares strings.
    • trim(): Removes leading/trailing whitespace.
    • replace(char oldChar, char newChar): Replaces characters.

valueO()

The String class provides valueOf() methods to convert various data types to strings. These are static methods.

int num = 42;
String strNum = String.valueOf(num); // "42"

double d = 3.14;
String strDouble = String.valueOf(d); // "3.14"

boolean b = true;
String strBool = String.valueOf(b); // "true"

char[] chars = {'H', 'i'};
String strChars = String.valueOf(chars); // "Hi"

StringBuffer Class

  • The StringBuffer class is used for mutable strings, unlike the immutable String class. It is thread-safe (synchronized methods), making it suitable for multi-threaded applications.
  • Key Features of StringBuffer hello
    • Mutable: Allows in-place modification of the character sequence.
    • Thread-safe: Synchronized methods ensure safe use in multi-threaded environments.
    • Capacity: Internal buffer size, which grows dynamically (default initial capacity is 16).
StringBuffer sb1 = new StringBuffer(); // Empty, capacity 16
StringBuffer sb2 = new StringBuffer("Hello"); // Initialized with "Hello"
StringBuffer sb3 = new StringBuffer(50); // Empty, capacity 50
  • StringBuffer Methods
    • append(String str): Appends data (overloaded for various types: int, double, etc.).
    • insert(int offset, String str): Inserts data at the specified position.
    • delete(int start, int end): Removes characters from start to end-1.
    • replace(int start, int end, String str): Replaces characters from start to end-1 with str.
    • reverse(): Reverses the character sequence.
    • capacity(): Returns the current capacity.
    • ensureCapacity(int minCapacity): Ensures the buffer has at least the specified capacity.
    • toString(): Converts the StringBuffer to a String.

When to Use StringBuffer

  • Use StringBuffer for string manipulation in multi-threaded environments.
  • For single-threaded applications, prefer StringBuilder for better performance (no synchronization overhead).
  • Example:
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 1000; i++) {
        sb.append(i); // Efficient for repeated modifications
    }
    

String vs. StringBuffer

FeatureStringStringBuffer
MutabilityImmutableMutable
Thread-SafetyThread-safe (immutable)Thread-safe (synchronized)
PerformanceSlower for modificationsFaster for modifications
Memory UsageCreates new objectsModifies in-place
Use CaseFixed stringsDynamic string manipulation

Exceptions

  • exception is an event that disrupts the normal flow of a program during execution, typically caused by errors such as invalid input, file not found, or network issues.
  • in java, exceptions are objects derived from the java.lang.Throwable class, which has two main subclasses.
  • java provides a structured way to handle exceptions using the try-catch mechanism, with optional finally and throw constructs.

handling exceptions

using try-catch

  • the catch block executes only if the specified exception.
  • use e.getMessage() to get the exception’s error messages or e.printStackTrace() for a detailed stack trace.
  • multiple catch can be used to catch multiple exceptions.
public class Main {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // Throws ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Error: Division by zero");
            System.out.println("Message: " + e.getMessage());
        }
        System.out.println("Program continues...");
    }
}

using multiple catches

try {
    String str = null;
    System.out.println(str.length()); // Throws NullPointerException
    int[] arr = new int[2];
    arr[3] = 10; // Throws ArrayIndexOutOfBoundsException
} catch (NullPointerException e) {
    System.out.println("Null reference: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Invalid index: " + e.getMessage());
}

catching superclass exceptions

try {
    int result = 10 / 0;
} catch (Exception e) {
    System.out.println("General error: " + e.getMessage());
}

using finally

the finally block contains code that executes regardless of whatever an exception is thrown or caught

public class Main {
    public static void main(String[] args) {
        try {
            int result = 10 / 0; // Throws ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Error: " + e.getMessage());
        } finally {
            System.out.println("Cleanup in finally block");
        }
    }
}

types of exceptions

checked exceptions

  • must be declared in a method’s throws clause or handled in a try-catch block.
  • compile time checking ensures they are addressed.
  • extend java.lang.Exception
  • ex - IOException, SQLException, classNotFoundException.

unchecked exception

  • doesnt require explicit handling or declaration
  • typically indicates programming errors or runtime issues.
  • extend java.lang.RuntimeException
  • ex - NullPointerException, ArrayIndexOutOfBoundsException, etc.

throwing exceptions

the throw keyword is used to explicitly throw an expression either built in or custom exception.

public class Main {
    public static void checkAge(int age) throws IllegalArgumentException {
        if (age < 18) {
            throw new IllegalArgumentException("Age must be 18 or older");
        }
        System.out.println("Access granted");
    }

    public static void main(String[] args) {
        try {
            checkAge(15);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

writing exception subclasses

// Custom checked exception
class InvalidBalanceException extends Exception {
    public InvalidBalanceException(String message) {
        super(message);
    }

    public InvalidBalanceException(String message, Throwable cause) {
        super(message, cause);
    }
}

// Custom unchecked exception
class InsufficientFundsException extends RuntimeException {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

class BankAccount {
    private double balance;

    public void withdraw(double amount) throws InvalidBalanceException {
        if (amount < 0) {
            throw new InvalidBalanceException("Withdrawal amount cannot be negative");
        }
        if (amount > balance) {
            throw new InsufficientFundsException("Not enough funds");
        }
        balance -= amount;
    }

    public void setBalance(double balance) throws InvalidBalanceException {
        if (balance < 0) {
            throw new InvalidBalanceException("Balance cannot be negative");
        }
        this.balance = balance;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();
        try {
            account.setBalance(100);
            account.withdraw(150); // Throws InsufficientFundsException
        } catch (InvalidBalanceException e) {
            System.out.println("Balance Error: " + e.getMessage());
        } catch (InsufficientFundsException e) {
            System.out.println("Funds Error: " + e.getMessage());
        }
    }
}

Multithreading

  • multi-threading is an ability of a program to execute multiple threads concurrently, where each thread represents an independent flow of execution.
  • threads share the same memory space within a process, making them lightweight compared to separate processes.
  • benefits -
    • concurrency - perform multiple tasks simultaneosly
    • responsiveness - keep application responsive
    • efficiency - utilizes cpu more effectively
    • modularity - break complex tasks into independent threads.
  • challenges -
    • race conditions.
    • deadlocks - thread waiting for each other infinitely.
    • thread safety - ensuring shared resources are accessed correctly.
  • thread lifecycle -
    • new - thread created but not started
    • runnable - thread is ready to run or running
    • blocked/waiting - thread is waiting for a monitor lock or another condition.
    • timed waiting - waiting for a specific amount of time.
    • terminated - thread has completed execution.

main thread

  • it is the default thread created when a java program starts.
  • created automatically by jvm.
  • responsible for executing main() method
  • can create and manage other threads.
  • program terminates when the main thread ends.
public class MainThread {
    public static void main(String[] args) {
        Thread current = Thread.currentThread(); // Get main thread
        System.out.println("Main thread: " + current.getName());
        System.out.println("Priority: " + current.getPriority());
    }
}

java thread model

it provides a robust thread model through java.lang.Thread class and java.lang.Runnnable interface. threads can be created in two ways -

  • extending the thread class
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(getName() + ": Count " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start(); // Start thread
    }
}
  • implementing runnable interface
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {
            System.out.println(Thread.currentThread().getName() + ": Count " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable(), "RunnableThread");
        t1.start();
    }
}

key methods

  • start(): Begins thread execution (calls run()).
  • run(): Contains the thread’s task (override in subclass or Runnable).
  • sleep(long millis): Pauses the thread for the specified time.
  • join(): Waits for the thread to terminate.
  • setName(String name): Sets the thread’s name.
  • getName(): Gets the thread’s name.
  • setPriority(int priority): Sets the thread’s priority.
  • isAlive(): Checks if the thread is running.

thread priorities

thread priorities determine the relative importance of threads, influencing the schedular’s decision on which thread to execute when multiple threads are runnable. Java assigns prirorites as integer from 1 to 10.

Thread t1 = new Thread(() -> System.out.println("Low priority"));
t1.setPriority(Thread.MIN_PRIORITY);

Thread t2 = new Thread(() -> System.out.println("High priority"));
t2.setPriority(Thread.MAX_PRIORITY);

t1.start();
t2.start();

synchronization

  • when multiple threads access the same shared resources, race conditions can occur, leading to inconsistent or incorrect results.
  • Synchronization ensures that only one thread can access the data once at a time.
  • mechanism -
    • synchronization methods - add synchronized keyword to a method to ensure only one thread can execute at a time for a given object. uses the object’s intrinsic lock (monitor).
    • synchronized blocks - synchronize a specific block of code using an object’s lock
// synchronization method
class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

// synchronization blocks
class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }

    public int getCount() {
        return count;
    }
}

interthread communication

  • interthread communication allows threads to coordinate their actions, typically using the wait-notify mechanism to avoid busy-waiting (polling).
  • this is useful when one thread needs to wait for another to complete a task or update a shared resource.

key methods

  • wait() - causes the current thread to wait until another thread calls notify().
  • notify() - wakes up one waiting thread
  • notifyall() - wakes up all waiting threads.
class SharedBuffer {
    private int data;
    private boolean available = false;

    public synchronized void produce(int value) throws InterruptedException {
        while (available) {
            wait(); // Wait if buffer is full
        }
        data = value;
        available = true;
        System.out.println("Produced: " + data);
        notifyAll();
    }

    public synchronized int consume() throws InterruptedException {
        while (!available) {
            wait(); // Wait if buffer is empty
        }
        available = false;
        System.out.println("Consumed: " + data);
        notifyAll();
        return data;
    }
}

public class Main {
    public static void main(String[] args) {
        SharedBuffer buffer = new SharedBuffer();

        Thread producer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    buffer.produce(i);
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                for (int i = 1; i <= 5; i++) {
                    buffer.consume();
                    Thread.sleep(2000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producer.start();
        consumer.start();
    }
}

Streams

java organizes I/O into byte and character streams, each with specific class for different tasks.

byte stream

  • handle raw binary data
  • base classes
    • InputStream - Abstract class for reading bytes
    • OutputStream - Abstract class for writing bytes
  • common subclasses -
    • FileInputStream, FileOutputStream - read/write bytes from/to files.
    • BufferedInputStream, BufferedOutputStream- use buffering to reduce direct access to the underlying system.
    • DataInputStream, DataOutputStream - read/write primitive data types
    • ObjectInputStream, ObjectOutputStream - serialize/deserialize objects.
import java.io.*;

public class ByteStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             FileOutputStream fos = new FileOutputStream("output.txt")) {
            int byteData;
            while ((byteData = fis.read()) != -1) { // Read byte-by-byte
                fos.write(byteData); // Write byte
            }
        } catch (IOException e) {
            System.out.println("IO Error: " + e.getMessage());
        }
    }
}

character streams

  • handles unicode characters, ideal for text data.
  • base classes -
    • Reader - abstract class for reading characters
    • Writer - abstract class for writing characters.
  • common subclasses -
    • FileReader, FileWriter - read/write characters to/from files.
    • BufferedReader, BufferedWriter - buffer characters data for efficiency.
    • InputStreamReader, OutputStreamWriter - bridge bytes streams to character streams
import java.io.*;

public class CharStreamExample {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"));
             BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                writer.write(line);
                writer.newLine();
            }
        } catch (IOException e) {
            System.out.println("IO Error: " + e.getMessage());
        }
    }
}

predefined streams

  • system.in
  • system.out
  • system.err

I/O

java’s io is built around the concept of streams, which are a sequence of data used for input and output. Streams abstract the underlying data source or destinations, allowing uniform handling of different I/O operations.

concepts

  • input - reading from data source
  • output - writing data to a destination
  • stream types -
    • byte streams - handle raw binary data
    • character streams - handle unicode characters
  • IO operations are often wrapped in try-catch with IOException.

core packages

  • java.io
  • java.nio
  • java.nio.file

Swing

  • swing is a part of Java’s JFC(Java Foundation Classes) and provides a rich set of gui components for building desktop applications
  • lightweight
  • platform-independant
  • highly customizable

Swing Components

  • JFrame - top level container for a swing application
  • JPanel - a generic container for grouping other containers
  • JButton - a clickable button for user interactions
  • JLabel - displays text or images
  • JTextField - allows user input of a single line
  • JTextArea - supports multi-line text input
  • JCheckBox - a textbox for selecting options
  • JRadioButton - a radio button for mutually exclusive selections
  • JComboBox - a dropdown menu for selecting one option from a list
  • JTable - displays tabular data
  • JScrollPane - adds scrollbar to components like textarea or tables
  • JMenu - for creating menu’s
  • JMenuBar - for modal or non-modal dialog boxes.
public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Swing App");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.setSize(400, 300);
        JPanel panel = new JPanel()
        JLabel label = new JLabel("Enter Name: ");
        JTextField textField = new JTextField(20);
        JButton button = new JButton("Submit");

        panel.add(label);
        panel.add(textField);
        panel.add(button);

        frame.add(panel);
        frame.setVisible(true);
    }
}

Look and feel

the look and feel defines the appearance and behavior of swing components. Swing components supports pluggable look and feel, allowing you to changethe visual style of the application.

common look and feel options are -

  • metal
  • nimbus
  • windows
  • motif
  • system look and feel
public class Main {
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch(Exception e) {
            e.printStackTrace();
        }

        JFrame frame = new JFrame("Nimbus L&F Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);

        JPanel panel = new JPanel();
        panel.add(new JButton("Click Me"));
        panel.add(new JLabel("Nimbus Look & Feel"));
        frame.add(panel);

        frame.setVisible(true);
    }
}

Event listeners

swing uses an event-driver model where components generate events and event listeners handle them.

common event listeners -

  • ActionListener - Handles actions like button clicks
  • MouseListener - Responds to mouse events
  • KeyListener - Handles keyboard input
  • WindowListener - Manages window events
  • ItenListener - handles changes in components like checkboxes or combo boxes.
public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);

        JPanel panel = new JPanel();
        JButton button = new JButton("Click Me");
        JLabel label = new JLabel("No clicks yet");

        button.addActionListener(e -> label.setText("Button Clicked!"));

        panel.add(button);
        panel.add(label);
        frame.add(panel);

        frame.setVisible(true);
    }
}

Concurrency in Swing

  • swing is single threaded and all gui updates must occur on the event dispatch thread(EDT) to avoid thread safety issues.
  • Long running tasks are offloaded to worker threads to prevent GUI from freezing
import javax.swing.*;
import java.awt.*;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        JFrame frame = new JFrame("SwingWorker Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 300);

        JPanel panel = new JPanel();
        JLabel label = new JLabel("Status: Idle");
        JButton startButton = new JButton("Start Task");

        startButton.addActionListener(e -> {
            SwingWorker<Void, String> worker = new SwingWorker<>() {
                @Override
                protected Void doInBackground() throws Exception {
                    for (int i = 1; i <= 5; i++) {
                        Thread.sleep(1000); // Simulate long task
                        publish("Processing step " + i);
                    }
                    return null;
                }

                @Override
                protected void process(List<String> chunks) {
                    label.setText(chunks.get(chunks.size() - 1));
                }

                @Override
                protected void done() {
                    label.setText("Task Completed!");
                }
            };
            worker.execute();
        });

        panel.add(startButton);
        panel.add(label);
        frame.add(panel);
        frame.setVisible(true);
    }
}

Keywords

final

final keyword is used to provide restriction to classes, methods or variables, ensuring immutability.

uses

  • variables - make then constant
  • -methods - cannot be overridden
  • classes - cannot be inherited.
class Example {
    final int MAX_VALUE = 100; // Final instance variable, initialized at declaration
    final double PI; // Blank final variable

    Example() {
        PI = 3.14159; // Initialized in constructor
    }

    void modify() {
        // MAX_VALUE = 200; // Compilation error: Cannot assign a value to final variable
        // PI = 3.14; // Compilation error
    }
}

class Main {
    public static void main(String[] args) {
        final int localVar = 10; // Final local variable
        // localVar = 20; // Compilation error
        System.out.println(localVar); // Output: 10

        Example ex = new Example();
        System.out.println(ex.MAX_VALUE); // Output: 100
        System.out.println(ex.PI); // Output: 3.14159
    }
}
  • final prevents reassigning the reference but the objects internal state can still be modified.

super

the super keyword is used in subclasses to

  • call the superclass constructors
  • access superclass members
class Animal {
    String name = "Animal";
    void eat() {
        System.out.println("Animal eats.");
    }
}

class Dog extends Animal {
    String name = "Dog";
    void eat() {
        super.eat(); // Call superclass method
        System.out.println("Dog eats bones.");
    }
    void display() {
        System.out.println("Superclass name: " + super.name); // Access superclass field
    }
}

class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();    // Output: Animal eats. Dog eats bones.
        dog.display(); // Output: Superclass name: Animal
    }
}

this

this keyword is a reference to the current object of the class. it is used to resolve ambiguity between instance variables and parameters or to invoke methods/constructors of the current class.

referring to instance variable

class Person  Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name; // 'this.name' refers to the instance variable, 'name' to the parameter
        this.age = age;
    }
}

invoking another constructor

class Person {
    String name;
    int age;

    Person(String name) {
        this(name, 0); // Calls the constructor with two parameters
    }

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

returning to current object

class Person {
    String name;
    int age;

    Person setName(String name) {
        this.name = name;
        return this; // Returns the current object
    }

    Person setAge(int age) {
        this.age = age;
        return this;
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Alice").setAge(25); // Method chaining
    }
}

passing the current object to a method

class Person {
    String name;

    void display(Person person) {
        System.out.println("Person's name: " + person.name);
    }

    void show() {
        display(this); // Passes the current object
    }
}
  • usage of this keyword is not allowed in static blocks or methods because they belong to the class rather than the instance.
  • improved code readability and prevent errors due to naming conflicts
  • it is commonly used in constructors, getters and setters.

finalize

  • the finalize() method is called by the garbage collector before an object is reclaimed. It’s defined in the Object class and can be overriden to perform cleanup
  • protected void finalize() throws Throwable
  • rarely used in modern java as try catch is more preferrable
  • deprecated in java 9 due to performance issues and unreliability.
class Resource {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("Cleaning up resource");
        // Perform cleanup
        super.finalize();
    }
}

class Main {
    public static void main(String[] args) {
        Resource r = new Resource();
        r = null; // Eligible for GC
        System.gc(); // Suggests GC, may trigger finalize()
    }
}

Transient and Volatile

serialization

it is the process of converting an object’s state into a byte stream, which can be saved to a file, sent over a network or stored in a database.

transient modifier

  • The transient modifier is used to indicate that a field should not be serialized when an object is converted to a byte stream
  • When an object is serialized, transient fields are excluded from the serialized data and are typically initialized to their default values (e.g., null for objects, 0 for numbers) when deserialized.
import java.io.*;

class User implements Serializable {
    String name;
    transient String password; // This field won't be serialized

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        User user = new User("Alice", "secret123");
        // Serialize
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
        oos.writeObject(user);
        oos.close();

        // Deserialize
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
        User deserializedUser = (User) ois.readObject();
        ois.close();

        System.out.println("Name: " + deserializedUser.name); // Output: Alice
        System.out.println("Password: " + deserializedUser.password); // Output: null
    }
}

volatile

  • The volatile modifier ensures that a field’s value is always read from and written to the main memory, preventing thread-local caching.
  • It guarantees visibility of changes to a variable across multiple threads and prevents certain compiler optimizations that could reorder instructions.
  • Ensures visibility: When one thread modifies a volatile variable, the change is immediately visible to all other threads.
  • Prevents reordering: The compiler and JVM won’t reorder operations on a volatile variable in a way that breaks its visibility guarantees.
  • Does not provide atomicity (e.g., i++ is not thread-safe even if i is volatile).
class SharedResource {
    volatile boolean flag = false;

    public void toggleFlag() {
        flag = true; // Write to main memory
    }

    public boolean isFlag() {
        return flag; // Read from main memory
    }
}

public class Main {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        // Thread 1: Modifies flag
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                resource.toggleFlag();
                System.out.println("Flag set to true");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // Thread 2: Reads flag
        new Thread(() -> {
            while (!resource.isFlag()) {
                // Busy wait
            }
            System.out.println("Flag is now true");
        }).start();
    }
}
Featuretransientvolatile
PurposeControls serializationEnsures thread visibility
Applied ToInstance fieldsInstance or static fields
ContextSerialization (I/O)Multithreading
EffectExcludes field from serializationEnsures reads/writes go to main memory
Thread SafetyNot related to threadsProvides visibility, not atomicity

Garbage Collection

  • java’s garbage collector automatically reclaims memory by deallocating objects that are no longer reachable.
  • this is managed by jvm
  • garbage collection primarily works on the heap.

concepts

  • heap memory - java objects are stored in the heap, a region of memory divided into areas like:
    • young generation - new objects are allocated here.
    • old generation - for long lived objects
    • meta-space - for class metadata.
  • reachability - an object is reachable if it is accessible by the chain of references from root. if it is not it is elidgible for garbage collection.

working

  • marking - the gc traverse the object graphing, marking all objects that are being referenced . unmarked objects are considered for garbage collection
  • sweeping - the gc reclaims the memory by freeing the objects or moving the objects.
  • compaction - some gc compact the heap by moving live objects together reducing fragmentation and improving memory allocation frequency.

advantages

  • prevents memory leaks by automatically freeing unused memory
  • eleminates manual memory management like in c/c++.
  • reduces bugs like dangling pointer and double -free errors.

disadvantages

  • unpredictable timing
  • performance overhead
  • limited control
class Main {
    public static void main(String[] args) {
        Box b1 = new Box(); // b1 references a Box object
        Box b2 = new Box(); // b2 references another Box object
        b1 = null; // b1's object is now eligible for GC
        b2 = b1;  // b2's original object is also eligible for GC
        System.gc(); // Suggest GC
    }
}

Native Methods

  • native methods in Java are methods declared in a Java class but implemented in a native language like C or C++ using the Java Native Interface (JNI).
  • They are used when Java code needs to interact with hardware, operating system, or performance-critical operations that Java cannot handle efficiently.
  • The native keyword is used to declare such methods, and they are typically loaded from a shared library (e.g., .dll on Windows or .so on Unix).

concepts

  • Native Method Declaration - use the native keyword in the method signature, with no implementation in Java.
  • JNI: The Java Native Interface provides the framework to call native code and pass data between Java and the native environment.
  • Shared Library: The native code is compiled into a shared library, which is loaded into the JVM using System.loadLibrary().
  • Instance Context: For instance native methods, the native code receives a reference to the Java object (this) via the JNIEnv pointer.