JavaScript and TypeScript Shorthands

Master the essential shorthand techniques that reduce boilerplate, improve readability, and enhance performance in your modern web development workflow.

Why Use Shorthands in Modern Development

Modern web development demands clean, maintainable code. Both JavaScript and TypeScript offer numerous shorthand techniques that reduce boilerplate, improve readability, and can even enhance performance. This guide explores essential shorthands every developer should know, with practical examples and performance considerations.

As noted by LogRocket's comprehensive guide to JavaScript and TypeScript shorthands, mastering these patterns is essential for professional developers working on production applications. The evolution of JavaScript from ES5 to modern ES6+ has introduced powerful shorthand features that have become standard practice in professional development.

Our web development services team has witnessed firsthand how proper shorthand usage transforms codebase maintainability and developer productivity across projects of all sizes.

What You'll Learn

  • Core JavaScript shorthand techniques that every developer should master
  • TypeScript-specific shorthands including constructor shorthand
  • Performance implications of using various shorthand patterns
  • Best practices for readability and maintainability
  • Real-world code examples demonstrating each technique

The adoption of these shorthand techniques across the industry means developers can expect to encounter them in nearly every modern codebase. Understanding these patterns not only improves your own code but makes you more effective when collaborating with other developers and reviewing code in team environments.

Impact of Modern Shorthand Techniques

40%

Reduction in code verbosity with proper shorthand usage

3x

Faster code review with consistent shorthand patterns

100%

Modern browsers support ES6+ shorthand features

JavaScript Shorthand Essentials

These fundamental shorthand techniques form the foundation of modern JavaScript development. Mastering them will significantly improve your coding efficiency and code quality. According to Telerik's guide to JavaScript shorthands, these patterns are essential knowledge for any professional JavaScript developer.

Ternary Operator

The ternary operator provides a concise way to write conditional expressions. It's particularly useful for simple conditional assignments and keeps code compact when the logic is straightforward.

// Longhand
let result;
if (condition) {
 result = 'yes';
} else {
 result = 'no';
}

// Shorthand
const result = condition ? 'yes' : 'no';

Nested ternary operators can handle multiple conditions, but they require careful consideration of readability. When nesting ternaries, use parentheses to clarify the evaluation order and consider whether an if-else statement might be more appropriate for complex logic.

// Nested ternary - use with caution
const status = age >= 18 ? (age >= 65 ? 'senior' : 'adult') : 'minor';

Best practice is to avoid nesting more than one level deep. If you find yourself with complex conditional logic, consider using a switch statement or extracting the logic into named functions for better readability.

Optional Chaining Operator (?)

ES2020 introduced optional chaining to safely access nested properties without risking errors on undefined values. This operator has become essential for handling API responses and dynamic data structures where the presence of nested properties cannot be guaranteed.

// Longhand
const city = user && user.address && user.address.city;

// Shorthand
const city = user?.address?.city;

Optional chaining can be combined with method calls and array indices as well:

// Method calls
const firstName = user?.getName?.();

// Array access
const firstItem = users?.[0];

// Combined
const street = user?.address?.getStreet?.() ?? 'Unknown';

The optional chaining operator evaluates to undefined if any part of the chain is null or undefined, making it safe for deeply nested object access. This prevents the common TypeError: "Cannot read property of undefined" that frequently occurred in older JavaScript codebases.

Nullish Coalescing Operator (??)

The nullish coalescing operator provides a clean way to handle default values while distinguishing between null/undefined and other falsy values. This is critical for scenarios where 0 or empty strings are valid values that should not be replaced by defaults.

// Longhand
const value = myVar !== null && myVar !== undefined ? myVar : 'default';

// Shorthand
const value = myVar ?? 'default';

The key difference between the logical OR operator (||) and the nullish coalescing operator (??) is how they handle falsy values. The || operator treats all falsy values (0, '', false) as needing a default, while ?? only replaces null or undefined:

// Problem with || operator
const count = 0;
console.log(count || 'none'); // 'none' - wrong! 0 is a valid count

// Correct with ?? operator
console.log(count ?? 'none'); // 0 - correct!

This distinction is particularly important when working with numeric values, empty strings, or boolean flags where 0 or false might be meaningful values rather than indicators of missing data.

Related reading: Understanding how JavaScript has evolved from Lodash and Underscore to modern vanilla JavaScript patterns that reduce dependency on external libraries.

Default Parameter Values

ES2015+ allows defining default values directly in function parameters, eliminating the need for manual null checks and making function signatures more expressive. This feature works with any value type, including objects, arrays, and even other function calls.

// Longhand
function greet(name) {
 name = name || 'Guest';
 return 'Hello, ' + name;
}

// Shorthand
function greet(name = 'Guest') {
 return 'Hello, ' + name;
}

One critical consideration when using default parameters is how they interact with falsy values. Unlike the || operator, the default only activates for undefined, not for null or other falsy values like 0 or empty string. This behavior is intentional and often desirable:

function setValue(value = 'default') {
 console.log(value);
}

setValue(undefined); // 'default' - uses default
setValue(null); // null - does NOT use default
setValue(0); // 0 - does NOT use default (unlike ||)
setValue(''); // '' - does NOT use default

Default expressions can also reference other parameters, enabling powerful patterns for configuration:

function createUser(name, role = 'user', permissions = getDefaultPermissions(role)) {
 return { name, role, permissions };
}

Arrow Functions

Arrow functions provide more concise syntax and lexical this binding, making them ideal for callbacks and short functions. They have become the preferred syntax for functional programming patterns in modern JavaScript.

// Longhand
function add(a, b) {
 return a + b;
}

// Shorthand
const add = (a, b) => a + b;

Arrow functions support implicit return for single expressions, eliminating the need for curly braces and the return keyword:

// Single parameter - no parentheses needed
const square = x => x * x;

// Multiple parameters - parentheses required
const multiply = (a, b) => a * b;

// Multi-line - use curly braces and explicit return
const complex = (a, b) => {
 const temp = a * b;
 return temp + a;
};

However, arrow functions should not be used for class methods or object methods that need access to this based on the object they belong to. In those cases, regular function syntax or method shorthand is more appropriate:

// Correct - arrow for callback
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);

// Avoid - arrow loses `this` context
const counter = {
 count: 0,
 increment: () => this.count++ // Wrong!
};

// Correct - regular function for method
const counter = {
 count: 0,
 increment() {
 this.count++;
 }
};

Object Property Shorthand

When property names match variable names, you can use shorthand syntax to eliminate redundancy. This pattern is particularly useful when creating objects from variables or destructuring, and it helps maintain consistency in your codebase.

const name = 'John';
const age = 30;

// Longhand
const person = { name: name, age: age };

// Shorthand
const person = { name, age };

This shorthand works seamlessly with destructuring, making it easy to extract values and create new objects in a single expression:

const user = { name: 'Alice', city: 'Boston', active: true };

// Extract and create new object
const { name, city } = user;
const userSummary = { name, city };

// One-liner with parentheses
const summary = (({ name, city }) => ({ name, city }))(user);

Destructuring Assignment

Extract values from objects and arrays with concise, readable syntax. This is one of the most powerful features for working with complex data structures, particularly when dealing with function parameters or API responses.

// Array destructuring
const colors = ['red', 'green', 'blue'];
const [primary, secondary] = colors;

// Object destructuring
const user = { name: 'Alice', city: 'Boston' };
const { name, city } = user;

Destructuring supports default values, renaming, and the rest operator for collecting remaining properties:

// Default values
const config = { timeout: undefined };
const { timeout = 3000, debug = false } = config;

// Renaming properties
const user = { name: 'Alice' };
const { name: userName } = user;
console.log(userName); // 'Alice'

// Rest operator
const { first, second, ...rest } = { first: 1, second: 2, third: 3, fourth: 4 };
console.log(rest); // { third: 3, fourth: 4 }

Destructuring is particularly powerful for function parameters, allowing you to specify expected properties and their types in a clear, declarative way:

function greet({ name, age, city = 'Unknown' }) {
 return `Hello, ${name}! You are ${age} years old from ${city}.`;
}

greet({ name: 'Bob', age: 25 }); // Works

Spread Operator (...)

The spread operator simplifies array and object operations, making code more expressive and reducing the need for utility functions. It expands iterables into individual elements or object properties into new object literals.

// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

// Object spreading
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }

The spread operator excels at shallow cloning and merging objects without modifying the originals, which is essential for immutable state management patterns:

// Shallow clone
const original = { a: 1, b: { c: 2 } };
const clone = { ...original };

// Merge multiple objects
const base = { a: 1 };
const options = { b: 2 };
const config = { ...base, ...options, c: 3 }; // { a: 1, b: 2, c: 3 }

// Add/update properties immutably
const updated = { ...user, email: '[email protected]' };

For arrays, spread can also be used to convert iterables to arrays and spread elements into function calls:

// Convert iterable to array
const str = 'hello';
const chars = [...str]; // ['h', 'e', 'l', 'l', 'o']

// Function arguments
const nums = [1, 2, 3];
Math.max(...nums); // 3

Template Literals

Template literals enable string interpolation with cleaner syntax, supporting expressions and multiline strings without concatenation. They have largely replaced traditional string concatenation for readability and maintainability.

// Longhand
const message = 'Hello, ' + name + '! You have ' + messages + ' messages.';

// Shorthand
const message = `Hello, ${name}! You have ${messages} messages.`;

Template literals support arbitrary expressions, including function calls and calculations:

// Expression interpolation
const total = items.reduce((sum, item) => sum + item.price, 0);
const receipt = `Total: $${total.toFixed(2)}`;

// Multiline strings (preserves whitespace)
const html = `
 <div class="card">
 <h2>${title}</h2>
 <p>${description}</p>
 </div>
`;

// Tagged templates for custom processing
function sql(strings, ...values) {
 return strings.reduce((result, str, i) => 
 result + str + (values[i] ? `'${values[i]}'` : ''), '');
}
const query = sql`SELECT * FROM users WHERE id = ${userId}`;

For-of Loop

Modern iteration syntax that's more readable than traditional for loops, working with any iterable object. The for-of loop abstracts away index management and works consistently across different iterable types.

const items = ['a', 'b', 'c'];

// Longhand
for (let i = 0; i < items.length; i++) {
 console.log(items[i]);
}

// Shorthand
for (const item of items) {
 console.log(item);
}

The for-of loop works with strings, Maps, Sets, and any object implementing the iterable protocol. This makes it versatile for various data structures:

// Strings (iterates over characters)
for (const char of 'hello') {
 console.log(char); // h, e, l, l, o
}

// Maps (iterates over values)
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
 console.log(key, value);
}

// Sets
const set = new Set([1, 2, 3]);
for (const value of set) {
 console.log(value);
}

For simple iteration over arrays where you don't need the index, for-of is generally more readable than traditional for loops or forEach. However, forEach remains appropriate when you need to break out of iteration or when the callback-based API integrates better with your existing code.

For teams building modern web applications, understanding these JavaScript fundamentals pairs well with exploring modern CSS frameworks that complement clean JavaScript architecture.

TypeScript-Specific Shorthands

TypeScript provides powerful shorthand features that reduce type annotation boilerplate while maintaining type safety. As documented in the official TypeScript Handbook, these features help developers write more concise, expressive code without sacrificing the benefits of static typing.

TypeScript Constructor Shorthand

TypeScript's parameter properties feature allows you to declare and initialize class properties directly in the constructor signature. This eliminates repetitive property declarations and assignments, reducing boilerplate significantly for classes with multiple properties.

// Longhand
class User {
 private name: string;
 private age: number;

 constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
}

// Shorthand - Parameter Properties
class User {
 constructor(private name: string, private age: number) {}
}

The parameter property shorthand works with all access modifiers, allowing you to control visibility while simultaneously declaring and initializing:

class User {
 // Public - accessible from anywhere
 constructor(public id: string) {}

 // Private - only accessible within the class
 constructor(private secret: string) {}

 // Protected - accessible within class and subclasses
 constructor(protected token: string) {}

 // Readonly - cannot be modified after initialization
 constructor(readonly createdAt: Date) {}
}

// Combined modifiers
class Config {
 constructor(
 public readonly apiKey: string,
 protected environment: string,
 private debugMode: boolean
 ) {}
}

This shorthand is particularly valuable for dependency injection patterns and service classes where you want to clearly declare dependencies in the constructor signature.

Type Inference with Shorthand Object Types

TypeScript can often infer types without explicit annotations, reducing verbosity while maintaining type safety. The compiler is sophisticated enough to analyze object shapes and infer appropriate types in most cases.

// Longhand with explicit type
const user: { name: string; age: number; active: boolean } = {
 name: 'John',
 age: 30,
 active: true
};

// TypeScript infers the type automatically
const user = {
 name: 'John',
 age: 30,
 active: true
};

// Use 'satisfies' for validation without verbose annotations
const user = {
 name: 'John',
 age: 30,
 active: true
} satisfies { name: string; age: number; active: boolean };

The satisfies operator, introduced in TypeScript 4.9, allows you to validate that a value matches a type while keeping TypeScript's type inference. This is useful when you want to ensure correctness without explicitly annotating every object.

For functions, TypeScript can infer return types from the function body, which is particularly useful for complex transformations:

// Return type inferred
function processData(data: Input[]) {
 return data.map(item => ({
 id: item.id,
 label: item.name.toUpperCase(),
 timestamp: Date.now()
 }));
}

Interface and Type Shorthands

Clean patterns for defining complex types that reduce boilerplate and improve readability. TypeScript provides several shortcuts for common type patterns that you'll encounter frequently in real-world development.

// Function type shorthand
type Logger = (message: string) => void;

// Optional properties shorthand
interface Config {
 host: string;
 port?: number; // Optional property
 timeout?: number;
}

// Readonly modifier for immutability
interface User {
 readonly id: string;
 name: string;
}

// Index signatures for dynamic properties
interface Dictionary {
 [key: string]: unknown;
}

Intersection types combine multiple types into one, while union types allow values to be one of several types:

// Intersection type - combines properties
type Admin = { adminLevel: number };
type Employee = { employeeId: string };
type AdminEmployee = Admin & Employee;

// Union type - one of several
type Status = 'pending' | 'active' | 'completed' | 'failed';

// Practical example
interface APIResponse<T> {
 data: T;
 status: 'success' | 'error' | 'loading';
 message?: string;
}

TypeScript's utility types provide shorthand for common type transformations that you can use to create flexible, reusable type definitions:

// Partial - makes all properties optional
interface FullConfig {
 host: string;
 port: number;
 ssl: boolean;
}
type PartialConfig = Partial<FullConfig>;

// Pick - selects specific properties
type ConfigHost = Pick<FullConfig, 'host' | 'port'>;

// Record - creates object type with specified keys and value type
type UserMap = Record<string, User>;

Generic Type Parameter Inference

Let TypeScript infer generic types when possible, reducing explicit type annotations while maintaining type safety. Generic inference allows your functions and classes to work with any type while preserving type information.

// Explicit generic
function identity<T>(value: T): T {
 return value;
}
const num = identity<number>(42);

// TypeScript infers the generic
const num = identity(42); // TypeScript infers number

// Multiple type parameters
function pair<A, B>(a: A, b: B): [A, B] {
 return [a, b];
}
const result = pair('hello', 42); // Inferred as [string, number]

Inference works with complex types including classes and callback functions, making your APIs more ergonomic without sacrificing type safety:

// With class
class Container<T> {
 constructor(public value: T) {}
 getValue(): T {
 return this.value;
 }
}
const container = new Container('test'); // Container<string>

// With callbacks
async function fetchData<T>(url: string, parser: (json: unknown) => T): Promise<T> {
 const response = await fetch(url);
 const json = await response.json();
 return parser(json);
}

const user = fetchData('/api/user', json => ({
 id: json.id as number,
 name: json.name as string
}));

If you're comparing TypeScript with other type systems, you might find our guide on comparing Dart and TypeScript valuable for understanding type safety across different languages.

Performance Considerations

While shorthand syntax is generally equivalent to longhand at runtime, understanding performance implications helps you make informed decisions about when to use each pattern. Modern JavaScript engines optimize most shorthand patterns effectively, but there are edge cases worth knowing.

When Shorthands Impact Performance

  • Spread operator vs. concat for large arrays - The spread operator creates a new array by copying elements, which for very large arrays (thousands of elements) may use more memory than concat(). For most use cases under 10,000 elements, the difference is negligible.

  • Object destructuring in hot paths - Destructuring creates temporary variables and can trigger garbage collection more frequently. In performance-critical code like game loops or high-frequency trading systems, consider using direct property access instead.

  • Optional chaining - Adds slight runtime overhead for the undefined checks, but this is typically worth it for the error prevention benefits. The cost is minimal compared to the potential runtime errors from missing checks.

  • Bundle size with modern syntax - When targeting older browsers, Babel transpiles modern syntax to equivalent but more verbose ES5 code. This can result in larger bundle sizes. Consider using browserslist to target only browsers that support the features you use.

Modern JavaScript and Build Tools

Modern bundlers like Vite, webpack, and esbuild are optimized to handle shorthand syntax efficiently and often provide better output than manual optimization:

  • Tree shaking eliminates dead code from production builds, so unused shorthand patterns don't increase bundle size
  • Babel/tsc transpile modern syntax for older browser targets while preserving semantic equivalence
  • Source maps enable debugging of original shorthand syntax in browser dev tools
  • Minification works better with consistent shorthand patterns, resulting in smaller minified output

For most web development projects, the benefits of improved readability and maintainability far outweigh any minor runtime differences. Modern JavaScript engines like V8 (Chrome/Node.js), SpiderMonkey (Firefox), and JavaScriptCore (Safari) are highly optimized for common patterns.

If you're working on performance-critical applications, profile your code with tools like Chrome DevTools to identify actual bottlenecks rather than optimizing speculatively. Premature optimization of syntax patterns rarely provides meaningful improvements compared to algorithmic optimizations.

For teams building high-performance web applications, integrating AI automation services can help optimize development workflows and ensure code quality at scale.

Best Practices and Common Pitfalls

Using shorthands effectively requires understanding when they improve versus reduce code quality. The goal is always improved maintainability, not merely reduced character count.

Readability Guidelines

Use shorthands when:

  • The operation is simple and obvious to your team
  • The shorthand is widely recognized and standard
  • It reduces boilerplate without hiding important logic
  • You're consistently applying the same patterns across the codebase

Avoid shorthands when:

  • The logic is complex or nested deeply
  • The shorthand obscures the intent for your team
  • Your team isn't familiar with the syntax yet
  • Error handling needs to be explicit and visible
// Good - concise and clear
const isActive = user?.isActive ?? false;
const names = users.map(u => u.name).filter(Boolean);

// Avoid - too clever, hard to parse
const result = users.reduce((acc, u) => (acc[u.id] = u, acc), {});

// Better - explicit and readable
const userMap = {};
for (const user of users) {
 userMap[user.id] = user;
}

Code Review Checklist

When reviewing code that uses shorthand patterns, consider these points:

  • Shorthand usage doesn't obscure business logic or make bugs harder to find
  • Optional chaining is used appropriately for truly optional properties
  • Type annotations are clear when they serve as documentation
  • Error handling isn't compromised by excessive use of optional chaining
  • Team style guide is followed consistently across the codebase
  • New team members can understand the code without excessive explanation

Introducing Shorthands to Legacy Codebases

When working with legacy codebases, introduce modern shorthands gradually:

  1. Start with low-risk changes - Template literals and object property shorthand are safe entry points
  2. Establish coding standards - Document which shorthands are encouraged and which to avoid
  3. Use automated tools - Configure ESLint and Prettier to enforce consistency
  4. Pair with team members - Explain the benefits when making changes during code reviews
  5. Focus on new code - Update existing code when you're already working in that file

The goal is consistency across your codebase, whether that's modern shorthand patterns or traditional syntax. Mixing styles arbitrarily creates cognitive overhead for developers navigating the code.

For teams transitioning to TypeScript, our web development services include code review and best practices training to help your team adopt modern patterns effectively.

Frequently Asked Questions

When should I use optional chaining vs. regular property access?

Use optional chaining (?.) when accessing properties that may not exist, especially in scenarios with dynamic data, API responses, or user-generated content. Use regular access (.) when you're certain the property exists or when the absence should trigger an error for debugging. Optional chaining is most valuable at system boundaries where data structure cannot be guaranteed.

Are there performance differences between shorthand and longhand code?

In most cases, modern JavaScript engines optimize both patterns equally. The runtime performance difference is negligible for typical web applications. However, spread operators on very large arrays may be slightly slower than concat(). The main benefit of shorthands is developer productivity, code maintainability, and reduced error rates, not raw performance.

How do I introduce shorthand syntax to a legacy codebase?

Start with low-risk changes like template literals and object shorthand. Establish coding standards with your team and document which patterns are encouraged. Use automated tools like Prettier and ESLint to enforce consistency. Gradually introduce more complex shorthands as the team becomes comfortable. Focus on new code first, then update existing code when you're already working in that area.

What are the browser compatibility considerations for modern shorthand features?

Most ES6+ shorthand features are supported in all modern browsers released after 2016. Optional chaining and nullish coalescing work in all browsers released after 2020. For older browser support, use Babel to transpile modern syntax. Check caniuse.com for specific feature support tables. Modern web applications typically target recent browser versions, making these features safe to use in production.

Ready to Optimize Your Web Development Workflow?

Our team of expert developers specializes in modern JavaScript and TypeScript patterns that improve code quality and developer productivity. Let us help you build maintainable, performant applications.